From 93f6cf9c41fb8c5ae1b92709f6c9604ed1baccd5 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Thu, 12 Jun 2025 09:49:42 +0200 Subject: [PATCH 01/44] - Add enums - Add doc start - Add Paginator / PaginationState --- .../Components/DataGrid/FluentDataGrid.md | 9 ++ .../Migration/MigrationFluentDataGrid.md | 8 ++ spelling.dic | 8 ++ src/Core/Components/Icons/CoreIcons.cs | 60 +++++++++- .../Pagination/FluentPaginator.razor | 42 +++++++ .../Pagination/FluentPaginator.razor.cs | 95 ++++++++++++++++ .../Pagination/FluentPaginator.razor.css | 25 +++++ .../Components/Pagination/PaginationState.cs | 104 ++++++++++++++++++ .../TotalItemCountChangedEventArgs.cs | 22 ++++ src/Core/Enums/DataGridCellType.cs | 30 +++++ src/Core/Enums/DataGridDisplayMode.cs | 24 ++++ src/Core/Enums/DataGridResizeType.cs | 21 ++++ src/Core/Enums/DataGridRowSize.cs | 32 ++++++ src/Core/Enums/DataGridRowType.cs | 29 +++++ src/Core/Enums/DataGridSelectMode.cs | 26 +++++ src/Core/Enums/DataGridSortDirection.cs | 28 +++++ src/Core/Enums/HorizontalAlignment.cs | 17 +++ .../EventCallbackSubscribable.cs | 37 +++++++ .../Infrastructure/EventCallbackSubscriber.cs | 47 ++++++++ 19 files changed, 660 insertions(+), 4 deletions(-) create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/GetStarted/Migration/MigrationFluentDataGrid.md create mode 100644 src/Core/Components/Pagination/FluentPaginator.razor create mode 100644 src/Core/Components/Pagination/FluentPaginator.razor.cs create mode 100644 src/Core/Components/Pagination/FluentPaginator.razor.css create mode 100644 src/Core/Components/Pagination/PaginationState.cs create mode 100644 src/Core/Components/Pagination/TotalItemCountChangedEventArgs.cs create mode 100644 src/Core/Enums/DataGridCellType.cs create mode 100644 src/Core/Enums/DataGridDisplayMode.cs create mode 100644 src/Core/Enums/DataGridResizeType.cs create mode 100644 src/Core/Enums/DataGridRowSize.cs create mode 100644 src/Core/Enums/DataGridRowType.cs create mode 100644 src/Core/Enums/DataGridSelectMode.cs create mode 100644 src/Core/Enums/DataGridSortDirection.cs create mode 100644 src/Core/Infrastructure/EventCallbackSubscribable.cs create mode 100644 src/Core/Infrastructure/EventCallbackSubscriber.cs diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md new file mode 100644 index 0000000000..5ac04a4efc --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md @@ -0,0 +1,9 @@ +--- +title: DataGrid +route: /DataGrid +--- + +# DataGrid + +Overview page and examples to follow + diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/GetStarted/Migration/MigrationFluentDataGrid.md b/examples/Demo/FluentUI.Demo.Client/Documentation/GetStarted/Migration/MigrationFluentDataGrid.md new file mode 100644 index 0000000000..2ac06c663f --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/GetStarted/Migration/MigrationFluentDataGrid.md @@ -0,0 +1,8 @@ +### Renamed parameters +- `ColumnOptionsLabels` has been renamed to `ColumnOptionUISettings` +- `ColumnResizeLabels` has been renamed to `ColumnResizeUISettings` +- `ColumnSortLabels` has been renamed to `ColumnSortUISettings` + +### Enum changes +- `SortDirection` has been renamed to `DataGridSortDirection` +- `Align` has been renamed to `HorizontalAlignment` diff --git a/spelling.dic b/spelling.dic index ef868a5175..c1e5200f58 100644 --- a/spelling.dic +++ b/spelling.dic @@ -84,3 +84,11 @@ menuclicked menuchecked rendertree testid +columnheader +rowheader +datagrid +keycapture +wdelta +sortabillity +Subscribable +eventargs diff --git a/src/Core/Components/Icons/CoreIcons.cs b/src/Core/Components/Icons/CoreIcons.cs index 2fc651a871..7bb2829294 100644 --- a/src/Core/Components/Icons/CoreIcons.cs +++ b/src/Core/Components/Icons/CoreIcons.cs @@ -11,13 +11,32 @@ internal static partial class CoreIcons { /* * *************************** NOTES *************************** - * + * * Try to use only the Regular.Size20 and Filled.Size20 icons, * to avoid duplicating the same icon in different sizes. - * + * ************************************************************* */ + internal static partial class Regular + { + // TODO: We might need these for the FluentDataGrid header UI. Size 20 couldbe too big. Will delete if not needed + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + internal static partial class Size16 + { + //public class ArrowSort : Icon { public ArrowSort() : base("ArrowSort", IconVariant.Regular, IconSize.Size16, "") { } } + //public class CheckmarkCircle : Icon { public CheckmarkCircle() : base("CheckmarkCircle", IconVariant.Regular, IconSize.Size16, "") { } } + //public class Dismiss : Icon { public Dismiss() : base("Dismiss", IconVariant.Regular, IconSize.Size16, "") { } } + //public class DismissCircle : Icon { public DismissCircle() : base("DismissCircle", IconVariant.Regular, IconSize.Size16, "") { } } + //public class Filter : Icon { public Filter() : base("Filter", IconVariant.Regular, IconSize.Size16, "") { } } + //public class Info : Icon { public Info() : base("Info", IconVariant.Regular, IconSize.Size16, "") { } } + //public class MoreVertical : Icon { public MoreVertical() : base("MoreVertical", IconVariant.Regular, IconSize.Size16, "") { } } + //public class TableResizeColumn : Icon { public TableResizeColumn() : base("TableResizeColumn", IconVariant.Regular, IconSize.Size16, "") { } } + //public class Warning : Icon { public Warning() : base("Warning", IconVariant.Regular, IconSize.Size16, "") { } } + //public class Search : Icon { public Search() : base("Search", IconVariant.Regular, IconSize.Size16, "") { } } + } + } + /// /// Regular icons /// @@ -26,14 +45,47 @@ internal static partial class Regular [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static partial class Size20 { + public class Add : Icon { public Add() : base("Add", IconVariant.Regular, IconSize.Size20, "") { } } + + public class ArrowReset : Icon { public ArrowReset() : base("ArrowReset", IconVariant.Regular, IconSize.Size20, "") { } } + + public class ArrowSort : Icon { public ArrowSort() : base("ArrowSort", IconVariant.Regular, IconSize.Size20, "") { } } + + public class ArrowSortDown : Icon { public ArrowSortDown() : base("ArrowSortDown", IconVariant.Regular, IconSize.Size20, "") { } } + + public class ArrowSortUp : Icon { public ArrowSortUp() : base("ArrowSortUp", IconVariant.Regular, IconSize.Size20, "") { } } + + public class ChevronDown : Icon { public ChevronDown() : base("ChevronDown", IconVariant.Regular, IconSize.Size20, "") { } } + + public class ChevronDoubleLeft : Icon { public ChevronDoubleLeft() : base("ChevronDoubleLeft", IconVariant.Regular, IconSize.Size20, "") { } } + + public class ChevronDoubleRight : Icon { public ChevronDoubleRight() : base("ChevronDoubleRight", IconVariant.Regular, IconSize.Size20, "") { } } + + public class Dismiss : Icon { public Dismiss() : base("Dismiss", IconVariant.Regular, IconSize.Size20, "") { } }; + + public class Filter : Icon { public Filter() : base("Filter", IconVariant.Regular, IconSize.Size20, "") { } } + + public class FilterDismiss : Icon { public FilterDismiss() : base("FilterDismiss", IconVariant.Regular, IconSize.Size20, "") { } } + public class LineHorizontal3 : Icon { public LineHorizontal3() : base("LineHorizontal3", IconVariant.Regular, IconSize.Size20, "") { } }; public class QuestionCircle : Icon { public QuestionCircle() : base("QuestionCircle", IconVariant.Regular, IconSize.Size20, "") { } }; - public class Dismiss : Icon { public Dismiss() : base("Dismiss", IconVariant.Regular, IconSize.Size20, "") { } }; + public class Subtract : Icon { public Subtract() : base("Subtract", IconVariant.Regular, IconSize.Size20, "") { } } + + public class TableResizeColumn : Icon { public TableResizeColumn() : base("TableResizeColumn", IconVariant.Regular, IconSize.Size20, "") { } } } } + internal static partial class Regular + { + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + internal static partial class Size24 + { + public class ChevronLeft : Icon { public ChevronLeft() : base("ChevronLeft", IconVariant.Regular, IconSize.Size24, "") { } } + public class ChevronRight : Icon { public ChevronRight() : base("ChevronRight", IconVariant.Regular, IconSize.Size24, "") { } } + } + } /// /// Filled icons /// @@ -48,7 +100,7 @@ public class Info : Icon { public Info() : base("Info", IconVariant.Filled, Icon public class Warning : Icon { public Warning() : base("Warning", IconVariant.Filled, IconSize.Size20, "") { } } - public class DismissCircle : Icon { public DismissCircle() : base("WaDismissCirclerning", IconVariant.Filled, IconSize.Size20, "") { } } + public class DismissCircle : Icon { public DismissCircle() : base("DismissCircle", IconVariant.Filled, IconSize.Size20, "") { } } } } } diff --git a/src/Core/Components/Pagination/FluentPaginator.razor b/src/Core/Components/Pagination/FluentPaginator.razor new file mode 100644 index 0000000000..975959e8ff --- /dev/null +++ b/src/Core/Components/Pagination/FluentPaginator.razor @@ -0,0 +1,42 @@ +@namespace Microsoft.FluentUI.AspNetCore.Components +@inherits FluentComponentBase +
+ @if (State.TotalItemCount.HasValue) + { +
+ @if (SummaryTemplate is not null) + { + @SummaryTemplate + } + else + { + @State.TotalItemCount items + } +
+ + } +
diff --git a/src/Core/Components/Pagination/FluentPaginator.razor.cs b/src/Core/Components/Pagination/FluentPaginator.razor.cs new file mode 100644 index 0000000000..ae792e3ca6 --- /dev/null +++ b/src/Core/Components/Pagination/FluentPaginator.razor.cs @@ -0,0 +1,95 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +using Microsoft.AspNetCore.Components; +using Microsoft.FluentUI.AspNetCore.Components.Infrastructure; + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// A component that provides a user interface for . +/// +public partial class FluentPaginator : FluentComponentBase, IDisposable +{ + private readonly EventCallbackSubscriber _totalItemCountChanged; + private readonly EventCallbackSubscriber _currentPageItemsChanged; + + /// + /// Gets or sets the callback that is invoked when the current page index changes. + /// + /// The callback receives the new page index as an parameter. Use this property + /// to handle page index changes in a parent component or service. + [Parameter] + public EventCallback CurrentPageIndexChanged { get; set; } + + /// + /// Disables the pagination buttons + /// + [Parameter] + public bool Disabled { get; set; } + + /// + /// Gets or sets the associated . This parameter is required. + /// + [Parameter, EditorRequired] + public PaginationState State { get; set; } = default!; + + /// + /// Optionally supplies a template for rendering the page count summary. + /// The following values can be included: + /// {your State parameter name}.TotalItemCount (for the total number of items) + /// + [Parameter] + public RenderFragment? SummaryTemplate { get; set; } + + /// + /// Optionally supplies a template for rendering the pagination summary. + /// The following values can be included: + /// {your State parameter name}.CurrentPageIndex (zero-based, so +1 for the current page number) + /// {your State parameter name}.LastPageIndex (zero-based, so +1 for the total number of pages) + /// + [Parameter] + public RenderFragment? PaginationTextTemplate { get; set; } + + /// + /// Constructs an instance of . + /// + public FluentPaginator() + { + // The "total item count" handler doesn't need to do anything except cause this component to re-render + _totalItemCountChanged = new(new EventCallback(this, null)); + _currentPageItemsChanged = new(new EventCallback(this, null)); + } + + private Task GoFirstAsync() => GoToPageAsync(0); + private Task GoPreviousAsync() => GoToPageAsync(State.CurrentPageIndex - 1); + private Task GoNextAsync() => GoToPageAsync(State.CurrentPageIndex + 1); + private Task GoLastAsync() => GoToPageAsync(State.LastPageIndex.GetValueOrDefault(0)); + + private bool CanGoBack => State.CurrentPageIndex > 0; + private bool CanGoForwards => State.CurrentPageIndex < State.LastPageIndex; + + private async Task GoToPageAsync(int pageIndex) + { + await State.SetCurrentPageIndexAsync(pageIndex); + if (CurrentPageIndexChanged.HasDelegate) + { + await CurrentPageIndexChanged.InvokeAsync(State.CurrentPageIndex); + } + } + + /// + protected override void OnParametersSet() + { + _totalItemCountChanged.SubscribeOrMove(State.TotalItemCountChangedSubscribable); + _currentPageItemsChanged.SubscribeOrMove(State.CurrentPageItemsChanged); + } + + /// + public void Dispose() + { + _totalItemCountChanged.Dispose(); + _currentPageItemsChanged.Dispose(); + } +} diff --git a/src/Core/Components/Pagination/FluentPaginator.razor.css b/src/Core/Components/Pagination/FluentPaginator.razor.css new file mode 100644 index 0000000000..c0724d89aa --- /dev/null +++ b/src/Core/Components/Pagination/FluentPaginator.razor.css @@ -0,0 +1,25 @@ +.paginator { + display: flex; + /*border-top: 1px solid var(--neutral-stroke-divider-rest);*/ + margin-top: 0.5rem; + padding: 0.25rem 0; + align-items: center; +} + +.pagination-text { + margin: 0 0.5rem; +} + +.paginator-nav { + padding: 0; + display: flex; + margin-inline-start: auto; + margin-inline-end: 0; + gap: 0.5rem; + align-items: center; +} + + +[dir="rtl"] * ::deep fluent-button > svg { + transform: rotate(180deg); +} diff --git a/src/Core/Components/Pagination/PaginationState.cs b/src/Core/Components/Pagination/PaginationState.cs new file mode 100644 index 0000000000..8d80041320 --- /dev/null +++ b/src/Core/Components/Pagination/PaginationState.cs @@ -0,0 +1,104 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +using Microsoft.FluentUI.AspNetCore.Components.Infrastructure; + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// Holds state to represent pagination in a . +/// +public class PaginationState +{ + /// + /// Gets or sets the number of items on each page. + /// To set it and update any associated , call + /// + public int ItemsPerPage { get; set; } = 10; + + /// + /// Gets the current zero-based page index. To set it, call . + /// + public int CurrentPageIndex { get; private set; } + + /// + /// Gets the total number of items across all pages, if known. The value will be null until an + /// associated assigns a value after loading data. + /// + public int? TotalItemCount { get; private set; } + + /// + /// Gets the zero-based index of the last page, if known. The value will be null until is known. + /// + public int? LastPageIndex => (TotalItemCount - 1) / ItemsPerPage; + + /// + /// An event that is raised when the total item count has changed. + /// + public event EventHandler? TotalItemCountChanged; + + internal EventCallbackSubscribable CurrentPageItemsChanged { get; } = new(); + internal EventCallbackSubscribable TotalItemCountChangedSubscribable { get; } = new(); + + /// + public override int GetHashCode() + => HashCode.Combine(ItemsPerPage, CurrentPageIndex, TotalItemCount); + + /// + /// Sets the current page index, and notifies any associated + /// to fetch and render updated data. + /// + /// The new, zero-based page index. + /// A representing the completion of the operation. + public Task SetCurrentPageIndexAsync(int pageIndex) + { + CurrentPageIndex = pageIndex; + return CurrentPageItemsChanged.InvokeCallbacksAsync(this); + } + + /// + /// Sets the items per page and notifies any associated + /// to fetch and render updated data. + /// + /// The new number of items per page. + /// A representing the completion of the operation. + public async Task SetItemsPerPageAsync(int itemsPerPage) + { + ItemsPerPage = itemsPerPage; + + await CurrentPageItemsChanged.InvokeCallbacksAsync(this); + if (TotalItemCount.HasValue) + { + await SetTotalItemCountAsync(TotalItemCount.Value, true); + } + return; + } + + /// + /// Sets the total number of items and makes sure the current page index stays valid. + /// + /// The total number of items + /// If true, the total item count will be updated even if it is the same as the current value. + /// + public Task SetTotalItemCountAsync(int totalItemCount, bool force = false) + { + if (totalItemCount == TotalItemCount && !force) + { + return Task.CompletedTask; + } + + TotalItemCount = totalItemCount; + + if (CurrentPageIndex > 0 && CurrentPageIndex > LastPageIndex) + { + // If the number of items has reduced such that the current page index is no longer valid, move + // automatically to the final valid page index and trigger a further data load. + _ = SetCurrentPageIndexAsync(LastPageIndex.Value); + } + + // Under normal circumstances, we just want any associated pagination UI to update + TotalItemCountChanged?.Invoke(this, new TotalItemCountChangedEventArgs(TotalItemCount)); + return TotalItemCountChangedSubscribable.InvokeCallbacksAsync(this); + } +} diff --git a/src/Core/Components/Pagination/TotalItemCountChangedEventArgs.cs b/src/Core/Components/Pagination/TotalItemCountChangedEventArgs.cs new file mode 100644 index 0000000000..415cbeeda6 --- /dev/null +++ b/src/Core/Components/Pagination/TotalItemCountChangedEventArgs.cs @@ -0,0 +1,22 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// Provides data for the event. +/// +public class TotalItemCountChangedEventArgs : EventArgs +{ + /// + /// Initializes a new instance of the class. + /// + /// The total item count. + public TotalItemCountChangedEventArgs(int? totalItemCount) => TotalItemCount = totalItemCount; + + /// + /// Gets the total item count. + /// + public int? TotalItemCount { get; } +} diff --git a/src/Core/Enums/DataGridCellType.cs b/src/Core/Enums/DataGridCellType.cs new file mode 100644 index 0000000000..72c20e6884 --- /dev/null +++ b/src/Core/Enums/DataGridCellType.cs @@ -0,0 +1,30 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +using System.ComponentModel; + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// The type of in a . +/// +public enum DataGridCellType +{ + /// + /// A normal cell. + /// + Default, + + /// + /// A header cell. + /// + [Description("columnheader")] + ColumnHeader, + + /// + /// Cell is a row header. + /// + [Description("rowheader")] + RowHeader, +} diff --git a/src/Core/Enums/DataGridDisplayMode.cs b/src/Core/Enums/DataGridDisplayMode.cs new file mode 100644 index 0000000000..9050b71151 --- /dev/null +++ b/src/Core/Enums/DataGridDisplayMode.cs @@ -0,0 +1,24 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// The type of rendering to use for the +/// +public enum DataGridDisplayMode +{ + /// + /// Uses display:grid with HTML table elements to render the DataGrid. + /// With this mode fr units can be used to set the column widths. + /// + Grid, + + /// + /// Uses HTML table elements only to render the DataGrid. + /// With this mode fr units cannot be used to set the column widths. + /// + Table, + +} diff --git a/src/Core/Enums/DataGridResizeType.cs b/src/Core/Enums/DataGridResizeType.cs new file mode 100644 index 0000000000..55e806bb31 --- /dev/null +++ b/src/Core/Enums/DataGridResizeType.cs @@ -0,0 +1,21 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// The type of in a . +/// +public enum DataGridResizeType +{ + /// + /// Resize datagrid columns by discreet steps of 10 pixels + /// + Discrete, + + /// + /// Resize datagrid columns by exact pixel values + /// + Exact, +} diff --git a/src/Core/Enums/DataGridRowSize.cs b/src/Core/Enums/DataGridRowSize.cs new file mode 100644 index 0000000000..6c9296a4ec --- /dev/null +++ b/src/Core/Enums/DataGridRowSize.cs @@ -0,0 +1,32 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// The height of each in a . +/// Values are in pixels. +/// +public enum DataGridRowSize +{ + /// + /// Small row height (default) + /// + Small = 32, + + /// + /// Medium row height + /// + Medium = 44, + + /// + /// Smaller row height + /// + Smaller = 24, + + /// + /// Large row height + /// + Large = 58, +} diff --git a/src/Core/Enums/DataGridRowType.cs b/src/Core/Enums/DataGridRowType.cs new file mode 100644 index 0000000000..84d838e678 --- /dev/null +++ b/src/Core/Enums/DataGridRowType.cs @@ -0,0 +1,29 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +using System.ComponentModel; + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// The type of in a . +/// +public enum DataGridRowType +{ + /// + /// A normal row . + /// + Default, + + /// + /// A header row. + /// + Header, + + /// + /// A sticky header row. + /// + [Description("sticky-header")] + StickyHeader, +} diff --git a/src/Core/Enums/DataGridSelectMode.cs b/src/Core/Enums/DataGridSelectMode.cs new file mode 100644 index 0000000000..e9a9c1204b --- /dev/null +++ b/src/Core/Enums/DataGridSelectMode.cs @@ -0,0 +1,26 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// How rows can be selected in a when using a . +/// +public enum DataGridSelectMode +{ + /// + /// Allow only one selected row. + /// + Single, + + /// + /// Allow only one selected row. Keep selection if same row is clicked. + /// + SingleSticky, + + /// + /// Allow multiple selected rows. + /// + Multiple, +} diff --git a/src/Core/Enums/DataGridSortDirection.cs b/src/Core/Enums/DataGridSortDirection.cs new file mode 100644 index 0000000000..7f3d9fd615 --- /dev/null +++ b/src/Core/Enums/DataGridSortDirection.cs @@ -0,0 +1,28 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// Describes the direction in which a column is sorted. +/// +public enum DataGridSortDirection +{ + /// + /// Automatic sort order. When used with , + /// the sort order will automatically toggle between and on successive calls, and + /// resets to whenever the specified column is changed. + /// + Auto, + + /// + /// Ascending order. + /// + Ascending, + + /// + /// Descending order. + /// + Descending, +} diff --git a/src/Core/Enums/HorizontalAlignment.cs b/src/Core/Enums/HorizontalAlignment.cs index 3f69005058..574e3949b7 100644 --- a/src/Core/Enums/HorizontalAlignment.cs +++ b/src/Core/Enums/HorizontalAlignment.cs @@ -2,6 +2,8 @@ // MIT License - Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------------------ +using System.ComponentModel; + namespace Microsoft.FluentUI.AspNetCore.Components; /// @@ -11,36 +13,51 @@ public enum HorizontalAlignment { /// /// The content is aligned to the left. + /// Use for better RTL support. /// + [Description("flex-start")] Left, /// /// The content is aligned to the start. /// + [Description("flex-start")] Start, /// /// The content is center aligned. /// + [Description("center")] Center, /// /// The content is aligned to the right. + /// Use for better RTL support. /// + [Description("flex-end")] Right, /// /// The content is aligned to the end. /// + [Description("flex-end")] End, /// /// The content is stretched to fill the available space. /// + [Description("stretch")] Stretch, /// /// The items are evenly distributed within the alignment container along the main axis. /// + [Description("space-between")] SpaceBetween, + + /// + /// Aligns content at the baseline of the container. + /// + [Description("baseline")] + Baseline, } diff --git a/src/Core/Infrastructure/EventCallbackSubscribable.cs b/src/Core/Infrastructure/EventCallbackSubscribable.cs new file mode 100644 index 0000000000..a423e93912 --- /dev/null +++ b/src/Core/Infrastructure/EventCallbackSubscribable.cs @@ -0,0 +1,37 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +using Microsoft.AspNetCore.Components; + +namespace Microsoft.FluentUI.AspNetCore.Components.Infrastructure; + +/// +/// Represents an event that you may subscribe to. This differs from normal C# events in that the handlers +/// are EventCallback, and so may have async behaviors and cause component re-rendering +/// while retaining error flow. +/// +/// A type for the eventargs. +internal sealed class EventCallbackSubscribable +{ + private readonly Dictionary, EventCallback> _callbacks = []; + + /// + /// Invokes all the registered callbacks sequentially, in an undefined order. + /// + public async Task InvokeCallbacksAsync(T eventArg) + { + foreach (var callback in _callbacks.Values) + { + await callback.InvokeAsync(eventArg); + } + } + + // Don't call this directly - it gets called by EventCallbackSubscription + public void Subscribe(EventCallbackSubscriber owner, EventCallback callback) + => _callbacks.Add(owner, callback); + + // Don't call this directly - it gets called by EventCallbackSubscription + public void Unsubscribe(EventCallbackSubscriber owner) + => _callbacks.Remove(owner); +} diff --git a/src/Core/Infrastructure/EventCallbackSubscriber.cs b/src/Core/Infrastructure/EventCallbackSubscriber.cs new file mode 100644 index 0000000000..e8e861887a --- /dev/null +++ b/src/Core/Infrastructure/EventCallbackSubscriber.cs @@ -0,0 +1,47 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +using Microsoft.AspNetCore.Components; + +namespace Microsoft.FluentUI.AspNetCore.Components.Infrastructure; + +/// +/// Represents a subscriber that may be subscribe to an . +/// The subscription can move between instances over time, +/// and automatically unsubscribes from earlier instances +/// whenever it moves to a new one. +/// +internal sealed class EventCallbackSubscriber : IDisposable +{ + private readonly EventCallback _handler; + private EventCallbackSubscribable? _existingSubscription; + + public EventCallbackSubscriber(EventCallback handler) + { + _handler = handler; + } + + /// + /// Creates a subscription on the , or moves any existing subscription to it + /// by first unsubscribing from the previous . + /// + /// If the supplied is null, no new subscription will be created, but any + /// existing one will still be unsubscribed. + /// + /// + public void SubscribeOrMove(EventCallbackSubscribable? subscribable) + { + if (subscribable != _existingSubscription) + { + _existingSubscription?.Unsubscribe(this); + subscribable?.Subscribe(this, _handler); + _existingSubscription = subscribable; + } + } + + public void Dispose() + { + _existingSubscription?.Unsubscribe(this); + } +} From b255f69afc3468463258725acc95852b74df57d6 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Thu, 12 Jun 2025 10:07:57 +0200 Subject: [PATCH 02/44] - Add DataGrid component --- .../DataGrid/Columns/ColumnBase.razor | 221 ++++ .../DataGrid/Columns/ColumnBase.razor.cs | 350 +++++ .../DataGrid/Columns/ColumnBase.razor.css | 16 + .../DataGrid/Columns/ColumnKeyGridSort.cs | 57 + .../Columns/ColumnOptionsUISettings.cs | 35 + .../Columns/ColumnResizeOptions.razor | 37 + .../Columns/ColumnResizeOptions.razor.cs | 73 ++ .../Columns/ColumnResizeUISettings.cs | 65 + .../DataGrid/Columns/ColumnSortUISettings.cs | 45 + .../Components/DataGrid/Columns/GridSort.cs | 315 +++++ .../Components/DataGrid/Columns/IGridSort.cs | 27 + .../DataGrid/Columns/PropertyColumn.cs | 130 ++ .../DataGrid/Columns/SelectColumn.cs | 560 ++++++++ .../DataGrid/Columns/SortedProperty.cs | 21 + .../DataGrid/Columns/TemplateColumn.cs | 34 + .../Components/DataGrid/FluentDataGrid.razor | 263 ++++ .../DataGrid/FluentDataGrid.razor.cs | 1137 +++++++++++++++++ .../DataGrid/FluentDataGrid.razor.css | 91 ++ .../DataGrid/FluentDataGridCell.razor | 31 + .../DataGrid/FluentDataGridCell.razor.cs | 128 ++ .../DataGrid/FluentDataGridCell.razor.css | 108 ++ .../DataGrid/FluentDataGridRow.razor | 18 + .../DataGrid/FluentDataGridRow.razor.cs | 177 +++ .../DataGrid/FluentDataGridRow.razor.css | 10 + .../Components/DataGrid/GridItemsProvider.cs | 10 + .../DataGrid/GridItemsProviderRequest.cs | 90 ++ .../DataGrid/GridItemsProviderResult.cs | 39 + .../AsyncQueryExecutorSupplier.cs | 60 + .../ColumnsCollectedNotifier.cs | 60 + .../DataGrid/Infrastructure/Defer.cs | 28 + .../DisplayAttributeExtensions.cs | 34 + .../Infrastructure/IAsyncQueryExecutor.cs | 33 + .../Infrastructure/IBindableColumn.cs | 23 + .../Infrastructure/InternalGridContext.cs | 60 + .../Components/Pagination/PaginationState.cs | 1 + 35 files changed, 4387 insertions(+) create mode 100644 src/Core/Components/DataGrid/Columns/ColumnBase.razor create mode 100644 src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs create mode 100644 src/Core/Components/DataGrid/Columns/ColumnBase.razor.css create mode 100644 src/Core/Components/DataGrid/Columns/ColumnKeyGridSort.cs create mode 100644 src/Core/Components/DataGrid/Columns/ColumnOptionsUISettings.cs create mode 100644 src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor create mode 100644 src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor.cs create mode 100644 src/Core/Components/DataGrid/Columns/ColumnResizeUISettings.cs create mode 100644 src/Core/Components/DataGrid/Columns/ColumnSortUISettings.cs create mode 100644 src/Core/Components/DataGrid/Columns/GridSort.cs create mode 100644 src/Core/Components/DataGrid/Columns/IGridSort.cs create mode 100644 src/Core/Components/DataGrid/Columns/PropertyColumn.cs create mode 100644 src/Core/Components/DataGrid/Columns/SelectColumn.cs create mode 100644 src/Core/Components/DataGrid/Columns/SortedProperty.cs create mode 100644 src/Core/Components/DataGrid/Columns/TemplateColumn.cs create mode 100644 src/Core/Components/DataGrid/FluentDataGrid.razor create mode 100644 src/Core/Components/DataGrid/FluentDataGrid.razor.cs create mode 100644 src/Core/Components/DataGrid/FluentDataGrid.razor.css create mode 100644 src/Core/Components/DataGrid/FluentDataGridCell.razor create mode 100644 src/Core/Components/DataGrid/FluentDataGridCell.razor.cs create mode 100644 src/Core/Components/DataGrid/FluentDataGridCell.razor.css create mode 100644 src/Core/Components/DataGrid/FluentDataGridRow.razor create mode 100644 src/Core/Components/DataGrid/FluentDataGridRow.razor.cs create mode 100644 src/Core/Components/DataGrid/FluentDataGridRow.razor.css create mode 100644 src/Core/Components/DataGrid/GridItemsProvider.cs create mode 100644 src/Core/Components/DataGrid/GridItemsProviderRequest.cs create mode 100644 src/Core/Components/DataGrid/GridItemsProviderResult.cs create mode 100644 src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs create mode 100644 src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs create mode 100644 src/Core/Components/DataGrid/Infrastructure/Defer.cs create mode 100644 src/Core/Components/DataGrid/Infrastructure/DisplayAttributeExtensions.cs create mode 100644 src/Core/Components/DataGrid/Infrastructure/IAsyncQueryExecutor.cs create mode 100644 src/Core/Components/DataGrid/Infrastructure/IBindableColumn.cs create mode 100644 src/Core/Components/DataGrid/Infrastructure/InternalGridContext.cs diff --git a/src/Core/Components/DataGrid/Columns/ColumnBase.razor b/src/Core/Components/DataGrid/Columns/ColumnBase.razor new file mode 100644 index 0000000000..594b88ebca --- /dev/null +++ b/src/Core/Components/DataGrid/Columns/ColumnBase.razor @@ -0,0 +1,221 @@ +@using Microsoft.AspNetCore.Components.Rendering +@using Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure +@namespace Microsoft.FluentUI.AspNetCore.Components +@typeparam TGridItem +@{ + Grid.AddColumn(this, InitialSortDirection, IsDefaultSortColumn); +} +@code +{ + private void RenderDefaultHeaderTitle(RenderTreeBuilder __builder) + { + @if (HeaderCellTitleTemplate is not null) + { + @HeaderCellTitleTemplate(this) + } + else + { + @Title + } + } + + private void RenderDefaultHeaderContent(RenderTreeBuilder __builder) + { + @if (HeaderCellItemTemplate is not null) + { + @HeaderCellItemTemplate(this) + } + else if (Grid.HeaderCellAsButtonWithMenu) + { + string? tooltip = Tooltip ? (HeaderTooltip ?? Title) : null; + + + + @if (AnyColumnActionEnabled) + { + +
+ @HeaderTitleContent +
+ + @if (Grid.SortByAscending.HasValue && IsActiveSortColumn) + { + if (Grid.SortByAscending == true) + { + + } + else + { + + } + } + @if (ColumnOptions is not null && Filtered.GetValueOrDefault()) + { + + } +
+ } + else + { +
+
+ @HeaderTitleContent +
+
+ } + + @if (Sortable.HasValue ? Sortable.Value : IsSortableByDefault()) + { + + @GetSortOptionText() + @if (Grid.ColumnSortLabels.Icon is not null) + { + + + + } + + } + @if (Grid.ResizeType is not null && Grid.ResizableColumns) + { + + @Grid.ColumnResizeLabels.ResizeMenu + @if (Grid.ColumnResizeLabels.Icon is not null) + { + + + + } + + } + @if (ColumnOptions is not null) + { + + @Grid.ColumnOptionsLabels.OptionsMenu + @if (Grid.ColumnOptionsLabels.Icon is not null) + { + + + + } + + } + +
+ } + else + { + string? tooltip = Tooltip ? (HeaderTooltip ?? Title) : null; + string? wdelta = "10px"; + string? align; + + // determine align string based on Align value + align = Align switch + { + Align.Start => "flex-start", + Align.Center => "center", + Align.End => "flex-end", + _ => "flex-start" + }; + +
+ @if (Align == Align.Start || Align == Align.Center) + { + @if (Grid.ResizeType is not null) + { + @OptionsButton() + } + else + { + @if (ColumnOptions is not null) + { + @FilterButton() + } + } + } + + @if (Sortable.HasValue ? Sortable.Value : IsSortableByDefault()) + { + + +
+ @HeaderTitleContent +
+ + @if (Grid.SortByAscending.HasValue && IsActiveSortColumn) + { + if (Grid.SortByAscending == true) + { + + } + else + { + + } + } + @if (ColumnOptions is not null && Filtered.GetValueOrDefault()) + { + + } +
+
+ } + else + { +
+
+ @HeaderTitleContent + @if (ColumnOptions is not null && Filtered.GetValueOrDefault() && Grid.ResizeType.HasValue) + { + + + + } +
+
+ } + + @if (Align == Align.End) + { + @if (Grid.ResizeType is not null) + { + @OptionsButton() + } + else + { + @if (ColumnOptions is not null) + { + @FilterButton() + } + } + } +
+ + } + } + + internal void RenderPlaceholderContent(RenderTreeBuilder __builder, PlaceholderContext placeholderContext) + { + // Blank if no placeholder template was supplied, as it's enough to style with CSS by default + if (PlaceholderTemplate is not null) + { + @PlaceholderTemplate(placeholderContext) + } + } + + private RenderFragment OptionsButton() + { + return + @ + + ; + } + + private RenderFragment FilterButton() + { + return + @ + + ; + } +} diff --git a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs new file mode 100644 index 0000000000..091c6449f8 --- /dev/null +++ b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs @@ -0,0 +1,350 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +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; +using Microsoft.FluentUI.AspNetCore.Components.Utilities; + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// An abstract base class for columns in a . +/// +/// The type of data represented by each row in the grid. +public abstract partial class ColumnBase +{ + private bool _isMenuOpen; + private static readonly string[] KEYBOARD_MENU_SELECT_KEYS = ["Enter", "NumpadEnter"]; + private readonly string _columnId = Identifier.NewId(); + private FluentMenu? _menu; + + [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; } + + /// + /// Gets or sets the index (1-based) of the column + /// + [Parameter] + public int Index { 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; } + + /// + /// 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; } + + /// + /// If specified, controls the justification of header and grid cells for this column. + /// + [Parameter] + public HorizontalAlignment Align { get; set; } + + /// + /// If true, generates a title and aria-label attribute for the cell contents + /// + [Parameter] + public bool Tooltip { get; set; } = false; + + /// + /// Gets or sets the function that defines the value to be used as the tooltip and aria-label in this column's cells + /// + [Parameter] + public Func? TooltipText { get; set; } + + /// + /// Gets or sets the tooltip text for the column header. + /// + [Parameter] + public string? HeaderTooltip { 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; } + + /// + /// Gets or sets a template for the title content of this column's header cell. + /// If not specified, the default header template includes the . + /// + [Parameter] + public RenderFragment>? HeaderCellTitleTemplate { get; set; } + + /// + /// If specified, indicates that this column has this associated options UI. A button to display this + /// UI will be included in the header cell by default. + /// + /// 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; } + + /// + /// Gets or sets a value indicating whether the data should be sortable by this column. + /// + /// The default value may vary according to the column type (for example, a + /// or is sortable by default if any + /// or parameter is specified). + /// + [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; } + + /// + /// Gets or sets the sorting rules for a column. + /// + public abstract IGridSort? SortBy { get; set; } + + /// + /// Gets or sets the initial sort direction. + /// if is true. + /// + [Parameter] + public DataGridSortDirection 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; + + /// + /// If specified, virtualized grids will use this template to render cells whose data has not yet been loaded. + /// + [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; } + + /// + /// Gets a reference to the enclosing . + /// + protected FluentDataGrid Grid => InternalGridContext.Grid; + + /// + /// Gets a value indicating whether any column-related action is enabled. + /// + protected bool AnyColumnActionEnabled => Sortable is true || ColumnOptions != null || Grid.ResizableColumns; + + /// + protected override void OnInitialized() + { + if (GetType() == typeof(SelectColumn)) + { + Align = HorizontalAlignment.Center; + } + } + + /// + /// 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. + /// + /// The current . + /// The data for the row being rendered. + protected internal abstract void CellContent(RenderTreeBuilder builder, TGridItem item); + + /// + /// Overridden by derived components to provide the raw content for the column's cells. + /// + /// The data for the row being rendered. + protected internal virtual string? RawCellContent(TGridItem item) => null; + + /// + /// Gets or sets a that will be rendered for this column's header cell. + /// This allows derived components to change the header output. However, derived components are then + /// responsible for using within that new output if they want to continue + /// respecting that option. + /// + protected internal RenderFragment HeaderContent { get; protected set; } + + /// + /// Gets or sets a that will be rendered for this column's header title. + /// This allows derived components to change the header title output. However, derived components are then + /// responsible for using within that new output if they want to continue + /// respecting that option. + /// + protected internal RenderFragment HeaderTitleContent { get; protected set; } + + /// + /// Gets a value indicating whether this column should act as sortable if no value was set for the + /// parameter. The default behavior is not to be + /// sortable unless is true. + /// + /// Derived components may override this to implement alternative default sortabillity rules. + /// + /// True if the column should be sortable by default, otherwise false. + protected virtual bool IsSortableByDefault() => false; + + /// + /// Handles the key down event and performs actions based on the key combination pressed. + /// + /// If the is pressed and the key is , this method triggers the removal of sorting for the associated grid column + /// asynchronously. + /// The event arguments containing details about the key press, including the key code and modifier keys. + protected async Task HandleKeyDownAsync(FluentKeyCodeEventArgs e) + { + if (e.ShiftKey && e.Key == KeyCode.KeyR) + { + await Grid.RemoveSortByColumnAsync(this); + } + } + + /// + /// Indicates whether the current column is the active sort column. + /// + public bool IsActiveSortColumn; + + /// + protected ColumnBase() + { + HeaderContent = RenderDefaultHeaderContent; + HeaderTitleContent = RenderDefaultHeaderTitle; + } + + private async Task HandleColumnHeaderClickedAsync() + { + var hasSorting = Sortable is true || IsDefaultSortColumn; + var hasResize = Grid.ResizableColumns; + var hasOptions = ColumnOptions is not null; + var hasMultiple = (hasSorting && hasResize) || (hasSorting && hasOptions) || (hasResize && hasOptions); + + if (hasMultiple) + { + _isMenuOpen = !_isMenuOpen; + StateHasChanged(); + } + else if (hasSorting) + { + await Grid.SortByColumnAsync(this); + } + else if (hasResize) + { + await Grid.ShowColumnResizeAsync(this); + } + else if (hasOptions) + { + await Grid.ShowColumnOptionsAsync(this); + } + } + + private async Task HandleSortMenuKeyDownAsync(KeyboardEventArgs args) + { + if (KEYBOARD_MENU_SELECT_KEYS.Contains(args.Key, StringComparer.OrdinalIgnoreCase)) + { + await Grid.SortByColumnAsync(this); + StateHasChanged(); + _isMenuOpen = false; + } + } + + private async Task HandleResizeMenuKeyDownAsync(KeyboardEventArgs args) + { + if (KEYBOARD_MENU_SELECT_KEYS.Contains(args.Key, StringComparer.OrdinalIgnoreCase)) + { + await Grid.ShowColumnResizeAsync(this); + _isMenuOpen = false; + } + } + + private async Task HandleOptionsMenuKeyDownAsync(KeyboardEventArgs args) + { + if (KEYBOARD_MENU_SELECT_KEYS.Contains(args.Key, StringComparer.OrdinalIgnoreCase)) + { + await Grid.ShowColumnOptionsAsync(this); + _isMenuOpen = false; + } + } + + private string GetSortOptionText() + { + if (Grid.SortByAscending.HasValue && IsActiveSortColumn) + { + if (Grid.SortByAscending is true) + { + return Grid.ColumnSortLabels.SortMenuAscendingLabel; + } + + return Grid.ColumnSortLabels.SortMenuDescendingLabel; + } + + return Grid.ColumnSortLabels.SortMenu; + } +} diff --git a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.css b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.css new file mode 100644 index 0000000000..d8dfd0a8e3 --- /dev/null +++ b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.css @@ -0,0 +1,16 @@ +.col-title { + padding: 6px 16px; + user-select: none; +} + +.col-title-text { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + font-weight: 600; +} + +th.col-justify-center svg { + align-content: center; + text-align:center; +} diff --git a/src/Core/Components/DataGrid/Columns/ColumnKeyGridSort.cs b/src/Core/Components/DataGrid/Columns/ColumnKeyGridSort.cs new file mode 100644 index 0000000000..64a25a1af3 --- /dev/null +++ b/src/Core/Components/DataGrid/Columns/ColumnKeyGridSort.cs @@ -0,0 +1,57 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// Represents a sort order specification used within using the column key . +/// +/// The type of data represented by each row in the grid. +public sealed class ColumnKeyGridSort : IGridSort +{ + private readonly string _columnKey; + private readonly Func, bool, IOrderedQueryable>? _sortFunction; + + internal ColumnKeyGridSort( + string columnKey, + Func, bool, IOrderedQueryable>? sortFunction = null) + { + _columnKey = columnKey; + _sortFunction = sortFunction; + } + + /// + /// Apply the sort function to the collection + /// + /// The collection to sort + /// Sort ascending (true) or descending (false) + /// /// The ordered collection + public IOrderedQueryable Apply(IQueryable queryable, bool ascending) + { + if (_sortFunction != null) + { + return _sortFunction(queryable, ascending); + } + + // If no sort is provided, apply a sort that has no affect in order to be able to return an IOrderedQueryable + return queryable.OrderBy(x => 0); + } + + /// + /// Produces a readonly collection of (property name, direction) pairs representing the sorting rules. + /// + /// + /// The readonly collection of properties that can be sorted on + public IReadOnlyCollection ToPropertyList(bool ascending) + { + return [ + new SortedProperty + { + PropertyName = _columnKey, + Direction = ascending + ? DataGridSortDirection.Ascending + : DataGridSortDirection.Descending, + }, + ]; + } +} diff --git a/src/Core/Components/DataGrid/Columns/ColumnOptionsUISettings.cs b/src/Core/Components/DataGrid/Columns/ColumnOptionsUISettings.cs new file mode 100644 index 0000000000..7576fedccb --- /dev/null +++ b/src/Core/Components/DataGrid/Columns/ColumnOptionsUISettings.cs @@ -0,0 +1,35 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// Represents the UI settings for column options (usually filtering), including labels, aria attributes, and menu options in the . +/// +/// This type provides customizable settings for the column options menu, including the menu text, icon, +/// and icon positioning. It also includes a default configuration that can be used as a baseline. +public record ColumnOptionsUISettings +{ + /// + /// Gets or sets the text shown in the column menu + /// + public string OptionsMenu { get; set; } = "Filter"; + + /// + /// Gets or sets the icon to show in the column menu + /// + public Icon? Icon { get; set; } = new CoreIcons.Regular.Size16.Filter(); + + /// + /// Gets or sets whether the icon is positioned at the start (true) or + /// at the end (false) of the menu item + /// + public bool IconPositionStart { get; set; } = true; + + /// + /// Gets the default labels for the options UI. + /// + public static ColumnOptionsUISettings Default => new(); + +} + diff --git a/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor b/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor new file mode 100644 index 0000000000..120a152342 --- /dev/null +++ b/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor @@ -0,0 +1,37 @@ +@namespace Microsoft.FluentUI.AspNetCore.Components +@typeparam TGridItem + + + @if (ResizeType == DataGridResizeType.Discrete) + { + @Grid.ColumnResizeLabels.DiscreteLabel + + + + + + } + else + { + +
+ +
+ + + + +
+ } +
+ diff --git a/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor.cs b/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor.cs new file mode 100644 index 0000000000..f50f02e35a --- /dev/null +++ b/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor.cs @@ -0,0 +1,73 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; + +namespace Microsoft.FluentUI.AspNetCore.Components; +public partial class ColumnResizeOptions +{ + private string? _width; + + [CascadingParameter] + internal InternalGridContext InternalGridContext { get; set; } = default!; + + /// + /// Gets a reference to the enclosing . + /// + private FluentDataGrid Grid => InternalGridContext.Grid; + + /// + /// Gets or sets the index of the Column to act upon + /// + [Parameter] + public int Column { get; set; } + + /// + /// Gets or sets the type of resize to perform + /// Discrete: resize by a 10 pixels at a time + /// Exact: resize to the exact width specified (in pixels) + /// The display of this component is dependant on a ResizeType being set + /// + [Parameter] + public DataGridResizeType? ResizeType { get; set; } = DataGridResizeType.Discrete; + + protected override void OnParametersSet() + { + if (Column == 0) + { + throw new ArgumentException("Column must have a value greater than zero"); + } + } + + private async Task HandleShrinkAsync() + { + await Grid.SetColumnWidthDiscreteAsync(Column, -10); + } + + private async Task HandleGrowAsync() + { + await Grid.SetColumnWidthDiscreteAsync(Column, 10); + } + + private async Task HandleResetAsync() + { + await Grid.ResetColumnWidthsAsync(); + } + + private async Task HandleColumnWidthAsync() + { + + var valid = int.TryParse(_width, out var result); + if (valid) + { + await Grid.SetColumnWidthExactAsync(Column, result); + } + } + + private async Task HandleColumnWidthKeyDownAsync(KeyboardEventArgs args) + { + if (args.Key == "Enter") + { + await HandleColumnWidthAsync(); + } + } +} diff --git a/src/Core/Components/DataGrid/Columns/ColumnResizeUISettings.cs b/src/Core/Components/DataGrid/Columns/ColumnResizeUISettings.cs new file mode 100644 index 0000000000..2e765df254 --- /dev/null +++ b/src/Core/Components/DataGrid/Columns/ColumnResizeUISettings.cs @@ -0,0 +1,65 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// Represents the UI settings for column resizing, including labels, aria attributes, and menu options in the . +/// +/// This record provides customizable settings for the column resize UI, such as menu text, labels for +/// different resize modes, aria labels for accessibility, and icon configuration. Use this type to configure the +/// appearance and behavior of the column resize functionality in your application. +public record ColumnResizeUISettings +{ + /// + /// Gets or sets the text shown in the column menu + /// + public string ResizeMenu { get; set; } = "Resize"; + + /// + /// Gets or sets the label in the discrete mode resize UI + /// + public string DiscreteLabel { get; set; } = "Column width"; + + /// + /// Gets or sets the label in the exact mode resize UI + /// + public string ExactLabel { get; set; } = "Column width (in pixels)"; + + /// + /// Gets or sets the aria label for the grow button in the discrete resize UI + /// + public string? GrowAriaLabel { get; set; } = "Grow column width"; + + /// + /// Gets or sets the aria label for the shrink button in the discrete resize UI + /// + public string? ShrinkAriaLabel { get; set; } = "Shrink column width"; + + /// + /// Gets or sets the aria label for the reset button in the resize UI + /// + public string? ResetAriaLabel { get; set; } = "Reset column widths"; + + /// + /// Gets or sets the aria label for the submit button in the resize UI + /// + public string? SubmitAriaLabel { get; set; } = "Set column widths"; + + /// + /// Gets or sets the icon to show in the column menu + /// + public Icon? Icon { get; set; } = new CoreIcons.Regular.Size20.TableResizeColumn(); + + /// + /// Gets or sets whether the icon is positioned at the start (true) or + /// at the end (false) of the menu item + /// + public bool IconPositionStart { get; set; } = true; + + /// + /// Gets the default labels for the resize UI. + /// + public static ColumnResizeUISettings Default => new(); +} diff --git a/src/Core/Components/DataGrid/Columns/ColumnSortUISettings.cs b/src/Core/Components/DataGrid/Columns/ColumnSortUISettings.cs new file mode 100644 index 0000000000..172cbd50d4 --- /dev/null +++ b/src/Core/Components/DataGrid/Columns/ColumnSortUISettings.cs @@ -0,0 +1,45 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// Represents the UI settings for column sorting, including labels, aria attributes, and menu options in the . +/// +/// This type provides customizable options for the text and icons displayed in the column sorting menu, +/// including labels for ascending and descending sort orders, and the position of the sort icon. +public record ColumnSortUISettings +{ + /// + /// Gets or sets the text shown in the column menu + /// + public string SortMenu { get; set; } = "Sort"; + + /// + /// Gets or sets the text shown in the column menu when in ascending order + /// + public string SortMenuAscendingLabel { get; set; } = "Sort (ascending)"; + + /// + /// Gets or sets the text shown in the column menu when in descending order + /// + public string SortMenuDescendingLabel { get; set; } = "Sort (descending)"; + + /// + /// Gets or sets the icon to show in the column menu + /// + public Icon? Icon { get; set; } = new CoreIcons.Regular.Size20.ArrowSort(); + + /// + /// Gets or sets whether the icon is positioned at the start (true) or + /// at the end (false) of the menu item + /// + public bool IconPositionStart { get; set; } = true; + + /// + /// Gets the default labels for the sort UI. + /// + public static ColumnSortUISettings Default => new(); + +} + diff --git a/src/Core/Components/DataGrid/Columns/GridSort.cs b/src/Core/Components/DataGrid/Columns/GridSort.cs new file mode 100644 index 0000000000..f89ce8af13 --- /dev/null +++ b/src/Core/Components/DataGrid/Columns/GridSort.cs @@ -0,0 +1,315 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +using System.Linq.Expressions; + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// Represents a sort order specification used within . +/// +/// The type of data represented by each row in the grid. +public sealed class GridSort : IGridSort +{ + private const string ExpressionNotRepresentableMessage = "The supplied expression can't be represented as a property name for sorting. Only simple member expressions, such as @(x => x.SomeProperty), can be converted to property names."; + + private readonly Func, bool, IOrderedQueryable> _first; + private List, bool, IOrderedQueryable>>? _then; + + private (LambdaExpression, bool) _firstExpression; + private List<(LambdaExpression, bool)>? _thenExpressions; + + private IReadOnlyCollection? _cachedPropertyListAscending; + private IReadOnlyCollection? _cachedPropertyListDescending; + + internal GridSort(Func, bool, IOrderedQueryable> first, (LambdaExpression, bool) firstExpression) + { + _first = first; + _firstExpression = firstExpression; + _then = default; + _thenExpressions = default; + } + + /// + /// Produces a instance that sorts according to the specified , ascending. + /// + /// The type of the expression's value. + /// An expression defining how a set of instances are to be sorted. + /// A instance representing the specified sorting rule. +#pragma warning disable MA0018 // Do not declare static members on generic types (deprecated; use CA1000 instead) + public static GridSort ByAscending(Expression> expression) + => new((queryable, asc) => asc ? queryable.OrderBy(expression) : queryable.OrderByDescending(expression), + (expression, true)); + + /// + /// Produces a instance that sorts according to the specified + /// using the specified , ascending. + /// + /// The type of the expression's value. + /// An expression defining how a set of instances are to be sorted. + /// Defines how a items in a set of instances are to be compared. + /// A instance representing the specified sorting rule. + public static GridSort ByAscending(Expression> expression, IComparer comparer) + => new((queryable, asc) => asc ? queryable.OrderBy(expression, comparer) : queryable.OrderByDescending(expression, comparer), + (expression, true)); + + /// + /// Produces a instance that sorts according to the specified , descending. + /// + /// The type of the expression's value. + /// An expression defining how a set of instances are to be sorted. + /// A instance representing the specified sorting rule. + public static GridSort ByDescending(Expression> expression) + => new((queryable, asc) => asc ? queryable.OrderByDescending(expression) : queryable.OrderBy(expression), + (expression, false)); + + /// + /// Produces a instance that sorts according to the specified + /// using the specified , descending. + /// + /// The type of the expression's value. + /// An expression defining how a set of instances are to be sorted. + /// Defines how a items in a set of instances are to be compared. + /// A instance representing the specified sorting rule. + public static GridSort ByDescending(Expression> expression, IComparer comparer) +#pragma warning restore MA0018 // Do not declare static members on generic types (deprecated; use CA1000 instead) + => new((queryable, asc) => asc ? queryable.OrderByDescending(expression, comparer) : queryable.OrderBy(expression, comparer), + (expression, false)); + + /// + /// Updates a instance by appending a further sorting rule. + /// + /// The type of the expression's value. + /// An expression defining how a set of instances are to be sorted. + /// A instance representing the specified sorting rule. + public GridSort ThenAscending(Expression> expression) + { + return AddThenExpression( + (queryable, asc) => asc ? queryable.ThenBy(expression) : queryable.ThenByDescending(expression), + (expression, true) + ); + } + + /// + /// Updates a instance by appending a further sorting rule. + /// + /// The type of the expression's value. + /// An expression defining how a set of instances are to be sorted. + /// Defines how a items in a set of instances are to be compared. + /// A instance representing the specified sorting rule. + public GridSort ThenAscending(Expression> expression, IComparer comparer) + { + return AddThenExpression( + (queryable, asc) => asc ? queryable.ThenBy(expression, comparer) : queryable.ThenByDescending(expression, comparer), + (expression, true) + ); + } + + /// + /// Updates a instance by appending a further sorting rule. + /// + /// The type of the expression's value. + /// An expression defining how a set of instances are to be sorted. + /// A instance representing the specified sorting rule. + public GridSort ThenDescending(Expression> expression) + { + return AddThenExpression( + (queryable, asc) => asc ? queryable.ThenByDescending(expression) : queryable.ThenBy(expression), + (expression, false)); + } + + /// + /// Updates a instance by appending a further sorting rule. + /// + /// The type of the expression's value. + /// An expression defining how a set of instances are to be sorted. + /// Defines how a items in a set of instances are to be compared. + /// A instance representing the specified sorting rule. + public GridSort ThenDescending(Expression> expression, IComparer comparer) + { + return AddThenExpression( + (queryable, asc) => asc ? queryable.ThenByDescending(expression, comparer) : queryable.ThenBy(expression, comparer), + (expression, false) + ); + } + + /// + /// Updates a instance by appending a further sorting rule. + /// + /// The type of the expression's value. + /// An expression defining how a set of instances are to be sorted. + /// A instance representing the specified sorting rule. + public GridSort ThenAlwaysAscending(Expression> expression) + { + return AddThenExpression( + (queryable, _) => queryable.ThenBy(expression), + (expression, true)); + } + + /// + /// Updates a instance by appending a further sorting rule. + /// + /// The type of the expression's value. + /// An expression defining how a set of instances are to be sorted. + /// Defines how a items in a set of instances are to be compared. + /// A instance representing the specified sorting rule. + public GridSort ThenAlwaysAscending(Expression> expression, IComparer comparer) + { + return AddThenExpression( + (queryable, _) => queryable.ThenBy(expression, comparer), + (expression, true) + ); + } + + /// + /// Updates a instance by appending a further sorting rule. + /// + /// The type of the expression's value. + /// An expression defining how a set of instances are to be sorted. + /// A instance representing the specified sorting rule. + public GridSort ThenAlwaysDescending(Expression> expression) + { + return AddThenExpression( + (queryable, _) => queryable.ThenByDescending(expression), + (expression, false)); + } + + /// + /// Updates a instance by appending a further sorting rule. + /// + /// The type of the expression's value. + /// An expression defining how a set of instances are to be sorted. + /// Defines how a items in a set of instances are to be compared. + /// A instance representing the specified sorting rule. + public GridSort ThenAlwaysDescending(Expression> expression, IComparer comparer) + { + return AddThenExpression( + (queryable, _) => queryable.ThenByDescending(expression, comparer), + (expression, false) + ); + } + + private GridSort AddThenExpression(Func, bool, IOrderedQueryable> thenSortExpression, (LambdaExpression, bool) thenExpression) + { + _then ??= []; + _thenExpressions ??= []; + _then.Add(thenSortExpression); + _thenExpressions.Add(thenExpression); + _cachedPropertyListAscending = null; + _cachedPropertyListDescending = null; + + return this; + } + + /// + /// Apply the sort function to the collection + /// + /// The collection to sort + /// Sort ascending (true) or descending (false) + /// The ordered collection + public IOrderedQueryable Apply(IQueryable queryable, bool ascending) + { + var orderedQueryable = _first(queryable, ascending); + + if (_then is not null) + { + foreach (var clause in _then) + { + orderedQueryable = clause(orderedQueryable, ascending); + } + } + + return orderedQueryable; + } + + /// + /// Produces a readonly collection of (property name, direction) pairs representing the sorting rules. + /// + /// + /// The readonly collection of properties that can be sorted on + public IReadOnlyCollection ToPropertyList(bool ascending) + { + if (ascending) + { + _cachedPropertyListAscending ??= BuildPropertyList(ascending: true); + return _cachedPropertyListAscending; + } + + _cachedPropertyListDescending ??= BuildPropertyList(ascending: false); + return _cachedPropertyListDescending; + } + + private List BuildPropertyList(bool ascending) + { + var result = new List + { + new() { PropertyName = ToPropertyName(_firstExpression.Item1), Direction = (_firstExpression.Item2 ^ ascending) ? DataGridSortDirection.Descending : DataGridSortDirection.Ascending }, + }; + + if (_thenExpressions is not null) + { + foreach (var (thenLambda, thenAscending) in _thenExpressions) + { + result.Add(new SortedProperty { PropertyName = ToPropertyName(thenLambda), Direction = (thenAscending ^ ascending) ? DataGridSortDirection.Descending : DataGridSortDirection.Ascending }); + } + } + + return result; + } + + // Not sure we really want this level of complexity, but it converts expressions like @(c => c.Medals.Gold) to "Medals.Gold" +#pragma warning disable MA0015 // Specify the parameter name in ArgumentException + private static string ToPropertyName(LambdaExpression expression) + { + if (expression.Body is not MemberExpression body) + { + throw new ArgumentException(ExpressionNotRepresentableMessage); + } + + // Handles cases like @(x => x.Name) + if (body.Expression is ParameterExpression) + { + return body.Member.Name; + } + + // First work out the length of the string we'll need, so that we can use string.Create + var length = body.Member.Name.Length; + var node = body; + while (node.Expression is not null) + { + if (node.Expression is MemberExpression parentMember) + { + length += parentMember.Member.Name.Length + 1; + node = parentMember; + } + else + { + if (node.Expression is ParameterExpression) + { + break; + } + + throw new ArgumentException(ExpressionNotRepresentableMessage); + } + } + + // Now construct the string + return string.Create(length, body, (chars, body) => + { + var nextPos = chars.Length; + while (body is not null) + { + nextPos -= body.Member.Name.Length; + body.Member.Name.CopyTo(chars[nextPos..]); + if (nextPos > 0) + { + chars[--nextPos] = '.'; + } + + body = (body.Expression as MemberExpression)!; + } + }); + } +} +#pragma warning restore MA0015 // Specify the parameter name in ArgumentException diff --git a/src/Core/Components/DataGrid/Columns/IGridSort.cs b/src/Core/Components/DataGrid/Columns/IGridSort.cs new file mode 100644 index 0000000000..cd64f3bb66 --- /dev/null +++ b/src/Core/Components/DataGrid/Columns/IGridSort.cs @@ -0,0 +1,27 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// Defines a contract for applying sorting rules to a collection of grid items. +/// +/// The type of items in the grid to be sorted. +public interface IGridSort +{ + /// + /// Produces a readonly collection of (property name, direction) pairs representing the sorting rules. + /// + /// + /// The readonly collection of properties that can be sorted on + IReadOnlyCollection ToPropertyList(bool ascending); + + /// + /// Apply the sort function to the collection + /// + /// The collection to sort + /// Sort ascending (true) or descending (false) + /// The ordered collection + IOrderedQueryable Apply(IQueryable queryable, bool ascending); +} diff --git a/src/Core/Components/DataGrid/Columns/PropertyColumn.cs b/src/Core/Components/DataGrid/Columns/PropertyColumn.cs new file mode 100644 index 0000000000..f3a44c9616 --- /dev/null +++ b/src/Core/Components/DataGrid/Columns/PropertyColumn.cs @@ -0,0 +1,130 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; +using Microsoft.FluentUI.AspNetCore.Components.Extensions; +using System.Linq.Expressions; +using System.Reflection; + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// Represents a column whose cells display a single value. +/// +/// The type of data represented by each row in the grid. +/// The type of the value being displayed in the column's cells. +public class PropertyColumn : ColumnBase, IBindableColumn +{ + + private Expression>? _lastAssignedProperty; + private Func? _cellTextFunc; + private Func? _cellTooltipTextFunc; + private IGridSort? _sortBuilder; + private IGridSort? _customSortBy; + + public PropertyInfo? PropertyInfo { get; private set; } + + /// + /// Defines the value to be displayed in this column's cells. + /// + [Parameter, EditorRequired] public Expression> Property { get; set; } = default!; + + /// + /// Optionally specifies a format string for the value. + /// + /// Using this requires the type to implement . + /// + [Parameter] public string? Format { get; set; } + + /// + /// Optionally specifies how to compare values in this column when sorting. + /// + /// Using this requires the type to implement . + /// + [Parameter] public IComparer? Comparer { get; set; } = null; + + [Parameter] + public override IGridSort? SortBy + { + get => _customSortBy ?? _sortBuilder; + set => _customSortBy = value; + } + + /// + protected override void OnParametersSet() + { + // We have to do a bit of pre-processing on the lambda expression. Only do that if it's new or changed. + if (_lastAssignedProperty != Property) + { + _lastAssignedProperty = Property; + var compiledPropertyExpression = Property.Compile(); + + if (!string.IsNullOrEmpty(Format)) + { + // TODO: Consider using reflection to avoid having to box every value just to call IFormattable.ToString + // For example, define a method "string Type(Func property) where U: IFormattable", and + // then construct the closed type here with U=TProp when we know TProp implements IFormattable + + // If the type is nullable, we're interested in formatting the underlying type + var nullableUnderlyingTypeOrNull = Nullable.GetUnderlyingType(typeof(TProp)); + if (!typeof(IFormattable).IsAssignableFrom(nullableUnderlyingTypeOrNull ?? typeof(TProp))) + { + throw new InvalidOperationException($"A '{nameof(Format)}' parameter was supplied, but the type '{typeof(TProp)}' does not implement '{typeof(IFormattable)}'."); + } + + _cellTextFunc = item => ((IFormattable?)compiledPropertyExpression!(item))?.ToString(Format, null); + } + else + { + _cellTextFunc = item => + { + var value = compiledPropertyExpression!(item); + + if (typeof(TProp).IsEnum || typeof(TProp).IsNullableEnum()) + { + return (value as Enum)?.GetDisplayName(); + } + else + { + return value?.ToString(); + } + }; + } + if (Sortable.HasValue) + { + _sortBuilder = Comparer is not null ? GridSort.ByAscending(Property, Comparer) : GridSort.ByAscending(Property); + } + } + + _cellTooltipTextFunc = TooltipText ?? _cellTextFunc; + if (Property.Body is MemberExpression memberExpression) + { + if (Title is null) + { + PropertyInfo = memberExpression.Member as PropertyInfo; + var daText = memberExpression.Member.DeclaringType?.GetDisplayAttributeString(memberExpression.Member.Name); + if (!string.IsNullOrEmpty(daText)) + { + Title = daText; + } + else + { + Title = memberExpression.Member.Name; + } + } + } + } + + /// + protected internal override void CellContent(RenderTreeBuilder builder, TGridItem item) + => builder.AddContent(0, _cellTextFunc?.Invoke(item)); + + protected internal override string? RawCellContent(TGridItem item) + => _cellTooltipTextFunc?.Invoke(item); + + protected override bool IsSortableByDefault() + => _customSortBy is not null; +} diff --git a/src/Core/Components/DataGrid/Columns/SelectColumn.cs b/src/Core/Components/DataGrid/Columns/SelectColumn.cs new file mode 100644 index 0000000000..5ab898a0ce --- /dev/null +++ b/src/Core/Components/DataGrid/Columns/SelectColumn.cs @@ -0,0 +1,560 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.AspNetCore.Components.Web; + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// Represents a column whose cells render a selected checkbox updated when the user click on a row. +/// +/// The type of data represented by each row in the grid. +public class SelectColumn : ColumnBase +{ + /// + /// List of keys to press, to select/unselect a row. + /// + public static string[] KEYBOARD_SELECT_KEYS = ["Enter", "NumpadEnter"]; + + private readonly Icon IconUnselectedMultiple = new CoreIcons.Regular.Size20.CheckboxUnchecked().WithColor(Color.FillInverse); + private readonly Icon IconSelectedMultiple = new CoreIcons.Filled.Size20.CheckboxChecked(); + private readonly Icon IconUnselectedSingle = new CoreIcons.Regular.Size20.RadioButton().WithColor(Color.FillInverse); + private readonly Icon IconSelectedSingle = new CoreIcons.Filled.Size20.RadioButton(); + + private DataGridSelectMode _selectMode = DataGridSelectMode.Single; + private readonly List _selectedItems = []; + + /// + /// Initializes a new instance of . + /// + public SelectColumn() + { + Width = "50px"; + ChildContent = GetDefaultChildContent(); + } + + /// + /// Gets or sets the content to be rendered for each row in the table. + /// + [Parameter] + public RenderFragment ChildContent { get; set; } + + /// + /// Gets or sets whether the [All] checkbox is disabled (not clickable). + /// + [Parameter] + public bool SelectAllDisabled { get; set; } = false; + + /// + /// Gets or sets whether the selection of rows is restricted to the SelectColumn (false) or if the whole row can be clicked to toggled the selection (true). + /// Default is True. + /// + [Parameter] + public bool SelectFromEntireRow { get; set; } = true; + + /// + /// Gets or sets the template for the [All] checkbox column template. + /// + [Parameter] + public RenderFragment? SelectAllTemplate { get; set; } + + /// + /// Gets or sets the list of selected items. + /// + [Parameter] + public IEnumerable SelectedItems + { + get => _selectedItems; + set + { + if (_selectedItems != value) + { + _selectedItems.Clear(); + _selectedItems.AddRange(value); + SelectAll = false; + } + } + } + + /// + /// Gets or sets a callback when list of selected items changed. + /// + [Parameter] + public EventCallback> SelectedItemsChanged { get; set; } + + /// + /// Gets or sets the selection mode (Single, SingleSticky or Multiple). + /// + [Parameter] + public DataGridSelectMode SelectMode + { + get => _selectMode; + set + { + _selectMode = value; + + if (value is DataGridSelectMode.Single or DataGridSelectMode.SingleSticky) + { + KeepOnlyFirstSelectedItemAsync().Wait(); + } + + RefreshHeaderContent(); + } + } + + /// + /// Gets or sets the Icon to be rendered when the row is non selected. + /// + [Parameter] + public Icon? IconUnchecked { get; set; } + + /// + /// Gets or sets the Icon title display as a tooltip and used with Accessibility. + /// The default text is "Row unselected". + /// + [Parameter] + public string TitleUnchecked { get; set; } = "Row unselected"; + + /// + /// Gets or sets the Icon to be rendered when the row is selected. + /// + [Parameter] + public Icon? IconChecked { get; set; } + + /// + /// Gets or sets the Icon title display as a tooltip and used with Accessibility. + /// The default text is "Row selected". + /// + [Parameter] + public string TitleChecked { get; set; } = "Row selected."; + + /// + /// Gets or sets the Icon to be rendered when some but not all rows are selected. + /// Only when is Multiple. + /// + [Parameter] + public Icon? IconIndeterminate { get; set; } = new CoreIcons.Filled.Size20.CheckboxIndeterminate(); + + /// + /// Gets or sets the Icon title display as a tooltip and used with Accessibility. + /// The default text is "All rows are selected.". + /// + [Parameter] + public string TitleAllChecked { get; set; } = "All rows are selected."; + + /// + /// Gets or sets the Icon title display as a tooltip and used with Accessibility. + /// The default text is "No rows are selected.". + /// + [Parameter] + public string TitleAllUnchecked { get; set; } = "No rows are selected."; + + /// + /// Gets or sets the Icon title display as a tooltip and used with Accessibility. + /// The default text is "Some rows are selected.". + /// + [Parameter] + public string TitleAllIndeterminate { get; set; } = "Some rows are selected."; + + /// + /// Gets or sets the action to be executed when the row is selected or unselected. + /// This action is required to update you data model. + /// + [Parameter] + public EventCallback<(TGridItem Item, bool Selected)> OnSelect { get; set; } + + /// + /// Gets or sets the value indicating whether the [All] checkbox is selected. + /// Null is undefined. + /// + [Parameter] + public bool? SelectAll { get; set; } = false; + + /// + /// Gets or sets the action to be executed when the [All] checkbox is clicked. + /// When this action is defined, the [All] checkbox is displayed. + /// This action is required to update you data model. + /// + [Parameter] + public EventCallback SelectAllChanged { get; set; } + + /// + /// Gets or sets the function executed to determine if the item can be selected. + /// + [Parameter] + public Func? Selectable { get; set; } + + /// + /// Gets or sets the function to executed to determine checked/unchecked status. + /// + [Parameter] + public Func Property { get; set; } = (item) => false; + + /// + [Parameter] + public override IGridSort? SortBy { get; set; } + + /// + /// Allows to clear the selection. + /// + public void ClearSelection() + { + _selectedItems.Clear(); + RefreshHeaderContent(); + } + + /// + /// Allows to clear the selection. + /// + public async Task ClearSelectionAsync() + { + 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; + } + + /// + private async Task AddOrRemoveSelectedItemAsync(TGridItem? item) + { + if (item != null && (Selectable == null || Selectable.Invoke(item))) + { + if (SelectMode is DataGridSelectMode.SingleSticky && _selectedItems.Contains(item)) + { + return; + } + + if (SelectedItems.Contains(item)) + { + _selectedItems.Remove(item); + SelectAll = false; + await CallOnSelectAsync(item, false); + } + else + { + if (SelectMode is DataGridSelectMode.Single or DataGridSelectMode.SingleSticky) + { + foreach (var previous in _selectedItems) + { + await CallOnSelectAsync(previous, false); + } + _selectedItems.Clear(); + } + + _selectedItems.Add(item); + await CallOnSelectAsync(item, true); + } + + if (SelectedItemsChanged.HasDelegate) + { + await SelectedItemsChanged.InvokeAsync(SelectedItems); + } + + RefreshHeaderContent(); + } + + Task CallOnSelectAsync(TGridItem item, bool isSelected) + { + return OnSelect.HasDelegate + ? OnSelect.InvokeAsync((item, isSelected)) + : Task.CompletedTask; + } + } + + private Icon GetIcon(bool? selected) + { + if (selected == true) + { + return IconChecked ?? SelectMode switch + { + DataGridSelectMode.Single => IconSelectedSingle, + DataGridSelectMode.SingleSticky => IconSelectedSingle, + _ => IconSelectedMultiple + }; + } + else + { + return IconUnchecked ?? SelectMode switch + { + DataGridSelectMode.Single => IconUnselectedSingle, + DataGridSelectMode.SingleSticky => IconUnselectedSingle, + _ => IconUnselectedMultiple + }; + } + } + + private async Task KeepOnlyFirstSelectedItemAsync() + { + if (_selectedItems.Count <= 1) + { + return; + } + + // Unselect all except the first + foreach (var item in _selectedItems.Skip(1)) + { + await OnSelect.InvokeAsync((item, false)); + } + + // Keep the first selected item + _selectedItems.RemoveRange(1, _selectedItems.Count - 1); + + if (SelectedItemsChanged.HasDelegate) + { + await SelectedItemsChanged.InvokeAsync(_selectedItems); + } + + // Indeterminate + SelectAll = null; + if (SelectAllChanged.HasDelegate) + { + await SelectAllChanged.InvokeAsync(SelectAll); + } + } + + /// + private RenderFragment GetDefaultChildContent() + { + return (item) => new RenderFragment((builder) => + { + if (Selectable != null && Selectable.Invoke(item) == false) + { + return; + } + + var selected = _selectedItems.Contains(item) || Property.Invoke(item); + + // Sync with SelectedItems list + if (selected && !_selectedItems.Contains(item)) + { + _selectedItems.Add(item); + RefreshHeaderContent(); + } + else if (!selected && _selectedItems.Contains(item)) + { + _selectedItems.Remove(item); + } + + builder.OpenComponent>(0); + builder.AddAttribute(1, "Value", GetIcon(selected)); + builder.AddAttribute(2, "Title", selected ? TitleChecked : TitleUnchecked); + builder.AddAttribute(3, "row-selected", selected); + + if (!SelectFromEntireRow) + { + builder.AddAttribute(4, "style", "cursor: pointer;"); + } + builder.CloseComponent(); + }); + } + + /// + private RenderFragment GetHeaderContent() + { + switch (SelectMode) + { + case DataGridSelectMode.Single: + return new RenderFragment((builder) => { }); + + case DataGridSelectMode.SingleSticky: + return new RenderFragment((builder) => { }); + + case DataGridSelectMode.Multiple: + var selectedAll = GetSelectAll(); + var iconAllChecked = (selectedAll == null && IconIndeterminate != null) + ? IconIndeterminate + : GetIcon(selectedAll); + + return new RenderFragment((builder) => + { + builder.OpenComponent>(0); + builder.AddAttribute(1, "Value", iconAllChecked); + builder.AddAttribute(2, "all-selected", SelectAll); + if (!SelectAllDisabled) + { + builder.AddAttribute(3, "OnClick", EventCallback.Factory.Create(this, OnClickAllAsync)); + builder.AddAttribute(4, "onkeydown", EventCallback.Factory.Create(this, OnKeyAllAsync)); + } + builder.AddAttribute(5, "Title", iconAllChecked == IconIndeterminate + ? TitleAllIndeterminate + : (iconAllChecked == GetIcon(true) ? TitleAllChecked : TitleAllUnchecked)); + builder.CloseComponent(); + }); + + default: + return new RenderFragment((builder) => { }); + } + } + + /// + private void RefreshHeaderContent() + { + if (SelectAllTemplate == null) + { + HeaderContent = GetHeaderContent(); + } + else + { + HeaderContent = new RenderFragment((builder) => + { + builder.OpenElement(0, "div"); + if (!SelectAllDisabled) + { + 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(4, SelectAllTemplate.Invoke(new SelectAllTemplateArgs(GetSelectAll()))); + builder.CloseElement(); + }); + } + } + + /// + private bool? GetSelectAll() + { + // Using SelectedItems only + if (InternalGridContext != null && (Grid.Items != null || Grid.ItemsProvider != null)) + { + if (!SelectedItems.Any()) + { + return false; + } + else if (SelectedItems.Count() == InternalGridContext.TotalItemCount || SelectAll == true) + { + return true; + } + else + { + return null; + } + } + else + { + return null; + } + } + + /// + protected internal override void CellContent(RenderTreeBuilder builder, TGridItem item) + => builder.AddContent(0, ChildContent(item)); + + /// + protected internal override string? RawCellContent(TGridItem item) + { + return TooltipText?.Invoke(item); + } + + /// + protected override bool IsSortableByDefault() => SortBy is not null; + + /// + internal async Task OnClickAllAsync(MouseEventArgs e) + { + if (Grid == null || SelectMode != DataGridSelectMode.Multiple || SelectAllDisabled) + { + return; + } + + // SelectAllChanged + SelectAll = !(SelectAll == true); + if (SelectAllChanged.HasDelegate) + { + await SelectAllChanged.InvokeAsync(SelectAll); + } + + var count = SelectedItems.Count(); + // SelectedItems + _selectedItems.Clear(); + if (SelectAll == true && count != InternalGridContext.TotalItemCount) + { + // Only add selectable items + _selectedItems.AddRange((InternalGridContext.Grid.Items?.ToList() ?? InternalGridContext.Items) + .Where(item => Selectable?.Invoke(item) ?? true) + ); + } + + if (SelectedItemsChanged.HasDelegate) + { + await SelectedItemsChanged.InvokeAsync(SelectedItems); + } + + 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/Columns/SortedProperty.cs b/src/Core/Components/DataGrid/Columns/SortedProperty.cs new file mode 100644 index 0000000000..26f22078e0 --- /dev/null +++ b/src/Core/Components/DataGrid/Columns/SortedProperty.cs @@ -0,0 +1,21 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// Holds the name of a property and the direction to sort by. +/// +public readonly struct SortedProperty +{ + /// + /// Gets or sets the property name for the sorting rule. + /// + public string PropertyName { get; init; } + + /// + /// Gets or sets the direction to sort by. + /// + public DataGridSortDirection Direction { get; init; } +} diff --git a/src/Core/Components/DataGrid/Columns/TemplateColumn.cs b/src/Core/Components/DataGrid/Columns/TemplateColumn.cs new file mode 100644 index 0000000000..8a3b43e954 --- /dev/null +++ b/src/Core/Components/DataGrid/Columns/TemplateColumn.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Rendering; + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// Represents a column whose cells render a supplied template. +/// +/// The type of data represented by each row in the grid. +public class TemplateColumn : ColumnBase +{ + private static readonly RenderFragment EmptyChildContent = _ => builder => { }; + + /// + /// Gets or sets the content to be rendered for each row in the table. + /// + [Parameter] public RenderFragment ChildContent { get; set; } = EmptyChildContent; + + /// + [Parameter] public override IGridSort? SortBy { get; set; } + + /// + protected internal override void CellContent(RenderTreeBuilder builder, TGridItem item) + => builder.AddContent(0, ChildContent(item)); + + protected internal override string? RawCellContent(TGridItem item) + { + return TooltipText?.Invoke(item); + } + + /// + protected override bool IsSortableByDefault() + => SortBy is not null; +} diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor b/src/Core/Components/DataGrid/FluentDataGrid.razor new file mode 100644 index 0000000000..071d862b77 --- /dev/null +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor @@ -0,0 +1,263 @@ +@using Microsoft.AspNetCore.Components.Rendering +@using Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure +@using System.Globalization +@namespace Microsoft.FluentUI.AspNetCore.Components +@inherits FluentComponentBase +@typeparam TGridItem + + @{ + StartCollectingColumns(); + } + @if (!_manualGrid) + { + @ChildContent + } + + @{ + FinishCollectingColumns(); + } + + + + @if (GenerateHeader != GenerateHeaderOption.None) + { + DataGridRowType headerType = DataGridRowType.Header; + if (GenerateHeader == GenerateHeaderOption.Sticky) + { + headerType = DataGridRowType.StickyHeader; + } + + + @_renderColumnHeaders + + + } + + @if (EffectiveLoadingValue) + { + @_renderLoadingContent + } + else + { + @if (Virtualize) + { + if (_internalGridContext.TotalItemCount == 0) + { + @_renderEmptyContent + } + else + { + + } + } + else + { + @_renderNonVirtualizedRows + } + } + @if (_manualGrid) + { + @ChildContent + } + +
+
+
+ +@code { + private void RenderNonVirtualizedRows(RenderTreeBuilder __builder) + { + var initialRowIndex = (GenerateHeader != GenerateHeaderOption.None) ? 2 : 1; // aria-rowindex is 1-based, plus 1 if there is a header + var rowIndex = initialRowIndex; + if (_internalGridContext.Items.Any()) + { + foreach (var item in _internalGridContext.Items) + { + RenderRow(__builder, rowIndex++, item); + } + } + else + { + RenderEmptyContent(__builder); + } + } + + private void RenderRow(RenderTreeBuilder __builder, int rowIndex, TGridItem item) + { + var rowClass = RowClass?.Invoke(item) ?? null; + var rowStyle = RowStyle?.Invoke(item) ?? null; + + + @for (var colIndex = 0; colIndex < _columns.Count; colIndex++) + { + var col = _columns[colIndex]; + + string? tooltip = col.Tooltip ? @col.RawCellContent(item) : null; + + + @((RenderFragment)(__builder => col.CellContent(__builder, item))) + + } + + } + + private void RenderPlaceholderRow(RenderTreeBuilder __builder, PlaceholderContext placeholderContext) + { + string? _rowsDataSize = $"height: {ItemSize}px"; + + + @for (var i = 0; i < _columns.Count; i++) + { + var col = _columns[i]; + + + @((RenderFragment)(__builder => col.RenderPlaceholderContent(__builder, placeholderContext))) + + } + + } + + private void RenderColumnHeaders(RenderTreeBuilder __builder) + { + @for (var i = 0; i < _columns.Count; i++) + { + var col = _columns[i]; + + if (_sortByColumn == col) + col.IsActiveSortColumn = true; + else + col.IsActiveSortColumn = false; + + + @col.HeaderContent + @if (HeaderCellAsButtonWithMenu) + { + @if (col == _displayOptionsForColumn) + { +
+ @col.ColumnOptions +
+ } + @if (ResizableColumns && col == _displayResizeForColumn) + { +
+ + @if (ResizeType is not null) + { + + } + +
+ } + } + else + { + @if (col == _displayOptionsForColumn) + { +
+ + @if (ResizeType is not null) + { + + @if (@col.ColumnOptions is not null) + { + + } + } + @col.ColumnOptions + +
+ } + } + + + @if (ResizableColumns) + { +
+ } +
+ } + } + + private void RenderEmptyContent(RenderTreeBuilder __builder) + { + if (_manualGrid) + { + return; + } + + string? style = null; + string? colspan = null; + if (DisplayMode == DataGridDisplayMode.Grid) + { + style = $"grid-column: 1 / {_columns.Count + 1}"; + } + else + { + colspan = _columns.Count.ToString(); + } + + + + @if (EmptyContent is null) + { + @("No data to show!") + } + else + { + @EmptyContent + } + + + + } + + private void RenderLoadingContent(RenderTreeBuilder __builder) + { + string? style = null; + string? colspan = null; + if (DisplayMode == DataGridDisplayMode.Grid) + { + style = $"grid-column: 1 / {_columns.Count + 1}"; + } + else + { + colspan = _columns.Count.ToString(CultureInfo.InvariantCulture); + } + + + + @if (LoadingContent is null) + { + +
Loading...
+
+ } + else + { + @LoadingContent + } +
+
+ } +} diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs new file mode 100644 index 0000000000..ce8f4f5b57 --- /dev/null +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -0,0 +1,1137 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web.Virtualization; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; +using Microsoft.FluentUI.AspNetCore.Components.Extensions; +using Microsoft.FluentUI.AspNetCore.Components.Infrastructure; +using Microsoft.FluentUI.AspNetCore.Components.Utilities; +using Microsoft.JSInterop; + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// A component that displays a grid. +/// +/// The type of data represented by each row in the grid. +[CascadingTypeParameter(nameof(TGridItem))] +public partial class FluentDataGrid : FluentComponentBase, IHandleEvent, IAsyncDisposable +{ + private const string JAVASCRIPT_FILE = "./_content/Microsoft.FluentUI.AspNetCore.Components/Components/DataGrid/FluentDataGrid.razor.js"; + public const string EMPTY_CONTENT_ROW_CLASS = "empty-content-row"; + public const string LOADING_CONTENT_ROW_CLASS = "loading-content-row"; + public List _menuReferences = []; + + /// + [Inject] + private LibraryConfiguration LibraryConfiguration { get; set; } = default!; + + [Inject] + private NavigationManager NavigationManager { get; set; } = default!; + + [Inject] + private IServiceScopeFactory ScopeFactory { get; set; } = default!; + + [Inject] + private IJSRuntime JSRuntime { get; set; } = default!; + + [Inject] + private IKeyCodeService KeyCodeService { get; set; } = default!; + + /// + /// Gets or sets a queryable source of data for the grid. + /// + /// This could be in-memory data converted to queryable using the + /// extension method, + /// or an EntityFramework DataSet or an derived from it. + /// + /// You should supply either or , but not both. + /// + [Parameter] + public IQueryable? Items { get; set; } + + /// + /// Gets or sets a callback which will be called if there is a change in pagination, ordering or if a RefreshDataAsync is forced. + /// + /// You must supply if you use this callback. + /// + [Parameter] + public Func, Task>? RefreshItems { get; set; } + + /// + /// Gets or sets a callback that supplies data for the rid. + /// + /// You should supply either or , but not both. + /// + [Parameter] + public GridItemsProvider? ItemsProvider { get; set; } + + /// + /// Gets or sets the child components of this instance. For example, you may define columns by adding + /// components derived from the base class. + /// + [Parameter] + public RenderFragment? ChildContent { get; set; } + + /// + /// If true, the grid will be rendered with virtualization. This is normally used in conjunction with + /// scrolling and causes the grid to fetch and render only the data around the current scroll viewport. + /// This can greatly improve the performance when scrolling through large data sets. + /// + /// If you use , you should supply a value for and must + /// ensure that every row renders with the same constant height. + /// + /// Generally it's preferable not to use if the amount of data being rendered + /// is small or if you are using pagination. + /// + [Parameter] + public bool Virtualize { get; set; } + + /// + /// This is applicable only when using . It defines how many additional items will be rendered + /// before and after the visible region to reduce rendering frequency during scrolling. While higher values can improve + /// scroll smoothness by rendering more items off-screen, they can also increase initial load times. Finding a balance + /// based on your data set size and user experience requirements is recommended. The default value is 3. + /// + [Parameter] + public int OverscanCount { get; set; } = 3; + + /// + /// This is applicable only when using . It defines an expected height in pixels for + /// each row, allowing the virtualization mechanism to fetch the correct number of items to match the display + /// size and to ensure accurate scrolling. + /// + [Parameter] + public float ItemSize { get; set; } = 32; + + /// + /// If true, renders draggable handles around the column headers and adds a button to invoke a resize UI. + /// This allows the user to resize columns manually. Size changes are not persisted. + /// + [Parameter] + public bool ResizableColumns { get; set; } + + /// + /// To comply with WCAG 2.2, a one-click option should be offered to change column widths. We provide such an option through the + /// ColumnOptions UI. This parameter allows you to enable or disable this resize UI.Enable it by setting the type of resize to perform + /// Discrete: resize by a 10 pixels at a time + /// Exact: resize to the exact width specified (in pixels) + /// Note: This does not affect resizing by mouse dragging, just the keyboard driven resize. + /// + [Parameter] + public DataGridResizeType? ResizeType { get; set; } + + /// + /// (Aria) Labels used in the column resize UI. + /// + [Parameter] + public ColumnResizeUISettings ColumnResizeLabels { get; set; } = ColumnResizeUISettings.Default; + + /// + /// Labels used in the column sort UI. + /// + [Parameter] + public ColumnSortUISettings ColumnSortLabels { get; set; } = ColumnSortUISettings.Default; + + /// + /// Labels used in the column options UI. + /// + [Parameter] + public ColumnOptionsUISettings ColumnOptionsLabels { get; set; } = ColumnOptionsUISettings.Default; + + /// + /// If true, enables the new style of header cell that includes a button to display all column options through a menu. + /// + [Parameter] + public bool HeaderCellAsButtonWithMenu { get; set; } + + /// + /// Use IMenuService to create the menu, if this service was injected. + /// This value must be defined before the component is rendered (you can't change it during the component lifecycle). + /// Default, true. + /// + [Parameter] + public bool UseMenuService { get; set; } = true; + + /// + /// Optionally defines a value for @key on each rendered row. Typically this should be used to specify a + /// unique identifier, such as a primary key value, for each data item. + /// + /// This allows the grid to preserve the association between row elements and data items based on their + /// unique identifiers, even when the instances are replaced by new copies (for + /// example, after a new query against the underlying data store). + /// + /// If not set, the @key will be the instance itself. + /// + [Parameter] + public Func ItemKey { get; set; } = x => x!; + + /// + /// Optionally links this instance with a model, + /// causing the grid to fetch and render only the current page of data. + /// + /// This is normally used in conjunction with a component or some other UI logic + /// that displays and updates the supplied instance. + /// + [Parameter] + public PaginationState? Pagination { get; set; } + + /// + /// Gets or sets a value indicating whether the component will not add itself to the tab queue. + /// Default is false. + /// + [Parameter] + public bool NoTabbing { get; set; } + + /// + /// Gets or sets a value indicating whether the grid should automatically generate a header row and its type. + /// See + /// + [Parameter] + public GenerateHeaderOption? GenerateHeader { get; set; } = GenerateHeaderOption.Default; + + /// + /// Gets or sets the value that gets applied to the css gridTemplateColumns attribute of child rows. + /// Can be specified here or on the column level with the Width parameter but not both. + /// Needs to be a valid CSS string of space-separated values, such as "auto 1fr 2fr 100px". + /// + [Parameter] + public string? GridTemplateColumns { get; set; } = null; + + /// + /// Gets or sets a callback when a row is focused. + /// As of 4.11 a row is a tr element with a 'display: contents'. Browsers can not focus such elements currently, but work is underway to fix that. + /// + [Parameter] + public EventCallback> OnRowFocus { get; set; } + + /// + /// Gets or sets a callback when a row is focused. + /// + [Parameter] + public EventCallback> OnCellFocus { get; set; } + + /// + /// Gets or sets a callback when a cell is clicked. + /// + [Parameter] + public EventCallback> OnCellClick { get; set; } + + /// + /// Gets or sets a callback when a row is clicked. + /// + [Parameter] + public EventCallback> OnRowClick { get; set; } + + /// + /// Gets or sets a callback when a row is double-clicked. + /// + [Parameter] + public EventCallback> OnRowDoubleClick { get; set; } + + /// + /// Optionally defines a class to be applied to a rendered row. + /// + [Parameter] + public Func? RowClass { get; set; } + + /// + /// Optionally defines a style to be applied to a rendered row. + /// Do not use to dynamically update a row style after rendering as this will interfere with the script that use this attribute. Use instead. + /// + [Parameter] + public Func? RowStyle { get; set; } + + /// + /// Gets or sets a value indicating whether the grid should show a hover effect on rows. + /// + [Parameter] + public bool ShowHover { get; set; } + + /// + /// If specified, grids render this fragment when there is no content. + /// + [Parameter] + public RenderFragment? EmptyContent { get; set; } + + /// + /// Gets or sets a value to indicate the grid loading data state. + /// If not set and a is present, the grid will show until the provider's first return. + /// + [Parameter] + public bool? Loading { get; set; } + + /// + /// Gets or sets the content to render when is true. + /// A default fragment is used if loading content is not specified. + /// + [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; } + + /// + /// Automatically fit the number of items per page to the available height. + /// + [Parameter] + public bool AutoItemsPerPage { get; set; } + + /// + /// Gets or set the of the grid. + /// Default is 'Grid'. + /// When set to Grid, can be used to specify column widths. + /// When set to Table, widths need to be specified at the column level. + /// When using , it is recommended to use Table. + /// + [Parameter] + public DataGridDisplayMode DisplayMode { get; set; } = DataGridDisplayMode.Grid; + + /// + /// Gets or sets the size of each row in the grid based on the enum. + /// + [Parameter] + public DataGridRowSize RowSize { get; set; } = DataGridRowSize.Small; + + /// + /// Gets or sets a value indicating whether the grid should allow multiple lines of text in cells. + /// Cannot be used together with Virtualize. + /// + [Parameter] + public bool MultiLine { get; set; } = false; + + /// + /// Gets or sets a value indicating whether the grid should save its paging state in the URL. + /// This is an experimental feature, which might cause unwanted jumping in the page when you change something in the grid. + /// + [Parameter] + public bool SaveStateInUrl { get; set; } + + /// + /// Gets or sets a prefix to use when saving the grid state in the URL. + /// + /// Only relevant when is set to on multiple grids on a single page. + [Parameter] + public string? SaveStatePrefix { get; set; } + + /// + /// Gets or sets a value indicating whether the grids' first cell should be focused. + /// + [Parameter] + public bool AutoFocus { get; set; } = false; + + // Returns Loading if set (controlled). If not controlled, + // we assume the grid is loading until the next data load completes + internal bool EffectiveLoadingValue => Loading ?? ItemsProvider is not null; + + private ElementReference? _gridReference; + //private DotNetObjectReference? _dotNetObjectReference; + private Virtualize<(int, TGridItem)>? _virtualizeComponent; + + // IQueryable only exposes synchronous query APIs. IAsyncQueryExecutor is an adapter that lets us invoke any + // async query APIs that might be available. We have built-in support for using EF Core's async query APIs. + private IAsyncQueryExecutor? _asyncQueryExecutor; + private AsyncServiceScope? _scope; + + // We cascade the InternalGridContext to descendants, which in turn call it to add themselves to _columns + // This happens on every render so that the column list can be updated dynamically + private readonly InternalGridContext _internalGridContext; + internal readonly List> _columns; + private bool _collectingColumns;// Columns might re-render themselves arbitrarily. We only want to capture them at a defined time. + + // Tracking state for options and sorting + private ColumnBase? _displayOptionsForColumn; + private ColumnBase? _displayResizeForColumn; + private ColumnBase? _sortByColumn; + private bool _sortByAscending; + private bool _checkColumnOptionsPosition; + private bool _checkColumnResizePosition; + private bool _manualGrid; + + // The associated ES6 module, which uses document-level event listeners + private IJSObjectReference? Module; + private IJSObjectReference? _jsEventDisposable; + + // Caches of method->delegate conversions + private readonly RenderFragment _renderColumnHeaders; + private readonly RenderFragment _renderNonVirtualizedRows; + + private readonly RenderFragment _renderEmptyContent; + private readonly RenderFragment _renderLoadingContent; + + private string? _internalGridTemplateColumns; + + // We try to minimize the number of times we query the items provider, since queries may be expensive + // We only re-query when the developer calls RefreshDataAsync, or if we know something's changed, such + // as sort order, the pagination state, or the data source itself. These fields help us detect when + // things have changed, and to discard earlier load attempts that were superseded. + private PaginationState? _lastRefreshedPaginationState; + private IQueryable? _lastAssignedItems; + private int _lastAssignedItemsHashCode; + private GridItemsProvider? _lastAssignedItemsProvider; + private CancellationTokenSource? _pendingDataLoadCancellationTokenSource; + + private GridItemsProviderRequest? _lastRequest; + private bool _forceRefreshData; + + // If the PaginationState mutates, it raises this event. We use it to trigger a re-render. + private readonly EventCallbackSubscriber _currentPageItemsChanged; + public bool? SortByAscending => _sortByAscending; + + /// + /// Constructs an instance of . + /// + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(DataGridCellFocusEventArgs))] + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(DataGridRowFocusEventArgs))] + public FluentDataGrid() + { + Id = Identifier.NewId(); + _columns = []; + _internalGridContext = new(this); + _currentPageItemsChanged = new(EventCallback.Factory.Create(this, RefreshDataCoreAsync)); + _renderColumnHeaders = RenderColumnHeaders; + _renderNonVirtualizedRows = RenderNonVirtualizedRows; + _renderEmptyContent = RenderEmptyContent; + _renderLoadingContent = RenderLoadingContent; + + // As a special case, we don't issue the first data load request until we've collected the initial set of columns + // This is so we can apply default sort order (or any future per-column options) before loading data + // We use EventCallbackSubscriber to safely hook this async operation into the synchronous rendering flow + EventCallbackSubscriber? columnsFirstCollectedSubscriber = new( + EventCallback.Factory.Create(this, RefreshDataCoreAsync)); + columnsFirstCollectedSubscriber.SubscribeOrMove(_internalGridContext.ColumnsFirstCollected); + } + + /// + protected override void OnInitialized() + { + KeyCodeService.RegisterListener(OnKeyDownAsync); + if (SaveStateInUrl) + { + LoadStateFromQueryString(new Uri(NavigationManager.Uri).Query); + } + } + + /// + protected override Task OnParametersSetAsync() + { + // The associated pagination state may have been added/removed/replaced + _currentPageItemsChanged.SubscribeOrMove(Pagination?.CurrentPageItemsChanged); + + if (Items is not null && ItemsProvider is not null) + { + throw new InvalidOperationException($"FluentDataGrid requires one of {nameof(Items)} or {nameof(ItemsProvider)}, but both were specified."); + } + + if (Virtualize && MultiLine) + { + throw new InvalidOperationException($"FluentDataGrid cannot use both {nameof(Virtualize)} and {nameof(MultiLine)} at the same time."); + } + + var currentItemsHash = FluentDataGrid.ComputeItemsHash(Items); + var itemsChanged = currentItemsHash != _lastAssignedItemsHashCode; + + // Perform a re-query only if the data source or something else has changed + var dataSourceHasChanged = itemsChanged || !Equals(ItemsProvider, _lastAssignedItemsProvider); + if (dataSourceHasChanged) + { + _scope?.Dispose(); + _scope = ScopeFactory.CreateAsyncScope(); + _lastAssignedItemsProvider = ItemsProvider; + _lastAssignedItems = Items; + _lastAssignedItemsHashCode = currentItemsHash; + _asyncQueryExecutor = AsyncQueryExecutorSupplier.GetAsyncQueryExecutor(_scope.Value.ServiceProvider, Items); + } + + var paginationStateHasChanged = + Pagination?.ItemsPerPage != _lastRefreshedPaginationState?.ItemsPerPage + || Pagination?.CurrentPageIndex != _lastRefreshedPaginationState?.CurrentPageIndex; + + var mustRefreshData = dataSourceHasChanged || paginationStateHasChanged || EffectiveLoadingValue; + + // We don't want to trigger the first data load until we've collected the initial set of columns, + // because they might perform some action like setting the default sort order, so it would be wasteful + // to have to re-query immediately + return (_columns.Count > 0 && mustRefreshData) ? RefreshDataCoreAsync() : Task.CompletedTask; + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender && _gridReference is not null) + { + Element = _gridReference.Value; + Module ??= await JSRuntime.InvokeAsync("import", JAVASCRIPT_FILE.FormatCollocatedUrl(LibraryConfiguration)); + try + { + _jsEventDisposable = await Module.InvokeAsync("init", _gridReference, AutoFocus); + if (AutoItemsPerPage) + { + await Module.InvokeVoidAsync("dynamicItemsPerPage", _gridReference, DotNetObjectReference.Create(this), (int)RowSize); + } + } + catch (JSException ex) + { + Console.WriteLine("[FluentDataGrid] " + ex.Message); + } + } + + SaveStateToQueryString(); + + if (_checkColumnOptionsPosition && _displayOptionsForColumn is not null) + { + _checkColumnOptionsPosition = false; + Module?.InvokeVoidAsync("checkColumnPopupPosition", _gridReference, ".col-options").AsTask(); + } + + if (_checkColumnResizePosition && _displayResizeForColumn is not null) + { + _checkColumnResizePosition = false; + _ = Module?.InvokeVoidAsync("checkColumnPopupPosition", _gridReference, ".col-resize").AsTask(); + } + + if (AutoFit && _gridReference is not null) + { + _ = Module?.InvokeVoidAsync("autoFitGridColumns", _gridReference, _columns.Count).AsTask(); + } + } + + // Invoked by descendant columns at a special time during rendering + internal void AddColumn(ColumnBase column, DataGridSortDirection? initialSortDirection, bool isDefaultSortColumn) + { + if (_collectingColumns) + { + column.Index = _columns.Count + 1; + _columns.Add(column); + + if (isDefaultSortColumn && _sortByColumn is null && initialSortDirection.HasValue) + { + _sortByColumn = column; + _sortByAscending = initialSortDirection.Value != DataGridSortDirection.Descending; + _internalGridContext.DefaultSortColumn = (column, initialSortDirection.Value); + } + } + } + + private void StartCollectingColumns() + { + _columns.Clear(); + _collectingColumns = true; + } + + private void FinishCollectingColumns() + { + _collectingColumns = false; + _manualGrid = _columns.Count == 0; + + if (!string.IsNullOrWhiteSpace(GridTemplateColumns) && _columns.Where(x => x is not SelectColumn).Any(x => !string.IsNullOrWhiteSpace(x.Width))) + { + throw new Exception("You can use either the 'GridTemplateColumns' parameter on the grid or the 'Width' property at the column level, not both."); + } + + // Always re-evaluate after collecting columns when using displaymode grid. A column might be added or hidden and the _internalGridTemplateColumns needs to reflect that. + if (DisplayMode == DataGridDisplayMode.Grid) + { + if (!AutoFit) + { + _internalGridTemplateColumns = GridTemplateColumns ?? string.Join(" ", Enumerable.Repeat("1fr", _columns.Count)); + } + + if (_columns.Any(x => !string.IsNullOrWhiteSpace(x.Width))) + { + _internalGridTemplateColumns = GridTemplateColumns ?? string.Join(" ", _columns.Select(x => x.Width ?? "auto")); + } + } + + if (ResizableColumns) + { + _ = Module?.InvokeVoidAsync("enableColumnResizing", _gridReference).AsTask(); + } + } + + /// + /// Sets the grid's current sort column to the specified . + /// + /// The column that defines the new sort order. + /// The direction of sorting. If the value is , then it will toggle the direction on each call. + /// A representing the completion of the operation. + public Task SortByColumnAsync(ColumnBase column, DataGridSortDirection direction = DataGridSortDirection.Auto) + { + _sortByAscending = direction switch + { + DataGridSortDirection.Ascending => true, + DataGridSortDirection.Descending => false, + DataGridSortDirection.Auto => _sortByColumn != column || !_sortByAscending, + _ => throw new NotSupportedException($"Unknown sort direction {direction}"), + }; + + _sortByColumn = column; + + StateHasChanged(); // We want to see the updated sort order in the header, even before the data query is completed + return RefreshDataAsync(); + } + + /// + /// Sorts the grid by the specified column found first. If the title is not found, nothing happens. + /// + /// The title of the column to sort by. + /// The direction of sorting. The default is . If the value is , then it will toggle the direction on each call. + /// A representing the completion of the operation. + public Task SortByColumnAsync(string title, DataGridSortDirection direction = DataGridSortDirection.Auto) + { + var column = _columns.FirstOrDefault(c => c.Title?.Equals(title, StringComparison.InvariantCultureIgnoreCase) ?? false); + + return column is not null ? SortByColumnAsync(column, direction) : Task.CompletedTask; + } + + /// + /// Sorts the grid by the specified column . If the index is out of range, nothing happens. + /// + /// The index of the column to sort by. + /// The direction of sorting. The default is . If the value is , then it will toggle the direction on each call. + /// A representing the completion of the operation. + public Task SortByColumnAsync(int index, DataGridSortDirection direction = DataGridSortDirection.Auto) + { + return index >= 0 && index < _columns.Count ? SortByColumnAsync(_columns[index], direction) : Task.CompletedTask; + } + + /// + /// Removes the grid's sort on double click if this is specified currently sorted on. + /// + /// The column to check against the current sorted on column. + /// A representing the completion of the operation. + public Task RemoveSortByColumnAsync(ColumnBase column) + { + if (_sortByColumn == column && !column.IsDefaultSortColumn) + { + _sortByColumn = _internalGridContext.DefaultSortColumn.Column ?? null; + _sortByAscending = _internalGridContext.DefaultSortColumn.Direction != DataGridSortDirection.Descending; + + StateHasChanged(); // We want to see the updated sort order in the header, even before the data query is completed + return RefreshDataCoreAsync(); + } + return Task.CompletedTask; + } + + /// + /// Removes the grid's sort on double click for the currently sorted column if it's not a default sort column. + /// + /// A representing the completion of the operation. + public Task RemoveSortByColumnAsync() => (_sortByColumn != null) ? RemoveSortByColumnAsync(_sortByColumn) : Task.CompletedTask; + + /// + /// Displays the UI for the specified column, closing any other column + /// options UI that was previously displayed. + /// + /// The column whose options are to be displayed, if any are available. + /// A representing the completion of the operation. + public Task ShowColumnOptionsAsync(ColumnBase column) + { + _displayOptionsForColumn = column; + _checkColumnOptionsPosition = true; // Triggers a call to JSRuntime to position the options element, apply autofocus, and any other setup + StateHasChanged(); + return Task.CompletedTask; + } + + /// + /// Displays the UI for the specified column found first, + /// closing any other column options UI that was previously displayed. If the title is not found, nothing happens. + /// + /// The column title whose options UI is to be displayed. + /// A representing the completion of the operation. + public Task ShowColumnOptionsAsync(string title) + { + var column = _columns.FirstOrDefault(c => c.Title?.Equals(title, StringComparison.InvariantCultureIgnoreCase) ?? false); + return (column is not null) ? ShowColumnOptionsAsync(column) : Task.CompletedTask; + } + + /// + /// Displays the UI for the specified column , + /// closing any other column options UI that was previously displayed. If the index is out of range, nothing happens. + /// + /// The column index whose options UI is to be displayed. + /// A representing the completion of the operation. + public Task ShowColumnOptionsAsync(int index) + { + return (index >= 0 && index < _columns.Count) ? ShowColumnOptionsAsync(_columns[index]) : Task.CompletedTask; + } + + /// + /// Closes the UI that was previously displayed. + /// + public Task CloseColumnOptionsAsync() + { + _displayOptionsForColumn = null; + StateHasChanged(); + return Task.CompletedTask; + } + + /// + /// Displays the column resize UI for the specified column, closing any other column + /// resize UI that was previously displayed. + /// + /// The column whose resize UI is to be displayed. + /// A representing the completion of the operation. + public Task ShowColumnResizeAsync(ColumnBase column) + { + _displayResizeForColumn = column; + _checkColumnResizePosition = true; // Triggers a call to JSRuntime to position the options element, apply autofocus, and any other setup + StateHasChanged(); + return Task.CompletedTask; + } + + /// + /// Displays the column resize UI for the specified column, closing any other column + /// resize UI that was previously displayed. + /// + /// The column title whose resize UI is to be displayed. + /// A representing the completion of the operation. + public Task ShowColumnResizeAsync(string title) + { + var column = _columns.FirstOrDefault(c => c.Title?.Equals(title, StringComparison.InvariantCultureIgnoreCase) ?? false); + return (column is not null) ? ShowColumnResizeAsync(column) : Task.CompletedTask; + } + + /// + /// Displays the column resize UI for the specified column, closing any other column + /// resize UI that was previously displayed. + /// + /// The column index whose resize UI is to be displayed. + /// A representing the completion of the operation. + public Task ShowColumnResizeAsync(int index) + { + return (index >= 0 && index < _columns.Count) ? ShowColumnResizeAsync(_columns[index]) : Task.CompletedTask; + } + + public void SetLoadingState(bool? loading) + { + Loading = loading; + } + + /// + /// Instructs the grid to re-fetch and render the current data from the supplied data source + /// (either or ). + /// + /// A that represents the completion of the operation. + public async Task RefreshDataAsync(bool force = false) + { + _forceRefreshData = force; + await RefreshDataCoreAsync(); + } + + // Same as RefreshDataAsync, except without forcing a re-render. We use this from OnParametersSetAsync + // because in that case there's going to be a re-render anyway. + private async Task RefreshDataCoreAsync() + { + // Move into a "loading" state, cancelling any earlier-but-still-pending load + _pendingDataLoadCancellationTokenSource?.Cancel(); + var thisLoadCts = _pendingDataLoadCancellationTokenSource = new CancellationTokenSource(); + + if (_virtualizeComponent is not null) + { + // If we're using Virtualize, we have to go through its RefreshDataAsync API otherwise: + // (1) It won't know to update its own internal state if the provider output has changed + // (2) We won't know what slice of data to query for + await _virtualizeComponent.RefreshDataAsync(); + _pendingDataLoadCancellationTokenSource = null; + + StateHasChanged(); + return; + } + + // If we're not using Virtualize, we build and execute a request against the items provider directly + var startIndex = Pagination is null ? 0 : (Pagination.CurrentPageIndex * Pagination.ItemsPerPage); + GridItemsProviderRequest request = new( + startIndex, Pagination?.ItemsPerPage, _sortByColumn, _sortByAscending, thisLoadCts.Token); + _lastRefreshedPaginationState = Pagination; + + if (RefreshItems is not null) + { + if (_forceRefreshData || _lastRequest == null || !_lastRequest.Value.IsSameRequest(request)) + { + _forceRefreshData = false; + _lastRequest = request; + await RefreshItems.Invoke(request); + } + } + + var result = await ResolveItemsRequestAsync(request); + if (!thisLoadCts.IsCancellationRequested) + { + _internalGridContext.Items = result.Items; + _internalGridContext.TotalItemCount = result.TotalItemCount; + if (RefreshItems is null) + { + Pagination?.SetTotalItemCountAsync(_internalGridContext.TotalItemCount); + } + _pendingDataLoadCancellationTokenSource = null; + } + _internalGridContext.ResetRowIndexes(startIndex); + + StateHasChanged(); + } + + // Gets called both by RefreshDataCoreAsync and directly by the Virtualize child component during scrolling + private async ValueTask> ProvideVirtualizedItemsAsync(ItemsProviderRequest request) + { + _lastRefreshedPaginationState = Pagination; + + // Debounce the requests. This eliminates a lot of redundant queries at the cost of slight lag after interactions. + // TODO: Consider making this configurable, or smarter (e.g., doesn't delay on first call in a batch, then the amount + // of delay increases if you rapidly issue repeated requests, such as when scrolling a long way) + await Task.Delay(100); + + if (request.CancellationToken.IsCancellationRequested) + { + return default; + } + + // Combine the query parameters from Virtualize with the ones from PaginationState + var startIndex = request.StartIndex; + var count = request.Count; + if (Pagination is not null) + { + startIndex += Pagination.CurrentPageIndex * Pagination.ItemsPerPage; + count = Math.Min(request.Count, Pagination.ItemsPerPage - request.StartIndex); + } + + GridItemsProviderRequest providerRequest = new( + startIndex, count, _sortByColumn, _sortByAscending, request.CancellationToken); + var providerResult = await ResolveItemsRequestAsync(providerRequest); + + if (!request.CancellationToken.IsCancellationRequested) + { + // ARIA's rowcount is part of the UI, so it should reflect what the human user regards as the number of rows in the table, + // not the number of physical elements. For virtualization this means what's in the entire scrollable range, not just + // the current viewport. In the case where you're also paginating then it means what's conceptually on the current page. + // TODO: This currently assumes we always want to expand the last page to have ItemsPerPage rows, but the experience might + // be better if we let the last page only be as big as its number of actual rows. + _internalGridContext.TotalItemCount = providerResult.TotalItemCount; + _internalGridContext.TotalViewItemCount = Pagination?.ItemsPerPage ?? providerResult.TotalItemCount; + + if (RefreshItems is null) + { + Pagination?.SetTotalItemCountAsync(_internalGridContext.TotalItemCount); + } + if (_internalGridContext.TotalItemCount > 0 && Loading is null) + { + Loading = false; + StateHasChanged(); + } + + // We're supplying the row _index along with each row's data because we need it for aria-rowindex, and we have to account for + // the virtualized start _index. It might be more performant just to have some _latestQueryRowStartIndex field, but we'd have + // to make sure it doesn't get out of sync with the rows being rendered. + return new ItemsProviderResult<(int, TGridItem)>( + items: providerResult.Items.Select((x, i) => ValueTuple.Create(i + request.StartIndex + 2, x)), + totalItemCount: _internalGridContext.TotalViewItemCount); + } + return default; + } + + // Normalizes all the different ways of configuring a data source so they have common GridItemsProvider-shaped API + private async ValueTask> ResolveItemsRequestAsync(GridItemsProviderRequest request) + { + try + { + if (ItemsProvider is not null) + { + var gipr = await ItemsProvider(request); + if (gipr.Items is not null && Loading is null) + { + Loading = false; + StateHasChanged(); + } + return gipr; + } + else if (Items is not null) + { + var totalItemCount = _asyncQueryExecutor is null ? Items.Count() : await _asyncQueryExecutor.CountAsync(Items, request.CancellationToken); + _internalGridContext.TotalItemCount = totalItemCount; + IQueryable? result; + if (RefreshItems is null) + { + result = request.ApplySorting(Items).Skip(request.StartIndex); + if (request.Count.HasValue) + { + result = result.Take(request.Count.Value); + } + } + else + { + result = Items; + } + var resultArray = _asyncQueryExecutor is null ? [.. result] : await _asyncQueryExecutor.ToArrayAsync(result, request.CancellationToken); + return GridItemsProviderResult.From(resultArray, totalItemCount); + } + } + catch (OperationCanceledException oce) when (oce.CancellationToken == request.CancellationToken) + { + // No-op; we canceled the operation, so it's fine to suppress this exception. + } + return GridItemsProviderResult.From(Array.Empty(), 0); + } + + private string AriaSortValue(ColumnBase column) + => _sortByColumn == column + ? (_sortByAscending ? "ascending" : "descending") + : "none"; + + private string? StyleValue => new StyleBuilder(Style) + .AddStyle("grid-template-columns", _internalGridTemplateColumns, !string.IsNullOrWhiteSpace(_internalGridTemplateColumns) && DisplayMode == DataGridDisplayMode.Grid) + .AddStyle("grid-template-rows", "auto 1fr", (_internalGridContext.Items.Count == 0 || Items is null || EffectiveLoadingValue) && DisplayMode == DataGridDisplayMode.Grid) + .AddStyle("height", "100%", _internalGridContext.TotalItemCount == 0 || EffectiveLoadingValue) + .AddStyle("border-collapse", "separate", GenerateHeader == GenerateHeaderOption.Sticky) + .AddStyle("border-spacing", "0", GenerateHeader == GenerateHeaderOption.Sticky) + .AddStyle("width", "100%", DisplayMode == DataGridDisplayMode.Table) + .Build(); + + private string? ColumnHeaderClass(ColumnBase column) + { + return new CssBuilder(Class) + .AddClass(ColumnJustifyClass(column)) + .AddClass("col-sort-asc", _sortByAscending && column.IsActiveSortColumn) + .AddClass("col-sort-desc", !_sortByAscending && column.IsActiveSortColumn) + .Build(); + } + + private string? GridClass() + { + return new CssBuilder(Class) + .AddClass("fluent-data-grid") + .AddClass("grid", DisplayMode == DataGridDisplayMode.Grid) + .AddClass("auto-fit", AutoFit) + .AddClass("loading", _pendingDataLoadCancellationTokenSource is not null) + .Build(); + } + + private static string? ColumnJustifyClass(ColumnBase column) + { + return new CssBuilder(column.Class) + .AddClass("col-justify-start", column.Align == Align.Start) + .AddClass("col-justify-center", column.Align == Align.Center) + .AddClass("col-justify-end", column.Align == Align.End) + .Build(); + } + + /// + public async ValueTask DisposeAsync() + { + _currentPageItemsChanged.Dispose(); + _scope?.Dispose(); + + try + { + if (_jsEventDisposable is not null) + { + await _jsEventDisposable.InvokeVoidAsync("stop"); + await _jsEventDisposable.DisposeAsync().ConfigureAwait(false); + } + + if (Module is not null) + { + await Module.DisposeAsync().ConfigureAwait(false); + } + } + catch (Exception ex) when (ex is JSDisconnectedException || + ex is OperationCanceledException) + { + // The JSRuntime side may routinely be gone already if the reason we're disposing is that + // the client disconnected. This is not an error. + } + } + + private void CloseColumnOptions() + { + _displayOptionsForColumn = null; + StateHasChanged(); + } + + private void CloseColumnResize() + { + _displayResizeForColumn = null; + StateHasChanged(); + } + + private void LoadStateFromQueryString(string queryString) + { + if (!SaveStateInUrl) + { + return; + } + + var query = System.Web.HttpUtility.ParseQueryString(queryString); + if (query.AllKeys.Contains($"{SaveStatePrefix}orderby")) + { + var orderBy = query[$"{SaveStatePrefix}orderby"]!.Split(' ', 2); + var title = orderBy[0]; + + var column = _columns.FirstOrDefault(c => c.Title == title); + if (column is not null) + { + _sortByColumn = column; + _sortByAscending = orderBy.Length == 2 && orderBy[1] == "asc"; + } + } + + if (Pagination is not null) + { + if (query.AllKeys.Contains($"{SaveStatePrefix}page") && int.TryParse(query[$"{SaveStatePrefix}page"]!, out var page)) + { + Pagination.SetCurrentPageIndexAsync(page - 1); + } + + if (query.AllKeys.Contains($"{SaveStatePrefix}top") && int.TryParse(query[$"{SaveStatePrefix}top"]!, out var itemsPerPage)) + { + Pagination.ItemsPerPage = itemsPerPage; + } + } + } + + private void SaveStateToQueryString() + { + if (!SaveStateInUrl) + { + return; + } + + var stateParams = new Dictionary(); + if (_sortByColumn is not null) + { + var order = _sortByAscending ? "asc" : "desc"; + stateParams.Add($"{SaveStatePrefix}orderby", $"{_sortByColumn.Title} {order}"); + } + stateParams.Add($"{SaveStatePrefix}page", Pagination?.CurrentPageIndex + 1 ?? null); + stateParams.Add($"{SaveStatePrefix}top", Pagination?.ItemsPerPage ?? null); + NavigationManager.NavigateTo(NavigationManager.GetUriWithQueryParameters(stateParams), replace: true); + } + + /// + /// Updates the s ItemPerPage parameter. + /// Guards the CurrentPageIndex from getting greater than the LastPageIndex + /// + /// + /// The maximum number of rows that fits the available space + /// + [JSInvokable] + public async Task UpdateItemsPerPageAsync(int visibleRows) + { + if (Pagination is null) + { + return; + } + + if (visibleRows < 2) + { + visibleRows = 2; + } + + await Pagination.SetItemsPerPageAsync(visibleRows - 1); // subtract 1 for the table header + + //if (Pagination.CurrentPageIndex > Pagination.LastPageIndex && Pagination.LastPageIndex.HasValue && Pagination.LastPageIndex.Value > 0) + //{ + // await Pagination.SetCurrentPageIndexAsync(Pagination.LastPageIndex.Value); + //} + + //await RefreshDataAsync(); + //StateHasChanged(); + } + + //public void SetPageReference(Type page) + //{ + // _dotNetObjectReference = DotNetObjectReference.Create(page); + //} + + public async Task OnKeyDownAsync(FluentKeyCodeEventArgs args) + { + if (args.ShiftKey == true && args.Key == KeyCode.KeyR) + { + await ResetColumnWidthsAsync(); + } + + if (args.Value == "-") + { + await SetColumnWidthDiscreteAsync(null, -10); + } + if (args.Value == "+") + { + // Resize column up + await SetColumnWidthDiscreteAsync(null, 10); + } + //return Task.CompletedTask; + } + + /// + /// Resizes the column width by a discrete amount. + /// + /// The column to be resized + /// The amount of pixels to change width with + /// + public async Task SetColumnWidthDiscreteAsync(int? columnIndex, float widthChange) + { + if (_gridReference is not null && Module is not null) + { + await Module.InvokeVoidAsync("resizeColumnDiscrete", _gridReference, columnIndex, widthChange); + } + } + + /// + /// Resizes the column width to the exact width specified (in pixels). + /// + /// The column to be resized + /// The new width in pixels + /// + public async Task SetColumnWidthExactAsync(int columnIndex, int width) + { + if (_gridReference is not null && Module is not null) + { + await Module.InvokeVoidAsync("resizeColumnExact", _gridReference, columnIndex, width); + } + } + + /// + /// Resets the column widths to their initial values as specified with the parameter. + /// If no value is specified, the default value is "1fr" for each column. + /// + /// + public async Task ResetColumnWidthsAsync() + { + if (_gridReference is not null && Module is not null) + { + await Module.InvokeVoidAsync("resetColumnWidths", _gridReference); + } + } + + /// + /// Computes a hash code for the given items. + /// To limit the effect on performance, only the given maximum number (default 250) of items will be considered. + /// + private static int ComputeItemsHash(IEnumerable? items, int maxItems = 250) + { + if (items == null) + { + return 0; + } + unchecked + { + var hash = 19; + var count = 0; + foreach (var item in items) + { + if (++count > maxItems) + { + break; + } + hash = (hash * 31) + (item?.GetHashCode() ?? 0); + } + return hash; + } + } +} + diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.css b/src/Core/Components/DataGrid/FluentDataGrid.razor.css new file mode 100644 index 0000000000..fcd8d0bd5c --- /dev/null +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.css @@ -0,0 +1,91 @@ +.fluent-data-grid { + --fluent-data-grid-resize-handle-color: var(--accent-fill-rest); + --fluent-data-grid-resize-handle-width: 1px; + --fluent-data-grid-header-opacity: 0.5; + width: auto; + flex: 1; + border-collapse: collapse; + align-items: center; + height: max-content; + margin-bottom: 0px; +} + + .fluent-data-grid.grid { + display: grid; + } + +.grid thead, +.grid tbody { + display: contents; +} + +.grid ::deep tr { + display: contents; +} + +.fluent-data-grid tbody tr .hover { + background: var(--neutral-fill-stealth-hover); +} + +.col-options, .col-resize { + position: absolute; + min-width: 250px; + top: 2.7rem; + background: var(--neutral-layer-2); + border: 1px solid var(--neutral-layer-3); + border-radius: 0.3rem; + box-shadow: 0 3px 8px 1px var(--neutral-layer-4); + padding: 1rem; + visibility: hidden; + z-index: 1; +} + +[dir=rtl] .col-options { + left: unset; +} + +.col-justify-end .col-options, +.col-justify-right .col-options { + left: unset; + margin-right: 0.6rem; +} + +[dir=rtl] .col-justify-end .col-options, +[dir=rtl] .col-justify-right .col-options { + right: unset; + margin-left: 0.6rem; +} + + + +.resize-options { + display: flex; + width: 100%; + justify-content: center; + align-items: center; +} + +::deep .resize-handle { + position: absolute; + top: 5px; + right: 0; + left: unset; + bottom: 0; + height: 30px; + cursor: col-resize; + width: 6px; + border-inline-end: var(--fluent-data-grid-resize-handle-width) solid var(--fluent-data-grid-resize-handle-color); + opacity: var(--fluent-data-grid-header-opacity); +} + +.header { + padding: 0; + z-index: 3; +} + +::deep tr[row-type='sticky-header'] > th { + position: sticky; + top: 0; + background-color: var(--neutral-fill-stealth-rest); + z-index: 2; +} diff --git a/src/Core/Components/DataGrid/FluentDataGridCell.razor b/src/Core/Components/DataGrid/FluentDataGridCell.razor new file mode 100644 index 0000000000..ecdf60e81e --- /dev/null +++ b/src/Core/Components/DataGrid/FluentDataGridCell.razor @@ -0,0 +1,31 @@ +@namespace Microsoft.FluentUI.AspNetCore.Components +@inherits FluentComponentBase +@typeparam TGridItem + +@if (CellType == DataGridCellType.Default) +{ + + @ChildContent + +} +else +{ + + @ChildContent + +} diff --git a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs new file mode 100644 index 0000000000..b6344436b8 --- /dev/null +++ b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs @@ -0,0 +1,128 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; +using Microsoft.FluentUI.AspNetCore.Components.Utilities; + +namespace Microsoft.FluentUI.AspNetCore.Components; + +public partial class FluentDataGridCell : FluentComponentBase +{ + internal string CellId { get; set; } = string.Empty; + + /// + /// Gets or sets the reference to the item that holds this cell's values. + /// + [Parameter] + public TGridItem? Item { get; set; } + + /// + /// Gets or sets the cell type. See . + /// + [Parameter] + public DataGridCellType CellType { get; set; } + + /// + /// Gets or sets the column index of the cell. + /// This will be applied to the css grid-column-index value applied to the cell. + /// + [Parameter] + public int GridColumn { get; set; } + + /// + /// Gets or sets the content to be rendered inside the component. + /// + [Parameter] + public RenderFragment? ChildContent { get; set; } + + /// + /// Gets or sets the owning component. + /// + [CascadingParameter(Name = "OwningRow")] + internal FluentDataGridRow Owner { get; set; } = default!; + + /// + /// Gets or sets the owning component + /// + [CascadingParameter] + internal InternalGridContext InternalGridContext { get; set; } = default!; + + /// + /// Gets a reference to the column that this cell belongs to. + /// + private ColumnBase? Column => Grid._columns.ElementAtOrDefault(GridColumn - 1); + + /// + /// Gets a reference to the enclosing . + /// + protected FluentDataGrid Grid => InternalGridContext.Grid; + + protected string? ClassValue => new CssBuilder(Class) + .AddClass("column-header", when: CellType == DataGridCellType.ColumnHeader) + .AddClass("select-all", when: CellType == DataGridCellType.ColumnHeader && Column is SelectColumn) + .AddClass("multiline-text", when: Grid.MultiLine && (Grid.Items is not null || Grid.ItemsProvider is not null) && CellType != DataGridCellType.ColumnHeader) + .AddClass(Owner.Class) + .Build(); + + protected string? StyleValue => new StyleBuilder(Style) + .AddStyle("grid-column", GridColumn.ToString(), () => !Grid.EffectiveLoadingValue && (Grid.Items is not null || Grid.ItemsProvider is not null) && Grid.DisplayMode == DataGridDisplayMode.Grid) + .AddStyle("text-align", "center", Column is SelectColumn) + .AddStyle("align-content", "center", Column is SelectColumn) + .AddStyle("padding-inline-start", "calc(((var(--design-unit)* 3) + var(--focus-stroke-width) - var(--stroke-width))* 1px)", Column is SelectColumn && Owner.RowType == DataGridRowType.Default) + .AddStyle("padding-top", "calc(var(--design-unit) * 2.5px)", Column is SelectColumn && (Grid.RowSize == DataGridRowSize.Medium || Owner.RowType == DataGridRowType.Header)) + .AddStyle("padding-top", "calc(var(--design-unit) * 1.5px)", Column is SelectColumn && Grid.RowSize == DataGridRowSize.Small && Owner.RowType == DataGridRowType.Default) + .AddStyle("width", Column?.Width, !string.IsNullOrEmpty(Column?.Width) && Grid.DisplayMode == DataGridDisplayMode.Table) + .AddStyle("height", $"{Grid.ItemSize:0}px", () => !Grid.EffectiveLoadingValue && Grid.Virtualize) + .AddStyle("height", $"{(int)Grid.RowSize}px", () => !Grid.EffectiveLoadingValue && !Grid.Virtualize && !Grid.MultiLine && (Grid.Items is not null || Grid.ItemsProvider is not null)) + .AddStyle("height", "100%", Grid.MultiLine) + .AddStyle("min-height", "44px", Owner.RowType != DataGridRowType.Default) + .AddStyle("z-index", ZIndex.DataGridHeaderPopup.ToString(), CellType == DataGridCellType.ColumnHeader && Grid._columns.Count > 0 && Grid.UseMenuService) + .AddStyle(Owner.Style) + .Build(); + + protected override void OnInitialized() + { + Owner.Register(this); + } + + /// + internal async Task HandleOnCellClickAsync() + { + if (Grid.OnCellClick.HasDelegate) + { + await Grid.OnCellClick.InvokeAsync(this); + } + + if (Column != null) + { + await Column.OnCellClickAsync(this); + } + } + + internal async Task HandleOnCellFocusAsync() + { + if (CellType == DataGridCellType.Default) + { + await Grid.OnCellFocus.InvokeAsync(this); + } + } + + internal async Task HandleOnCellKeyDownAsync(KeyboardEventArgs e) + { + if (!SelectColumn.KEYBOARD_SELECT_KEYS.Contains(e.Code)) + { + return; + } + + if (Column != null) + { + await Column.OnCellKeyDownAsync(this, e); + } + } + + public void Dispose() => Owner.Unregister(this); + +} diff --git a/src/Core/Components/DataGrid/FluentDataGridCell.razor.css b/src/Core/Components/DataGrid/FluentDataGridCell.razor.css new file mode 100644 index 0000000000..a7de410b1b --- /dev/null +++ b/src/Core/Components/DataGrid/FluentDataGridCell.razor.css @@ -0,0 +1,108 @@ +th, td { + border-bottom: calc(var(--stroke-width)* 1px) solid var(--neutral-stroke-divider-rest); +} + +td { + padding: 6px 16px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + align-content: center; +} + + + td.col-justify-center { + text-align: center; + } + + td.col-justify-end, + td.col-justify-right { + text-align: end; + } + +th.col-justify-end > div { + justify-content: flex-end; +} + + +td.grid-cell-placeholder:after { + content: '\2026'; /*horizontal ellipsis*/ + opacity: 0.75; +} + + +.empty-content-cell, +.loading-content-cell { + font-weight: 600; + text-align: center; + height: 100%; + user-select: none; +} + +.multiline-text { + white-space: inherit; + overflow: auto; + word-break: break-word; + align-content: start; +} + +.column-header { + font-weight: 600; + text-align: center; + position: relative; + padding: calc((var(--design-unit) + var(--focus-stroke-width) - var(--stroke-width)) * 1px) 1px calc((var(--design-unit) + var(--focus-stroke-width) - var(--stroke-width)) * 1px); +} + +::deep .col-sort-button { + width: calc(100% - 20px); + overflow: hidden; + text-overflow: ellipsis; +} + + ::deep .col-sort-button::part(content) { + overflow: hidden; + } + +::deep .col-options-button { + padding-inline-start: 4px; +} + +.col-justify-start ::deep .col-sort-button::part(control) { + justify-content: start; + overflow: hidden; + opacity: 1 +} + +.col-justify-center ::deep .col-sort-button::part(control) { + justify-content: center; + overflow: hidden; + opacity: 1 +} + +.col-justify-end ::deep .col-sort-button::part(control) { + justify-content: end; + overflow: hidden; + opacity: 1 +} + + +.col-justify-end ::deep .col-sort-button::part(start) { + margin-inline-end: 2px; +} + +.col-justify-start ::deep .col-sort-button::part(end), +.col-justify-center ::deep .col-sort-button::part(end) { + margin-inline-start: 2px; +} + +.col-justify-start ::deep .col-title { + text-align: left; +} + +.col-justify-center ::deep .col-title { + text-align: center; +} + +.col-justify-end ::deep .col-title { + text-align: end; +} diff --git a/src/Core/Components/DataGrid/FluentDataGridRow.razor b/src/Core/Components/DataGrid/FluentDataGridRow.razor new file mode 100644 index 0000000000..7233361c82 --- /dev/null +++ b/src/Core/Components/DataGrid/FluentDataGridRow.razor @@ -0,0 +1,18 @@ +@namespace Microsoft.FluentUI.AspNetCore.Components +@inherits FluentComponentBase +@typeparam TGridItem +@attribute [CascadingTypeParameter(nameof(TGridItem))] + + + @ChildContent + + diff --git a/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs b/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs new file mode 100644 index 0000000000..f3d3a0af59 --- /dev/null +++ b/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs @@ -0,0 +1,177 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; +using Microsoft.FluentUI.AspNetCore.Components.Utilities; + +namespace Microsoft.FluentUI.AspNetCore.Components; + +[CascadingTypeParameter(nameof(TGridItem))] +public partial class FluentDataGridRow : FluentComponentBase, IHandleEvent, IDisposable +{ + internal string RowId { get; set; } = string.Empty; + private readonly Dictionary> cells = []; + + /// + /// Gets or sets the reference to the item that holds this row's values. + /// + [Parameter] + public TGridItem? Item { get; set; } + + /// + /// Gets or sets the index of this row. + /// When FluentDataGrid is virtualized, this value is not used. + /// + [Parameter] + public int? RowIndex { get; set; } + + /// + /// Gets or sets the string that gets applied to the css gridTemplateColumns attribute for the row. + /// + [Parameter] + public string? GridTemplateColumns { get; set; } = null; + + /// + /// Gets or sets the type of row. See . + /// + [Parameter] + public DataGridRowType? RowType { get; set; } = DataGridRowType.Default; + + [Parameter] + public VerticalAlignment VerticalAlignment { get; set; } = VerticalAlignment.Center; + + /// + /// Gets or sets the content to be rendered inside the component. + /// + [Parameter] + public RenderFragment? ChildContent { get; set; } + + [Parameter] + public EventCallback> OnCellFocus { get; set; } + + /// + /// Gets or sets the owning component + /// + [CascadingParameter] + internal InternalGridContext InternalGridContext { get; set; } = default!; + + /// + /// Gets a reference to the enclosing . + /// + protected FluentDataGrid Grid => InternalGridContext.Grid; + + protected string? ClassValue => new CssBuilder(Class) + .AddClass("fluent-data-grid-row") + .AddClass("hover", when: Grid.ShowHover) + .Build(); + + protected string? StyleValue => new StyleBuilder(Style) + //.AddStyle("height", $"{Grid.ItemSize:0}px", () => Grid.Virtualize && RowType == DataGridRowType.Default) + //.AddStyle("height", "100%", () => (!Grid.Virtualize || InternalGridContext.Rows.Count == 0) && Grid.Loading && RowType == DataGridRowType.Default) + .Build(); + + protected override void OnInitialized() + { + RowId = $"r{InternalGridContext.GetNextRowId()}"; + InternalGridContext.Register(this); + } + + public void Dispose() => InternalGridContext.Unregister(this); + + internal void Register(FluentDataGridCell cell) + { + + cell.CellId = $"c{InternalGridContext.GetNextCellId()}"; + cells.Add(cell.CellId, cell); + } + + internal void Unregister(FluentDataGridCell cell) + { + cells.Remove(cell.CellId!); + } + + internal async Task HandleOnRowFocusAsync() + { + if (Grid.OnRowFocus.HasDelegate) + { + await Grid.OnRowFocus.InvokeAsync(this); + } + } + + /// + internal async Task HandleOnRowClickAsync(string rowId) + { + var row = GetRow(rowId); + + if (row is not null && !string.IsNullOrWhiteSpace(row.Class) && (row.Class.Contains(FluentDataGrid.EMPTY_CONTENT_ROW_CLASS) || row.Class.Contains(FluentDataGrid.LOADING_CONTENT_ROW_CLASS))) + { + return; + } + + if (row != null && Grid.OnRowClick.HasDelegate) + { + await Grid.OnRowClick.InvokeAsync(row); + } + + if (row != null && row.RowType == DataGridRowType.Default) + { + foreach (var column in Grid._columns) + { + await column.OnRowClickAsync(row); + } + } + } + + /// + internal async Task HandleOnRowDoubleClickAsync(string rowId) + { + var row = GetRow(rowId); + if (row != null && Grid.OnRowDoubleClick.HasDelegate) + { + await Grid.OnRowDoubleClick.InvokeAsync(row); + } + } + + /// + internal async Task HandleOnRowKeyDownAsync(string rowId, KeyboardEventArgs e) + { + if (!SelectColumn.KEYBOARD_SELECT_KEYS.Contains(e.Code)) + { + return; + } + + var row = GetRow(rowId); + + if (row != null && Grid.OnRowClick.HasDelegate) + { + await Grid.OnRowClick.InvokeAsync(row); + } + + if (row != null && row.RowType == DataGridRowType.Default) + { + foreach (var column in Grid._columns) + { + await column.OnRowKeyDownAsync(row, e); + } + } + } + + private FluentDataGridRow? GetRow(string rowId, Func, bool>? where = null) + { + if (!string.IsNullOrEmpty(rowId) && InternalGridContext.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); +} diff --git a/src/Core/Components/DataGrid/FluentDataGridRow.razor.css b/src/Core/Components/DataGrid/FluentDataGridRow.razor.css new file mode 100644 index 0000000000..2fa3af2c47 --- /dev/null +++ b/src/Core/Components/DataGrid/FluentDataGridRow.razor.css @@ -0,0 +1,10 @@ +.sticky-header { + z-index: 3; +} + +.hover:not([row-type='header'],[row-type='sticky-header'],.loading-content-row):hover ::deep td:not(.empty-content-cell) { + cursor: pointer; + background-color: var(--datagrid-hover-color, var(--neutral-fill-stealth-hover)); +} + + diff --git a/src/Core/Components/DataGrid/GridItemsProvider.cs b/src/Core/Components/DataGrid/GridItemsProvider.cs new file mode 100644 index 0000000000..09bb754e6e --- /dev/null +++ b/src/Core/Components/DataGrid/GridItemsProvider.cs @@ -0,0 +1,10 @@ +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// A callback that provides data for a . +/// +/// The type of data represented by each row in the grid. +/// Parameters describing the data being requested. +/// A that gives the data to be displayed. +public delegate ValueTask> GridItemsProvider( + GridItemsProviderRequest request); diff --git a/src/Core/Components/DataGrid/GridItemsProviderRequest.cs b/src/Core/Components/DataGrid/GridItemsProviderRequest.cs new file mode 100644 index 0000000000..3ca5ccbb3e --- /dev/null +++ b/src/Core/Components/DataGrid/GridItemsProviderRequest.cs @@ -0,0 +1,90 @@ +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// Parameters for data to be supplied by a 's . +/// +/// The type of data represented by each row in the grid. +public readonly struct GridItemsProviderRequest +{ + /// + /// Gets or sets the zero-based index of the first item to be supplied. + /// + public int StartIndex { get; init; } + + /// + /// If set, the maximum number of items to be supplied. If not set, the maximum number is unlimited. + /// + public int? Count { get; init; } + + /// + /// Gets or sets which column represents the sort order. + /// + /// Rather than inferring the sort rules manually, you should normally call either + /// or , since they also account for and automatically. + /// + public ColumnBase? SortByColumn { get; init; } + + /// + /// Gets or sets thecurrent sort direction. + /// + /// Rather than inferring the sort rules manually, you should normally call either + /// or , since they also account for and automatically. + /// + public bool SortByAscending { get; init; } + + /// + /// Gets or sets a token that indicates if the request should be cancelled. + /// + public CancellationToken CancellationToken { get; init; } + + internal GridItemsProviderRequest( + int startIndex, int? count, ColumnBase? sortByColumn, bool sortByAscending, + CancellationToken cancellationToken) + { + StartIndex = startIndex; + Count = count; + SortByColumn = sortByColumn; + SortByAscending = sortByAscending; + CancellationToken = cancellationToken; + } + + /// + /// Applies the request's sorting rules to the supplied . + /// + /// An . + /// A new representing the with sorting rules applied. + public IQueryable ApplySorting(IQueryable source) => + SortByColumn?.SortBy?.Apply(source, SortByAscending) ?? source; + + /// + /// Produces a collection of (property name, direction) pairs representing the sorting rules. + /// + /// A collection of (property name, direction) pairs representing the sorting rules + public IReadOnlyCollection GetSortByProperties() => + SortByColumn?.SortBy?.ToPropertyList(SortByAscending) ?? Array.Empty(); + + public bool IsSameRequest(GridItemsProviderRequest req) + { + if (StartIndex != req.StartIndex) + { + return false; + } + + if (Count != req.Count) + { + return false; + } + + if (SortByColumn?.Index != req.SortByColumn?.Index) + { + return false; + } + + if (SortByAscending != req.SortByAscending) + { + return false; + } + + return true; + } +} diff --git a/src/Core/Components/DataGrid/GridItemsProviderResult.cs b/src/Core/Components/DataGrid/GridItemsProviderResult.cs new file mode 100644 index 0000000000..42b8214b4f --- /dev/null +++ b/src/Core/Components/DataGrid/GridItemsProviderResult.cs @@ -0,0 +1,39 @@ +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// Holds data being supplied to a 's . +/// +/// The type of data represented by each row in the grid. +public readonly struct GridItemsProviderResult +{ + /// + /// Gets or sets the items being supplied. + /// + public /*required*/ ICollection Items { get; init; } + + /// + /// Gets or sets the total number of items that may be displayed in the grid. This normally means the total number of items in the + /// underlying data source after applying any filtering that is in effect. + /// + /// If the grid is paginated, this should include all pages. If the grid is virtualized, this should include the entire scroll range. + /// + public int TotalItemCount { get; init; } +} + +/// +/// Provides convenience methods for constructing instances. +/// +public static class GridItemsProviderResult +{ + // This is just to provide generic type inference, so you don't have to specify TGridItem yet again. + + /// + /// Supplies an instance of . + /// + /// The type of data represented by each row in the grid. + /// The items being supplied. + /// The total numer of items that exist. See for details. + /// An instance of . + public static GridItemsProviderResult From(ICollection items, int totalItemCount) + => new() { Items = items, TotalItemCount = totalItemCount }; +} diff --git a/src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs b/src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs new file mode 100644 index 0000000000..331f75700d --- /dev/null +++ b/src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs @@ -0,0 +1,60 @@ +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; + +internal static class AsyncQueryExecutorSupplier +{ + // The primary goal with this is to ensure that: + // - If you're using EF Core, then we resolve queries efficiently using its ToXyzAsync async extensions and don't + // just fall back on the synchronous IQueryable ToXyz calls + // - ... but without FluentDataGrid referencing Microsoft.EntityFramework directly. That's because it would bring in + // heavy dependencies you may not be using (and relying on trimming isn't enough, as it's still desirable to have + // heavy unused dependencies for Blazor Server). + // + // As a side-effect, we have an abstraction IAsyncQueryExecutor that developers could use to plug in their own + // mechanism for resolving async queries from other data sources than EF. It's not really a major goal to make this + // adapter generally useful beyond EF, but fine if people do have their own uses for it. + + private static readonly ConcurrentDictionary IsEntityFrameworkProviderTypeCache = new(); + + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2111", + Justification = "The reflection is a best effort to warn developers about sync-over-async behavior which can cause thread pool starvation.")] + public static IAsyncQueryExecutor? GetAsyncQueryExecutor(IServiceProvider services, IQueryable? queryable) + { + if (queryable is not null) + { + var executors = services.GetServices(); + + if (executors is null) + { + // It's useful to detect if the developer is unaware that they should be using the EF adapter, otherwise + // they will likely never notice and simply deploy an inefficient app that blocks threads on each query. + var providerType = queryable.Provider?.GetType(); + if (providerType is not null && IsEntityFrameworkProviderTypeCache.GetOrAdd(providerType, IsEntityFrameworkProviderType)) + { + throw new InvalidOperationException($"The supplied {nameof(IQueryable)} is provided by Entity Framework. To query it efficiently, see https://github.com/microsoft/fluentui-blazor#use-the-datagrid-component-with-ef-core for the needed steps."); + } + } + else + { + foreach (var executor in executors) + { + if (executor.IsSupported(queryable)) + { + return executor; + } + } + } + } + + return null; + } + + // We have to do this via reflection because the whole point is to avoid any static dependency on EF unless you + // reference the adapter. Trimming won't cause us any problems because this is only a way of detecting misconfiguration + // so it's sufficient if it can detect the misconfiguration in development. + private static bool IsEntityFrameworkProviderType([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type queryableProviderType) + => queryableProviderType.GetInterfaces().Any(x => string.Equals(x.FullName, "Microsoft.EntityFrameworkCore.Query.IAsyncQueryProvider", StringComparison.Ordinal)) == true; +} diff --git a/src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs b/src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs new file mode 100644 index 0000000000..161892eb90 --- /dev/null +++ b/src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs @@ -0,0 +1,60 @@ +using System.ComponentModel; +using Microsoft.AspNetCore.Components; + +namespace Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; + +// One awkwardness of the way FluentDataGrid collects its list of child columns is that, during OnParametersSetAsync, +// it only knows about the set of columns that were present on the *previous* render. If it's going to trigger a +// data load during OnParametersSetAsync, that operation can't depend on the current set of columns as it might +// have changed, or might even still be empty (i.e., on the first render). +// +// Ways this could be resolved: +// +// - In the future, we could implement the long-wanted feature of being able to query the contents of a RenderFragment +// separately from rendering. Then the whole trick of collection-during-rendering would not be needed. +// - Or, we could factor out most of FluentDataGrid's internals into some new component FluentDataGridCore. The parent component, +// FluentDataGrid, would then only be responsible for collecting columns followed by rendering FluentDataGridCore. So each time +// FluentDataGridCore renders, we'd already have the latest set of columns +// - Drawback: since FluentDataGrid has public API, it's much messier to have to forward all of that to some new child type. +// - However, this is arguably the most correct solution in general (at least until option 1 above is implemented) +// - Or, we could decide it's enough to fix this on the first render (since that's the only time we're going to guarantee +// to apply a default sort order), and then as a special case put in some extra component in the render flow that raises +// an event once the columns are first collected. +// - This is relatively simple and non-disruptive, though it doesn't cover cases where queries need to be delayed until +// after a dynamically-added column is added +// +// The final option is what's implemented here. We send the notification via EventCallbackSubscribable so that the async +// operation and re-rendering follows normal semantics without us having to call StateHasChanged or think about exceptions. + +/// +/// For internal use only. Do not use. +/// +/// For internal use only. Do not use. +[EditorBrowsable(EditorBrowsableState.Never)] +public sealed class ColumnsCollectedNotifier : Microsoft.AspNetCore.Components.IComponent +{ + private bool _isFirstRender = true; + + [CascadingParameter] internal InternalGridContext InternalGridContext { get; set; } = default!; + + /// + public void Attach(RenderHandle renderHandle) + { + // This component never renders, so we can ignore the renderHandle + } + + /// + public Task SetParametersAsync(ParameterView parameters) + { + if (_isFirstRender) + { + _isFirstRender = false; + parameters.SetParameterProperties(this); + return InternalGridContext.ColumnsFirstCollected.InvokeCallbacksAsync(null); + } + else + { + return Task.CompletedTask; + } + } +} diff --git a/src/Core/Components/DataGrid/Infrastructure/Defer.cs b/src/Core/Components/DataGrid/Infrastructure/Defer.cs new file mode 100644 index 0000000000..245482be6c --- /dev/null +++ b/src/Core/Components/DataGrid/Infrastructure/Defer.cs @@ -0,0 +1,28 @@ +using System.ComponentModel; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Rendering; + +namespace Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; + +// This is used by FluentDataGrid to move its body rendering to the end of the render queue so we can collect +// the list of child columns first. It has to be public only because it's used from .razor logic. + +/// +/// For internal use only. Do not use. +/// +[EditorBrowsable(EditorBrowsableState.Never)] +public sealed class Defer : ComponentBase +{ + /// + /// For internal use only. Do not use. + /// + [Parameter] public RenderFragment? ChildContent { get; set; } + + /// + /// For internal use only. Do not use. + /// + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + builder.AddContent(0, ChildContent); + } +} diff --git a/src/Core/Components/DataGrid/Infrastructure/DisplayAttributeExtensions.cs b/src/Core/Components/DataGrid/Infrastructure/DisplayAttributeExtensions.cs new file mode 100644 index 0000000000..0d69a8e0f3 --- /dev/null +++ b/src/Core/Components/DataGrid/Infrastructure/DisplayAttributeExtensions.cs @@ -0,0 +1,34 @@ +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; + +namespace Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; +internal static class DisplayAttributeExtensions +{ + + public static string? GetDisplayAttributeString([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] this Type itemType, string propertyName) + { + PropertyInfo? propertyInfo = itemType.GetProperty(propertyName); + //if (PropertyInfo == null && typeof(ICustomTypeProvider).IsAssignableFrom(itemType)) + // PropertyInfo = ((ICustomTypeProvider)Item).GetCustomType().GetProperty(PropertyName); + if (propertyInfo == null) + { + return null; + } + + var displayAttribute = propertyInfo.GetCustomAttributes(typeof(DisplayAttribute), true).FirstOrDefault() as DisplayAttribute; + if (displayAttribute is not null) + { + return displayAttribute.GetName(); + } + else + { + if (itemType.GetCustomAttribute(typeof(MetadataTypeAttribute)) is MetadataTypeAttribute metadata) + { + return metadata.MetadataClassType.GetDisplayAttributeString(propertyName); + } + } + return null; + } + +} diff --git a/src/Core/Components/DataGrid/Infrastructure/IAsyncQueryExecutor.cs b/src/Core/Components/DataGrid/Infrastructure/IAsyncQueryExecutor.cs new file mode 100644 index 0000000000..5e6f17d7fb --- /dev/null +++ b/src/Core/Components/DataGrid/Infrastructure/IAsyncQueryExecutor.cs @@ -0,0 +1,33 @@ +namespace Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; + +/// +/// Provides methods for asynchronous evaluation of queries against an . +/// +public interface IAsyncQueryExecutor +{ + /// + /// Determines whether the is supported by this type. + /// + /// The data type. + /// An instance. + /// True if this instance can perform asynchronous queries for the supplied , otherwise false. + bool IsSupported(IQueryable queryable); + + /// + /// Asynchronously counts the items in the , if supported. + /// + /// The data type. + /// An instance. + /// An instance. + /// The number of items in .. + Task CountAsync(IQueryable queryable, CancellationToken cancellationToken = default); + + /// + /// Asynchronously materializes the as an array, if supported. + /// + /// The data type. + /// An instance. + /// An instance. + /// The items in the .. + Task ToArrayAsync(IQueryable queryable, CancellationToken cancellationToken = default); +} diff --git a/src/Core/Components/DataGrid/Infrastructure/IBindableColumn.cs b/src/Core/Components/DataGrid/Infrastructure/IBindableColumn.cs new file mode 100644 index 0000000000..8978d1ce2a --- /dev/null +++ b/src/Core/Components/DataGrid/Infrastructure/IBindableColumn.cs @@ -0,0 +1,23 @@ +using System.Linq.Expressions; +using System.Reflection; + +namespace Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; + +/// +/// A column that can bind to a property of model +/// +public interface IBindableColumn +{ + PropertyInfo? PropertyInfo { get; } +} +/// +/// A column that can bind to a property of model +/// +/// Model item type +/// Type of property +internal interface IBindableColumn : IBindableColumn +{ + + public Expression> Property { get; set; } + +} diff --git a/src/Core/Components/DataGrid/Infrastructure/InternalGridContext.cs b/src/Core/Components/DataGrid/Infrastructure/InternalGridContext.cs new file mode 100644 index 0000000000..832b68f59f --- /dev/null +++ b/src/Core/Components/DataGrid/Infrastructure/InternalGridContext.cs @@ -0,0 +1,60 @@ +using Microsoft.FluentUI.AspNetCore.Components.Infrastructure; + +namespace Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; + +// The grid cascades this so that descendant columns can talk back to it. It's an internal type +// so that it doesn't show up by mistake in unrelated components. +internal sealed class InternalGridContext +{ + private int _index = 0; + private int _rowId = 0; + private int _cellId = 0; + + public (ColumnBase? Column, DataGridSortDirection? Direction) DefaultSortColumn { get; set; } + //public SortDirection? DefaultSortDirection { get; set; } + + public Dictionary> Rows { get; set; } = []; + + public ICollection Items { get; set; } = []; + public int TotalItemCount { get; set; } + public int TotalViewItemCount { get; set; } + + public FluentDataGrid Grid { get; } + public EventCallbackSubscribable ColumnsFirstCollected { get; } = new(); + + public InternalGridContext(FluentDataGrid grid) + { + Grid = grid; + } + + public int GetNextRowId() + { + Interlocked.Increment(ref _rowId); + return _rowId; + } + + public int GetNextCellId() + { + Interlocked.Increment(ref _cellId); + return _cellId; + } + + internal void ResetRowIndexes(int start) + { + _index = start; + } + + internal void Register(FluentDataGridRow row) + { + Rows.Add(row.RowId, row); + if (!Grid.Virtualize) + { + row.RowIndex = _index++; + } + } + + internal void Unregister(FluentDataGridRow row) + { + Rows.Remove(row.RowId); + } +} diff --git a/src/Core/Components/Pagination/PaginationState.cs b/src/Core/Components/Pagination/PaginationState.cs index 8d80041320..5e845253d2 100644 --- a/src/Core/Components/Pagination/PaginationState.cs +++ b/src/Core/Components/Pagination/PaginationState.cs @@ -72,6 +72,7 @@ public async Task SetItemsPerPageAsync(int itemsPerPage) { await SetTotalItemCountAsync(TotalItemCount.Value, true); } + return; } From 96e40df713bf8976d3d7968073ec04bce85aa18e Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Thu, 12 Jun 2025 10:10:56 +0200 Subject: [PATCH 03/44] Add DataGridGeneratedHeaderType --- .../Migration/MigrationFluentDataGrid.md | 3 ++- src/Core/Enums/DataGridGeneratedHeaderType.cs | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 src/Core/Enums/DataGridGeneratedHeaderType.cs diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/GetStarted/Migration/MigrationFluentDataGrid.md b/examples/Demo/FluentUI.Demo.Client/Documentation/GetStarted/Migration/MigrationFluentDataGrid.md index 2ac06c663f..d0e61b46c1 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/GetStarted/Migration/MigrationFluentDataGrid.md +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/GetStarted/Migration/MigrationFluentDataGrid.md @@ -4,5 +4,6 @@ - `ColumnSortLabels` has been renamed to `ColumnSortUISettings` ### Enum changes -- `SortDirection` has been renamed to `DataGridSortDirection` - `Align` has been renamed to `HorizontalAlignment` +- `GenerateHeaderOption` has been renamed to `DataGridGeneratedHeaderType` +- `SortDirection` has been renamed to `DataGridSortDirection` diff --git a/src/Core/Enums/DataGridGeneratedHeaderType.cs b/src/Core/Enums/DataGridGeneratedHeaderType.cs new file mode 100644 index 0000000000..f0a5d17799 --- /dev/null +++ b/src/Core/Enums/DataGridGeneratedHeaderType.cs @@ -0,0 +1,26 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// The option for generating a header for the . +/// +public enum DataGridGeneratedHeaderType +{ + /// + /// No header row. + /// + None, + + /// + /// Generate a header row. + /// + Default, + + /// + /// Generate a sticky header row. + /// + Sticky +} From 595ff781c965c14896834c2d23697646b2fecde3 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Thu, 12 Jun 2025 13:24:06 +0200 Subject: [PATCH 04/44] Fix analyzer errors --- spelling.dic | 9 + .../DataGrid/Columns/ColumnBase.razor | 16 +- .../DataGrid/Columns/ColumnBase.razor.cs | 8 + .../Columns/ColumnOptionsUISettings.cs | 2 +- .../Columns/ColumnResizeOptions.razor | 10 +- .../Columns/ColumnResizeOptions.razor.cs | 20 ++- .../DataGrid/Columns/PropertyColumn.cs | 35 ++-- .../DataGrid/Columns/SelectColumn.cs | 162 ++++++++---------- .../DataGrid/Columns/TemplateColumn.cs | 5 + .../Components/DataGrid/FluentDataGrid.razor | 10 +- .../DataGrid/FluentDataGrid.razor.cs | 107 +++++++----- .../DataGrid/FluentDataGridCell.razor.cs | 39 +++-- .../DataGrid/FluentDataGridRow.razor | 1 + .../DataGrid/FluentDataGridRow.razor.cs | 36 +++- .../Components/DataGrid/GridItemsProvider.cs | 4 + .../DataGrid/GridItemsProviderRequest.cs | 10 ++ .../DataGrid/GridItemsProviderResult.cs | 4 + .../AsyncQueryExecutorSupplier.cs | 4 + .../ColumnsCollectedNotifier.cs | 10 +- .../DataGrid/Infrastructure/Defer.cs | 4 + .../DisplayAttributeExtensions.cs | 34 ---- .../Infrastructure/IAsyncQueryExecutor.cs | 4 + .../Infrastructure/IBindableColumn.cs | 8 +- .../Infrastructure/InternalGridContext.cs | 12 +- src/Core/Components/Icons/CoreIcons.cs | 17 +- src/Core/Events/EventHandlers.cs | 3 + .../Extensions/DisplayAttributeExtensions.cs | 43 +++++ src/Core/Utilities/ZIndex.cs | 6 + 28 files changed, 378 insertions(+), 245 deletions(-) delete mode 100644 src/Core/Components/DataGrid/Infrastructure/DisplayAttributeExtensions.cs create mode 100644 src/Core/Extensions/DisplayAttributeExtensions.cs diff --git a/spelling.dic b/spelling.dic index c1e5200f58..047ee38ddc 100644 --- a/spelling.dic +++ b/spelling.dic @@ -92,3 +92,12 @@ wdelta sortabillity Subscribable eventargs +displaymode +rowcount +ontreechanged +ontreetoggle +onclosecolumnoptions +onclosecolumnresize +rowindex +colspan +onkeydown diff --git a/src/Core/Components/DataGrid/Columns/ColumnBase.razor b/src/Core/Components/DataGrid/Columns/ColumnBase.razor index 594b88ebca..11dd3dff4b 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnBase.razor +++ b/src/Core/Components/DataGrid/Columns/ColumnBase.razor @@ -42,16 +42,16 @@ { if (Grid.SortByAscending == true) { - + } else { - + } } @if (ColumnOptions is not null && Filtered.GetValueOrDefault()) { - + } } @@ -112,14 +112,14 @@ // determine align string based on Align value align = Align switch { - Align.Start => "flex-start", - Align.Center => "center", - Align.End => "flex-end", + HorizontalAlignment.Start => "flex-start", + HorizontalAlignment.Center => "center", + HorizontalAlignment.End => "flex-end", _ => "flex-start" };
- @if (Align == Align.Start || Align == Align.Center) + @if (Align == HorizontalAlignment.Start || Align == HorizontalAlignment.Center) { @if (Grid.ResizeType is not null) { @@ -175,7 +175,7 @@
} - @if (Align == Align.End) + @if (Align == HorizontalAlignment.End) { @if (Grid.ResizeType is not null) { diff --git a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs index 091c6449f8..4df5e1cb55 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs @@ -347,4 +347,12 @@ private string GetSortOptionText() return Grid.ColumnSortLabels.SortMenu; } + + /// + /// Sets the column index for the current instance. + /// + internal void SetColumnIndex(int index) + { + Index = index; + } } diff --git a/src/Core/Components/DataGrid/Columns/ColumnOptionsUISettings.cs b/src/Core/Components/DataGrid/Columns/ColumnOptionsUISettings.cs index 7576fedccb..2de7dc1443 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnOptionsUISettings.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnOptionsUISettings.cs @@ -18,7 +18,7 @@ public record ColumnOptionsUISettings /// /// Gets or sets the icon to show in the column menu /// - public Icon? Icon { get; set; } = new CoreIcons.Regular.Size16.Filter(); + public Icon? Icon { get; set; } = new CoreIcons.Regular.Size20.Filter(); /// /// Gets or sets whether the icon is positioned at the start (true) or diff --git a/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor b/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor index 120a152342..9d3dfa4f86 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor +++ b/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor @@ -15,11 +15,11 @@ {
-
- - + +
diff --git a/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor.cs b/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor.cs index f50f02e35a..82ad09ac04 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor.cs @@ -1,8 +1,20 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +using System.Globalization; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; using Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// Provides options for resizing a column in a . +/// +/// This class allows configuring the resizing behavior for a specific column in the grid. The resizing +/// can be performed either in discrete steps or to an exact width, depending on the . +/// The type of the data items displayed in the grid. public partial class ColumnResizeOptions { private string? _width; @@ -25,11 +37,12 @@ public partial class ColumnResizeOptions /// Gets or sets the type of resize to perform /// Discrete: resize by a 10 pixels at a time /// Exact: resize to the exact width specified (in pixels) - /// The display of this component is dependant on a ResizeType being set + /// The display of this component is dependent on a ResizeType being set ///
[Parameter] public DataGridResizeType? ResizeType { get; set; } = DataGridResizeType.Discrete; + /// protected override void OnParametersSet() { if (Column == 0) @@ -55,8 +68,7 @@ private async Task HandleResetAsync() private async Task HandleColumnWidthAsync() { - - var valid = int.TryParse(_width, out var result); + var valid = int.TryParse(_width, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result); if (valid) { await Grid.SetColumnWidthExactAsync(Column, result); @@ -65,7 +77,7 @@ private async Task HandleColumnWidthAsync() private async Task HandleColumnWidthKeyDownAsync(KeyboardEventArgs args) { - if (args.Key == "Enter") + if (string.Equals(args.Key, "Enter", StringComparison.OrdinalIgnoreCase)) { await HandleColumnWidthAsync(); } diff --git a/src/Core/Components/DataGrid/Columns/PropertyColumn.cs b/src/Core/Components/DataGrid/Columns/PropertyColumn.cs index f3a44c9616..d5b6176b34 100644 --- a/src/Core/Components/DataGrid/Columns/PropertyColumn.cs +++ b/src/Core/Components/DataGrid/Columns/PropertyColumn.cs @@ -2,12 +2,12 @@ // MIT License - Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------------------ +using System.Linq.Expressions; +using System.Reflection; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; using Microsoft.FluentUI.AspNetCore.Components.Extensions; -using System.Linq.Expressions; -using System.Reflection; namespace Microsoft.FluentUI.AspNetCore.Components; @@ -22,9 +22,10 @@ public class PropertyColumn : ColumnBase, IBindable private Expression>? _lastAssignedProperty; private Func? _cellTextFunc; private Func? _cellTooltipTextFunc; - private IGridSort? _sortBuilder; - private IGridSort? _customSortBy; + /// + /// Gets the property info for the property this column binds to. + /// public PropertyInfo? PropertyInfo { get; private set; } /// @@ -46,14 +47,14 @@ public class PropertyColumn : ColumnBase, IBindable /// [Parameter] public IComparer? Comparer { get; set; } = null; + /// + /// Gets or sets the sorting behavior for this column. + /// [Parameter] - public override IGridSort? SortBy - { - get => _customSortBy ?? _sortBuilder; - set => _customSortBy = value; - } + public override IGridSort? SortBy { get; set; } /// +#pragma warning disable IL2072 // Target parameter argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The return value of the source method does not have matching annotations. protected override void OnParametersSet() { // We have to do a bit of pre-processing on the lambda expression. Only do that if it's new or changed. @@ -85,17 +86,16 @@ protected override void OnParametersSet() if (typeof(TProp).IsEnum || typeof(TProp).IsNullableEnum()) { - return (value as Enum)?.GetDisplayName(); - } - else - { - return value?.ToString(); + return (value as Enum)?.GetDisplay(); } + + return value?.ToString(); }; } + if (Sortable.HasValue) { - _sortBuilder = Comparer is not null ? GridSort.ByAscending(Property, Comparer) : GridSort.ByAscending(Property); + SortBy = Comparer is not null ? GridSort.ByAscending(Property, Comparer) : GridSort.ByAscending(Property); } } @@ -117,14 +117,17 @@ protected override void OnParametersSet() } } } +#pragma warning restore IL2072 // Target parameter argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The return value of the source method does not have matching annotations. /// protected internal override void CellContent(RenderTreeBuilder builder, TGridItem item) => builder.AddContent(0, _cellTextFunc?.Invoke(item)); + /// protected internal override string? RawCellContent(TGridItem item) => _cellTooltipTextFunc?.Invoke(item); + /// protected override bool IsSortableByDefault() - => _customSortBy is not null; + => SortBy is not null; } diff --git a/src/Core/Components/DataGrid/Columns/SelectColumn.cs b/src/Core/Components/DataGrid/Columns/SelectColumn.cs index 5ab898a0ce..f5f1e4bff1 100644 --- a/src/Core/Components/DataGrid/Columns/SelectColumn.cs +++ b/src/Core/Components/DataGrid/Columns/SelectColumn.cs @@ -17,16 +17,13 @@ public class SelectColumn : ColumnBase /// /// List of keys to press, to select/unselect a row. /// - public static string[] KEYBOARD_SELECT_KEYS = ["Enter", "NumpadEnter"]; + public static readonly string[] KEYBOARD_SELECT_KEYS = ["Enter", "NumpadEnter"]; - private readonly Icon IconUnselectedMultiple = new CoreIcons.Regular.Size20.CheckboxUnchecked().WithColor(Color.FillInverse); + private readonly Icon IconUnselectedMultiple = new CoreIcons.Regular.Size20.CheckboxUnchecked().WithColor(Color.Lightweight); private readonly Icon IconSelectedMultiple = new CoreIcons.Filled.Size20.CheckboxChecked(); - private readonly Icon IconUnselectedSingle = new CoreIcons.Regular.Size20.RadioButton().WithColor(Color.FillInverse); + private readonly Icon IconUnselectedSingle = new CoreIcons.Regular.Size20.RadioButton().WithColor(Color.Lightweight); private readonly Icon IconSelectedSingle = new CoreIcons.Filled.Size20.RadioButton(); - private DataGridSelectMode _selectMode = DataGridSelectMode.Single; - private readonly List _selectedItems = []; - /// /// Initializes a new instance of . /// @@ -65,19 +62,7 @@ public SelectColumn() /// Gets or sets the list of selected items. /// [Parameter] - public IEnumerable SelectedItems - { - get => _selectedItems; - set - { - if (_selectedItems != value) - { - _selectedItems.Clear(); - _selectedItems.AddRange(value); - SelectAll = false; - } - } - } + public IList SelectedItems { get; set; } = []; /// /// Gets or sets a callback when list of selected items changed. @@ -89,21 +74,7 @@ public IEnumerable SelectedItems /// Gets or sets the selection mode (Single, SingleSticky or Multiple). /// [Parameter] - public DataGridSelectMode SelectMode - { - get => _selectMode; - set - { - _selectMode = value; - - if (value is DataGridSelectMode.Single or DataGridSelectMode.SingleSticky) - { - KeepOnlyFirstSelectedItemAsync().Wait(); - } - - RefreshHeaderContent(); - } - } + public DataGridSelectMode SelectMode { get; set; } = DataGridSelectMode.Single; /// /// Gets or sets the Icon to be rendered when the row is non selected. @@ -202,7 +173,7 @@ public DataGridSelectMode SelectMode /// public void ClearSelection() { - _selectedItems.Clear(); + SelectedItems?.Clear(); RefreshHeaderContent(); } @@ -284,14 +255,14 @@ private async Task AddOrRemoveSelectedItemAsync(TGridItem? item) { if (item != null && (Selectable == null || Selectable.Invoke(item))) { - if (SelectMode is DataGridSelectMode.SingleSticky && _selectedItems.Contains(item)) + if (SelectMode is DataGridSelectMode.SingleSticky && SelectedItems.Contains(item)) { return; } if (SelectedItems.Contains(item)) { - _selectedItems.Remove(item); + SelectedItems.Remove(item); SelectAll = false; await CallOnSelectAsync(item, false); } @@ -299,14 +270,15 @@ private async Task AddOrRemoveSelectedItemAsync(TGridItem? item) { if (SelectMode is DataGridSelectMode.Single or DataGridSelectMode.SingleSticky) { - foreach (var previous in _selectedItems) + foreach (var previous in SelectedItems) { await CallOnSelectAsync(previous, false); } - _selectedItems.Clear(); + + SelectedItems.Clear(); } - _selectedItems.Add(item); + SelectedItems.Add(item); await CallOnSelectAsync(item, true); } @@ -337,46 +309,44 @@ private Icon GetIcon(bool? selected) _ => IconSelectedMultiple }; } - else - { - return IconUnchecked ?? SelectMode switch - { - DataGridSelectMode.Single => IconUnselectedSingle, - DataGridSelectMode.SingleSticky => IconUnselectedSingle, - _ => IconUnselectedMultiple - }; - } - } - private async Task KeepOnlyFirstSelectedItemAsync() - { - if (_selectedItems.Count <= 1) + return IconUnchecked ?? SelectMode switch { - return; - } - - // Unselect all except the first - foreach (var item in _selectedItems.Skip(1)) - { - await OnSelect.InvokeAsync((item, false)); - } - - // Keep the first selected item - _selectedItems.RemoveRange(1, _selectedItems.Count - 1); - - if (SelectedItemsChanged.HasDelegate) - { - await SelectedItemsChanged.InvokeAsync(_selectedItems); - } - - // Indeterminate - SelectAll = null; - if (SelectAllChanged.HasDelegate) - { - await SelectAllChanged.InvokeAsync(SelectAll); - } + DataGridSelectMode.Single => IconUnselectedSingle, + DataGridSelectMode.SingleSticky => IconUnselectedSingle, + _ => IconUnselectedMultiple + }; } + //private async Task KeepOnlyFirstSelectedItemAsync() + //{ + // if (SelectedItems.Count <= 1) + // { + // return; + // } + + // // Unselect all except the first + // foreach (var item in SelectedItems.Skip(1)) + // { + // await OnSelect.InvokeAsync((item, false)); + // } + + // // Keep the first selected item + // SelectedItems.RemoveRange(1, SelectedItems.Count - 1); + + // if (SelectedItemsChanged.HasDelegate) + // { + // await SelectedItemsChanged.InvokeAsync(SelectedItems); + // } + + // // Indeterminate + // SelectAll = null; + // if (SelectAllChanged.HasDelegate) + // { + // await SelectAllChanged.InvokeAsync(SelectAll); + // } + //} + /// private RenderFragment GetDefaultChildContent() { @@ -387,17 +357,17 @@ private RenderFragment GetDefaultChildContent() return; } - var selected = _selectedItems.Contains(item) || Property.Invoke(item); + var selected = SelectedItems.Contains(item) || Property.Invoke(item); // Sync with SelectedItems list - if (selected && !_selectedItems.Contains(item)) + if (selected && !SelectedItems.Contains(item)) { - _selectedItems.Add(item); + SelectedItems.Add(item); RefreshHeaderContent(); } - else if (!selected && _selectedItems.Contains(item)) + else if (!selected && SelectedItems.Contains(item)) { - _selectedItems.Remove(item); + SelectedItems.Remove(item); } builder.OpenComponent>(0); @@ -409,6 +379,7 @@ private RenderFragment GetDefaultChildContent() { builder.AddAttribute(4, "style", "cursor: pointer;"); } + builder.CloseComponent(); }); } @@ -440,6 +411,7 @@ private RenderFragment GetHeaderContent() builder.AddAttribute(3, "OnClick", EventCallback.Factory.Create(this, OnClickAllAsync)); builder.AddAttribute(4, "onkeydown", EventCallback.Factory.Create(this, OnKeyAllAsync)); } + builder.AddAttribute(5, "Title", iconAllChecked == IconIndeterminate ? TitleAllIndeterminate : (iconAllChecked == GetIcon(true) ? TitleAllChecked : TitleAllUnchecked)); @@ -469,6 +441,7 @@ private void RefreshHeaderContent() builder.AddAttribute(2, "onclick", EventCallback.Factory.Create(this, OnClickAllAsync)); builder.AddAttribute(3, "onkeydown", EventCallback.Factory.Create(this, OnKeyAllAsync)); } + builder.AddContent(4, SelectAllTemplate.Invoke(new SelectAllTemplateArgs(GetSelectAll()))); builder.CloseElement(); }); @@ -485,19 +458,16 @@ private void RefreshHeaderContent() { return false; } - else if (SelectedItems.Count() == InternalGridContext.TotalItemCount || SelectAll == true) + + if (SelectedItems.Count == InternalGridContext.TotalItemCount || SelectAll == true) { return true; } - else - { - return null; - } - } - else - { + return null; } + + return null; } /// @@ -528,13 +498,13 @@ internal async Task OnClickAllAsync(MouseEventArgs e) await SelectAllChanged.InvokeAsync(SelectAll); } - var count = SelectedItems.Count(); + var count = SelectedItems.Count; // SelectedItems - _selectedItems.Clear(); + SelectedItems.Clear(); if (SelectAll == true && count != InternalGridContext.TotalItemCount) { // Only add selectable items - _selectedItems.AddRange((InternalGridContext.Grid.Items?.ToList() ?? InternalGridContext.Items) + SelectedItems.Concat((InternalGridContext.Grid.Items?.ToList() ?? InternalGridContext.Items) .Where(item => Selectable?.Invoke(item) ?? true) ); } @@ -550,11 +520,17 @@ internal async Task OnClickAllAsync(MouseEventArgs e) /// internal async Task OnKeyAllAsync(KeyboardEventArgs e) { - if (KEYBOARD_SELECT_KEYS.Contains(e.Code)) + if (KEYBOARD_SELECT_KEYS.Contains(e.Code, StringComparer.OrdinalIgnoreCase)) { await OnClickAllAsync(new MouseEventArgs()); } } } +/// +/// Represents the arguments for the SelectAll template. +/// +/// +#pragma warning disable MA0048 // File name must match type name public record SelectAllTemplateArgs(bool? AllSelected) { } +#pragma warning restore MA0048 // File name must match type name diff --git a/src/Core/Components/DataGrid/Columns/TemplateColumn.cs b/src/Core/Components/DataGrid/Columns/TemplateColumn.cs index 8a3b43e954..667060b41b 100644 --- a/src/Core/Components/DataGrid/Columns/TemplateColumn.cs +++ b/src/Core/Components/DataGrid/Columns/TemplateColumn.cs @@ -1,3 +1,7 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; @@ -23,6 +27,7 @@ public class TemplateColumn : ColumnBase protected internal override void CellContent(RenderTreeBuilder builder, TGridItem item) => builder.AddContent(0, ChildContent(item)); + /// protected internal override string? RawCellContent(TGridItem item) { return TooltipText?.Invoke(item); diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor b/src/Core/Components/DataGrid/FluentDataGrid.razor index 071d862b77..edffbe0d82 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor @@ -26,10 +26,10 @@ @onclosecolumnoptions="CloseColumnOptions" @onclosecolumnresize="CloseColumnResize" @attributes="AdditionalAttributes"> - @if (GenerateHeader != GenerateHeaderOption.None) + @if (GenerateHeader != DataGridGeneratedHeaderType.None) { DataGridRowType headerType = DataGridRowType.Header; - if (GenerateHeader == GenerateHeaderOption.Sticky) + if (GenerateHeader == DataGridGeneratedHeaderType.Sticky) { headerType = DataGridRowType.StickyHeader; } @@ -81,7 +81,7 @@ @code { private void RenderNonVirtualizedRows(RenderTreeBuilder __builder) { - var initialRowIndex = (GenerateHeader != GenerateHeaderOption.None) ? 2 : 1; // aria-rowindex is 1-based, plus 1 if there is a header + var initialRowIndex = (GenerateHeader != DataGridGeneratedHeaderType.None) ? 2 : 1; // aria-rowindex is 1-based, plus 1 if there is a header var rowIndex = initialRowIndex; if (_internalGridContext.Items.Any()) { @@ -181,7 +181,7 @@ @if (@col.ColumnOptions is not null) { - + } } @col.ColumnOptions @@ -250,7 +250,7 @@ @if (LoadingContent is null) { -
Loading...
+
Loading...
} else diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index ce8f4f5b57..658f1bd558 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -2,12 +2,11 @@ // MIT License - Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------------------ -using System.Diagnostics.CodeAnalysis; +using System.Globalization; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web.Virtualization; using Microsoft.Extensions.DependencyInjection; using Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; -using Microsoft.FluentUI.AspNetCore.Components.Extensions; using Microsoft.FluentUI.AspNetCore.Components.Infrastructure; using Microsoft.FluentUI.AspNetCore.Components.Utilities; using Microsoft.JSInterop; @@ -21,14 +20,10 @@ namespace Microsoft.FluentUI.AspNetCore.Components; [CascadingTypeParameter(nameof(TGridItem))] public partial class FluentDataGrid : FluentComponentBase, IHandleEvent, IAsyncDisposable { - private const string JAVASCRIPT_FILE = "./_content/Microsoft.FluentUI.AspNetCore.Components/Components/DataGrid/FluentDataGrid.razor.js"; - public const string EMPTY_CONTENT_ROW_CLASS = "empty-content-row"; - public const string LOADING_CONTENT_ROW_CLASS = "loading-content-row"; - public List _menuReferences = []; - - /// - [Inject] - private LibraryConfiguration LibraryConfiguration { get; set; } = default!; + private const string JAVASCRIPT_FILE = FluentJSModule.JAVASCRIPT_ROOT + "DataGrid/FluentDataGrid.razor.js"; + internal const string EMPTY_CONTENT_ROW_CLASS = "empty-content-row"; + internal const string LOADING_CONTENT_ROW_CLASS = "loading-content-row"; + //public List _menuReferences = []; [Inject] private NavigationManager NavigationManager { get; set; } = default!; @@ -36,9 +31,6 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve [Inject] private IServiceScopeFactory ScopeFactory { get; set; } = default!; - [Inject] - private IJSRuntime JSRuntime { get; set; } = default!; - [Inject] private IKeyCodeService KeyCodeService { get; set; } = default!; @@ -189,10 +181,10 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve /// /// Gets or sets a value indicating whether the grid should automatically generate a header row and its type. - /// See + /// See /// [Parameter] - public GenerateHeaderOption? GenerateHeader { get; set; } = GenerateHeaderOption.Default; + public DataGridGeneratedHeaderType? GenerateHeader { get; set; } = DataGridGeneratedHeaderType.Default; /// /// Gets or sets the value that gets applied to the css gridTemplateColumns attribute of child rows. @@ -383,13 +375,14 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve // If the PaginationState mutates, it raises this event. We use it to trigger a re-render. private readonly EventCallbackSubscriber _currentPageItemsChanged; + /// + /// Indicates whether the grid is currently sorted ascending. + /// public bool? SortByAscending => _sortByAscending; /// /// Constructs an instance of . /// - [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(DataGridCellFocusEventArgs))] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(DataGridRowFocusEventArgs))] public FluentDataGrid() { Id = Identifier.NewId(); @@ -420,7 +413,7 @@ protected override void OnInitialized() } /// - protected override Task OnParametersSetAsync() + protected override async Task OnParametersSetAsync() { // The associated pagination state may have been added/removed/replaced _currentPageItemsChanged.SubscribeOrMove(Pagination?.CurrentPageItemsChanged); @@ -442,7 +435,11 @@ protected override Task OnParametersSetAsync() var dataSourceHasChanged = itemsChanged || !Equals(ItemsProvider, _lastAssignedItemsProvider); if (dataSourceHasChanged) { - _scope?.Dispose(); + if (_scope.HasValue) + { + await _scope.Value.DisposeAsync(); + } + _scope = ScopeFactory.CreateAsyncScope(); _lastAssignedItemsProvider = ItemsProvider; _lastAssignedItems = Items; @@ -459,15 +456,17 @@ protected override Task OnParametersSetAsync() // We don't want to trigger the first data load until we've collected the initial set of columns, // because they might perform some action like setting the default sort order, so it would be wasteful // to have to re-query immediately - return (_columns.Count > 0 && mustRefreshData) ? RefreshDataCoreAsync() : Task.CompletedTask; + _ = (_columns.Count > 0 && mustRefreshData) ? RefreshDataCoreAsync() : Task.CompletedTask; } + /// protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender && _gridReference is not null) { - Element = _gridReference.Value; - Module ??= await JSRuntime.InvokeAsync("import", JAVASCRIPT_FILE.FormatCollocatedUrl(LibraryConfiguration)); + // Import the JavaScript module + Module ??= await JSModule.ImportJavaScriptModuleAsync(JAVASCRIPT_FILE); + try { _jsEventDisposable = await Module.InvokeAsync("init", _gridReference, AutoFocus); @@ -507,7 +506,7 @@ internal void AddColumn(ColumnBase column, DataGridSortDirection? ini { if (_collectingColumns) { - column.Index = _columns.Count + 1; + column.SetColumnIndex(_columns.Count + 1); _columns.Add(column); if (isDefaultSortColumn && _sortByColumn is null && initialSortDirection.HasValue) @@ -532,7 +531,7 @@ private void FinishCollectingColumns() if (!string.IsNullOrWhiteSpace(GridTemplateColumns) && _columns.Where(x => x is not SelectColumn).Any(x => !string.IsNullOrWhiteSpace(x.Width))) { - throw new Exception("You can use either the 'GridTemplateColumns' parameter on the grid or the 'Width' property at the column level, not both."); + throw new ArgumentException("You can use either the 'GridTemplateColumns' parameter on the grid or the 'Width' property at the column level, not both."); } // Always re-evaluate after collecting columns when using displaymode grid. A column might be added or hidden and the _internalGridTemplateColumns needs to reflect that. @@ -616,6 +615,7 @@ public Task RemoveSortByColumnAsync(ColumnBase column) StateHasChanged(); // We want to see the updated sort order in the header, even before the data query is completed return RefreshDataCoreAsync(); } + return Task.CompletedTask; } @@ -708,7 +708,10 @@ public Task ShowColumnResizeAsync(int index) { return (index >= 0 && index < _columns.Count) ? ShowColumnResizeAsync(_columns[index]) : Task.CompletedTask; } - + /// + /// Sets the grid's loading state to the specified value. + /// + /// public void SetLoadingState(bool? loading) { Loading = loading; @@ -730,7 +733,7 @@ public async Task RefreshDataAsync(bool force = false) private async Task RefreshDataCoreAsync() { // Move into a "loading" state, cancelling any earlier-but-still-pending load - _pendingDataLoadCancellationTokenSource?.Cancel(); + _pendingDataLoadCancellationTokenSource?.CancelAsync(); var thisLoadCts = _pendingDataLoadCancellationTokenSource = new CancellationTokenSource(); if (_virtualizeComponent is not null) @@ -770,8 +773,10 @@ private async Task RefreshDataCoreAsync() { Pagination?.SetTotalItemCountAsync(_internalGridContext.TotalItemCount); } + _pendingDataLoadCancellationTokenSource = null; } + _internalGridContext.ResetRowIndexes(startIndex); StateHasChanged(); @@ -819,6 +824,7 @@ private async Task RefreshDataCoreAsync() { Pagination?.SetTotalItemCountAsync(_internalGridContext.TotalItemCount); } + if (_internalGridContext.TotalItemCount > 0 && Loading is null) { Loading = false; @@ -832,6 +838,7 @@ private async Task RefreshDataCoreAsync() items: providerResult.Items.Select((x, i) => ValueTuple.Create(i + request.StartIndex + 2, x)), totalItemCount: _internalGridContext.TotalViewItemCount); } + return default; } @@ -848,9 +855,11 @@ private async ValueTask> ResolveItemsRequestA Loading = false; StateHasChanged(); } + return gipr; } - else if (Items is not null) + + if (Items is not null) { var totalItemCount = _asyncQueryExecutor is null ? Items.Count() : await _asyncQueryExecutor.CountAsync(Items, request.CancellationToken); _internalGridContext.TotalItemCount = totalItemCount; @@ -867,6 +876,7 @@ private async ValueTask> ResolveItemsRequestA { result = Items; } + var resultArray = _asyncQueryExecutor is null ? [.. result] : await _asyncQueryExecutor.ToArrayAsync(result, request.CancellationToken); return GridItemsProviderResult.From(resultArray, totalItemCount); } @@ -875,6 +885,7 @@ private async ValueTask> ResolveItemsRequestA { // No-op; we canceled the operation, so it's fine to suppress this exception. } + return GridItemsProviderResult.From(Array.Empty(), 0); } @@ -887,8 +898,8 @@ private string AriaSortValue(ColumnBase column) .AddStyle("grid-template-columns", _internalGridTemplateColumns, !string.IsNullOrWhiteSpace(_internalGridTemplateColumns) && DisplayMode == DataGridDisplayMode.Grid) .AddStyle("grid-template-rows", "auto 1fr", (_internalGridContext.Items.Count == 0 || Items is null || EffectiveLoadingValue) && DisplayMode == DataGridDisplayMode.Grid) .AddStyle("height", "100%", _internalGridContext.TotalItemCount == 0 || EffectiveLoadingValue) - .AddStyle("border-collapse", "separate", GenerateHeader == GenerateHeaderOption.Sticky) - .AddStyle("border-spacing", "0", GenerateHeader == GenerateHeaderOption.Sticky) + .AddStyle("border-collapse", "separate", GenerateHeader == DataGridGeneratedHeaderType.Sticky) + .AddStyle("border-spacing", "0", GenerateHeader == DataGridGeneratedHeaderType.Sticky) .AddStyle("width", "100%", DisplayMode == DataGridDisplayMode.Table) .Build(); @@ -914,14 +925,14 @@ private string AriaSortValue(ColumnBase column) private static string? ColumnJustifyClass(ColumnBase column) { return new CssBuilder(column.Class) - .AddClass("col-justify-start", column.Align == Align.Start) - .AddClass("col-justify-center", column.Align == Align.Center) - .AddClass("col-justify-end", column.Align == Align.End) + .AddClass("col-justify-start", column.Align == HorizontalAlignment.Start) + .AddClass("col-justify-center", column.Align == HorizontalAlignment.Center) + .AddClass("col-justify-end", column.Align == HorizontalAlignment.End) .Build(); } /// - public async ValueTask DisposeAsync() + public override async ValueTask DisposeAsync() { _currentPageItemsChanged.Dispose(); _scope?.Dispose(); @@ -967,27 +978,27 @@ private void LoadStateFromQueryString(string queryString) } var query = System.Web.HttpUtility.ParseQueryString(queryString); - if (query.AllKeys.Contains($"{SaveStatePrefix}orderby")) + if (query.AllKeys.Contains($"{SaveStatePrefix}orderby", StringComparer.Ordinal)) { var orderBy = query[$"{SaveStatePrefix}orderby"]!.Split(' ', 2); var title = orderBy[0]; - var column = _columns.FirstOrDefault(c => c.Title == title); + var column = _columns.FirstOrDefault(c => string.Equals(c.Title, title, StringComparison.Ordinal)); if (column is not null) { _sortByColumn = column; - _sortByAscending = orderBy.Length == 2 && orderBy[1] == "asc"; + _sortByAscending = orderBy.Length == 2 && string.Equals(orderBy[1], "asc", StringComparison.Ordinal); } } if (Pagination is not null) { - if (query.AllKeys.Contains($"{SaveStatePrefix}page") && int.TryParse(query[$"{SaveStatePrefix}page"]!, out var page)) + if (query.AllKeys.Contains($"{SaveStatePrefix}page", StringComparer.Ordinal) && int.TryParse(query[$"{SaveStatePrefix}page"]!, NumberStyles.Integer, CultureInfo.InvariantCulture, out var page)) { - Pagination.SetCurrentPageIndexAsync(page - 1); + _ = Pagination.SetCurrentPageIndexAsync(page - 1); } - if (query.AllKeys.Contains($"{SaveStatePrefix}top") && int.TryParse(query[$"{SaveStatePrefix}top"]!, out var itemsPerPage)) + if (query.AllKeys.Contains($"{SaveStatePrefix}top", StringComparer.Ordinal) && int.TryParse(query[$"{SaveStatePrefix}top"]!, NumberStyles.Integer, CultureInfo.InvariantCulture, out var itemsPerPage)) { Pagination.ItemsPerPage = itemsPerPage; } @@ -1001,12 +1012,13 @@ private void SaveStateToQueryString() return; } - var stateParams = new Dictionary(); + var stateParams = new Dictionary(StringComparer.Ordinal); if (_sortByColumn is not null) { var order = _sortByAscending ? "asc" : "desc"; stateParams.Add($"{SaveStatePrefix}orderby", $"{_sortByColumn.Title} {order}"); } + stateParams.Add($"{SaveStatePrefix}page", Pagination?.CurrentPageIndex + 1 ?? null); stateParams.Add($"{SaveStatePrefix}top", Pagination?.ItemsPerPage ?? null); NavigationManager.NavigateTo(NavigationManager.GetUriWithQueryParameters(stateParams), replace: true); @@ -1048,6 +1060,11 @@ public async Task UpdateItemsPerPageAsync(int visibleRows) // _dotNetObjectReference = DotNetObjectReference.Create(page); //} + /// + /// Checks if key pressed should be handled + /// + /// + /// public async Task OnKeyDownAsync(FluentKeyCodeEventArgs args) { if (args.ShiftKey == true && args.Key == KeyCode.KeyR) @@ -1055,16 +1072,15 @@ public async Task OnKeyDownAsync(FluentKeyCodeEventArgs args) await ResetColumnWidthsAsync(); } - if (args.Value == "-") + if (string.Equals(args.Value, "-", StringComparison.Ordinal)) { await SetColumnWidthDiscreteAsync(null, -10); } - if (args.Value == "+") + + if (string.Equals(args.Value, "+", StringComparison.Ordinal)) { - // Resize column up await SetColumnWidthDiscreteAsync(null, 10); } - //return Task.CompletedTask; } /// @@ -1118,6 +1134,7 @@ private static int ComputeItemsHash(IEnumerable? items, int maxItems { return 0; } + unchecked { var hash = 19; @@ -1128,8 +1145,10 @@ private static int ComputeItemsHash(IEnumerable? items, int maxItems { break; } + hash = (hash * 31) + (item?.GetHashCode() ?? 0); } + return hash; } } diff --git a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs index b6344436b8..85fc83145b 100644 --- a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs @@ -2,6 +2,7 @@ // MIT License - Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------------------ +using System.Globalization; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; using Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; @@ -9,6 +10,9 @@ namespace Microsoft.FluentUI.AspNetCore.Components; +/// +/// Represents a cell in a . +/// public partial class FluentDataGridCell : FluentComponentBase { internal string CellId { get; set; } = string.Empty; @@ -60,6 +64,7 @@ public partial class FluentDataGridCell : FluentComponentBase /// protected FluentDataGrid Grid => InternalGridContext.Grid; + /// protected string? ClassValue => new CssBuilder(Class) .AddClass("column-header", when: CellType == DataGridCellType.ColumnHeader) .AddClass("select-all", when: CellType == DataGridCellType.ColumnHeader && Column is SelectColumn) @@ -67,22 +72,24 @@ public partial class FluentDataGridCell : FluentComponentBase .AddClass(Owner.Class) .Build(); + /// protected string? StyleValue => new StyleBuilder(Style) - .AddStyle("grid-column", GridColumn.ToString(), () => !Grid.EffectiveLoadingValue && (Grid.Items is not null || Grid.ItemsProvider is not null) && Grid.DisplayMode == DataGridDisplayMode.Grid) - .AddStyle("text-align", "center", Column is SelectColumn) - .AddStyle("align-content", "center", Column is SelectColumn) - .AddStyle("padding-inline-start", "calc(((var(--design-unit)* 3) + var(--focus-stroke-width) - var(--stroke-width))* 1px)", Column is SelectColumn && Owner.RowType == DataGridRowType.Default) - .AddStyle("padding-top", "calc(var(--design-unit) * 2.5px)", Column is SelectColumn && (Grid.RowSize == DataGridRowSize.Medium || Owner.RowType == DataGridRowType.Header)) - .AddStyle("padding-top", "calc(var(--design-unit) * 1.5px)", Column is SelectColumn && Grid.RowSize == DataGridRowSize.Small && Owner.RowType == DataGridRowType.Default) - .AddStyle("width", Column?.Width, !string.IsNullOrEmpty(Column?.Width) && Grid.DisplayMode == DataGridDisplayMode.Table) - .AddStyle("height", $"{Grid.ItemSize:0}px", () => !Grid.EffectiveLoadingValue && Grid.Virtualize) - .AddStyle("height", $"{(int)Grid.RowSize}px", () => !Grid.EffectiveLoadingValue && !Grid.Virtualize && !Grid.MultiLine && (Grid.Items is not null || Grid.ItemsProvider is not null)) - .AddStyle("height", "100%", Grid.MultiLine) - .AddStyle("min-height", "44px", Owner.RowType != DataGridRowType.Default) - .AddStyle("z-index", ZIndex.DataGridHeaderPopup.ToString(), CellType == DataGridCellType.ColumnHeader && Grid._columns.Count > 0 && Grid.UseMenuService) - .AddStyle(Owner.Style) - .Build(); + .AddStyle("grid-column", GridColumn.ToString(CultureInfo.InvariantCulture), () => !Grid.EffectiveLoadingValue && (Grid.Items is not null || Grid.ItemsProvider is not null) && Grid.DisplayMode == DataGridDisplayMode.Grid) + .AddStyle("text-align", "center", Column is SelectColumn) + .AddStyle("align-content", "center", Column is SelectColumn) + .AddStyle("padding-inline-start", "calc(((var(--design-unit)* 3) + var(--focus-stroke-width) - var(--stroke-width))* 1px)", Column is SelectColumn && Owner.RowType == DataGridRowType.Default) + .AddStyle("padding-top", "calc(var(--design-unit) * 2.5px)", Column is SelectColumn && (Grid.RowSize == DataGridRowSize.Medium || Owner.RowType == DataGridRowType.Header)) + .AddStyle("padding-top", "calc(var(--design-unit) * 1.5px)", Column is SelectColumn && Grid.RowSize == DataGridRowSize.Small && Owner.RowType == DataGridRowType.Default) + .AddStyle("width", Column?.Width, !string.IsNullOrEmpty(Column?.Width) && Grid.DisplayMode == DataGridDisplayMode.Table) + .AddStyle("height", $"{Grid.ItemSize:0}px", () => !Grid.EffectiveLoadingValue && Grid.Virtualize) + .AddStyle("height", $"{(int)Grid.RowSize}px", () => !Grid.EffectiveLoadingValue && !Grid.Virtualize && !Grid.MultiLine && (Grid.Items is not null || Grid.ItemsProvider is not null)) + .AddStyle("height", "100%", Grid.MultiLine) + .AddStyle("min-height", "44px", Owner.RowType != DataGridRowType.Default) + .AddStyle("z-index", ZIndex.DataGridHeaderPopup.ToString(CultureInfo.InvariantCulture), CellType == DataGridCellType.ColumnHeader && Grid._columns.Count > 0 && Grid.UseMenuService) + .AddStyle(Owner.Style) + .Build(); + /// protected override void OnInitialized() { Owner.Register(this); @@ -112,7 +119,7 @@ internal async Task HandleOnCellFocusAsync() internal async Task HandleOnCellKeyDownAsync(KeyboardEventArgs e) { - if (!SelectColumn.KEYBOARD_SELECT_KEYS.Contains(e.Code)) + if (!SelectColumn.KEYBOARD_SELECT_KEYS.Contains(e.Code, StringComparer.OrdinalIgnoreCase)) { return; } @@ -123,6 +130,6 @@ internal async Task HandleOnCellKeyDownAsync(KeyboardEventArgs e) } } + /// public void Dispose() => Owner.Unregister(this); - } diff --git a/src/Core/Components/DataGrid/FluentDataGridRow.razor b/src/Core/Components/DataGrid/FluentDataGridRow.razor index 7233361c82..c8ee41ca60 100644 --- a/src/Core/Components/DataGrid/FluentDataGridRow.razor +++ b/src/Core/Components/DataGrid/FluentDataGridRow.razor @@ -1,4 +1,5 @@ @namespace Microsoft.FluentUI.AspNetCore.Components +@using Microsoft.FluentUI.AspNetCore.Components.Extensions @inherits FluentComponentBase @typeparam TGridItem @attribute [CascadingTypeParameter(nameof(TGridItem))] diff --git a/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs b/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs index f3d3a0af59..dbda1a59b9 100644 --- a/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs @@ -9,6 +9,14 @@ namespace Microsoft.FluentUI.AspNetCore.Components; +/// +/// Represents a row in a component, providing functionality for rendering, +/// interaction, and data binding. +/// +/// This class is used internally by the to manage rows and their +/// associated data. It provides support for row-specific behaviors such as focus, click, and keyboard interactions, as +/// well as managing the layout and content of the row. +/// The type of the data item associated with the row. [CascadingTypeParameter(nameof(TGridItem))] public partial class FluentDataGridRow : FluentComponentBase, IHandleEvent, IDisposable { @@ -38,8 +46,11 @@ public partial class FluentDataGridRow : FluentComponentBase, IHandle /// Gets or sets the type of row. See . /// [Parameter] - public DataGridRowType? RowType { get; set; } = DataGridRowType.Default; + public DataGridRowType RowType { get; set; } = DataGridRowType.Default; + /// + /// Gets or sets the vertical alignment of a row + /// [Parameter] public VerticalAlignment VerticalAlignment { get; set; } = VerticalAlignment.Center; @@ -49,6 +60,9 @@ public partial class FluentDataGridRow : FluentComponentBase, IHandle [Parameter] public RenderFragment? ChildContent { get; set; } + /// + /// Gets or sets a callback for when a cell is focused. + /// [Parameter] public EventCallback> OnCellFocus { get; set; } @@ -63,22 +77,32 @@ public partial class FluentDataGridRow : FluentComponentBase, IHandle /// protected FluentDataGrid Grid => InternalGridContext.Grid; + /// protected string? ClassValue => new CssBuilder(Class) .AddClass("fluent-data-grid-row") .AddClass("hover", when: Grid.ShowHover) .Build(); + /// protected string? StyleValue => new StyleBuilder(Style) - //.AddStyle("height", $"{Grid.ItemSize:0}px", () => Grid.Virtualize && RowType == DataGridRowType.Default) - //.AddStyle("height", "100%", () => (!Grid.Virtualize || InternalGridContext.Rows.Count == 0) && Grid.Loading && RowType == DataGridRowType.Default) .Build(); + /// protected override void OnInitialized() { RowId = $"r{InternalGridContext.GetNextRowId()}"; InternalGridContext.Register(this); } + /// + /// Sets the RowIndex for this row. + /// + public void SetRowIndex(int rowIndex) + { + RowIndex = rowIndex; + } + + /// public void Dispose() => InternalGridContext.Unregister(this); internal void Register(FluentDataGridCell cell) @@ -106,7 +130,9 @@ internal async Task HandleOnRowClickAsync(string rowId) { var row = GetRow(rowId); - if (row is not null && !string.IsNullOrWhiteSpace(row.Class) && (row.Class.Contains(FluentDataGrid.EMPTY_CONTENT_ROW_CLASS) || row.Class.Contains(FluentDataGrid.LOADING_CONTENT_ROW_CLASS))) + if (row is not null && !string.IsNullOrWhiteSpace(row.Class) && + (row.Class.Contains(FluentDataGrid.EMPTY_CONTENT_ROW_CLASS, StringComparison.Ordinal) || + row.Class.Contains(FluentDataGrid.LOADING_CONTENT_ROW_CLASS, StringComparison.Ordinal))) { return; } @@ -138,7 +164,7 @@ internal async Task HandleOnRowDoubleClickAsync(string rowId) /// internal async Task HandleOnRowKeyDownAsync(string rowId, KeyboardEventArgs e) { - if (!SelectColumn.KEYBOARD_SELECT_KEYS.Contains(e.Code)) + if (!SelectColumn.KEYBOARD_SELECT_KEYS.Contains(e.Code, StringComparer.Ordinal)) { return; } diff --git a/src/Core/Components/DataGrid/GridItemsProvider.cs b/src/Core/Components/DataGrid/GridItemsProvider.cs index 09bb754e6e..cd866ffa54 100644 --- a/src/Core/Components/DataGrid/GridItemsProvider.cs +++ b/src/Core/Components/DataGrid/GridItemsProvider.cs @@ -1,3 +1,7 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + namespace Microsoft.FluentUI.AspNetCore.Components; /// diff --git a/src/Core/Components/DataGrid/GridItemsProviderRequest.cs b/src/Core/Components/DataGrid/GridItemsProviderRequest.cs index 3ca5ccbb3e..c03ac236af 100644 --- a/src/Core/Components/DataGrid/GridItemsProviderRequest.cs +++ b/src/Core/Components/DataGrid/GridItemsProviderRequest.cs @@ -1,3 +1,7 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + namespace Microsoft.FluentUI.AspNetCore.Components; /// @@ -63,6 +67,12 @@ public IQueryable ApplySorting(IQueryable source) => public IReadOnlyCollection GetSortByProperties() => SortByColumn?.SortBy?.ToPropertyList(SortByAscending) ?? Array.Empty(); + /// + /// Determines whether the specified request is equivalent to the current request. + /// + /// The to compare with the current request. + /// if the specified request has the same start index, count, sort column, and sort order as + /// the current request; otherwise, . public bool IsSameRequest(GridItemsProviderRequest req) { if (StartIndex != req.StartIndex) diff --git a/src/Core/Components/DataGrid/GridItemsProviderResult.cs b/src/Core/Components/DataGrid/GridItemsProviderResult.cs index 42b8214b4f..36887bd92d 100644 --- a/src/Core/Components/DataGrid/GridItemsProviderResult.cs +++ b/src/Core/Components/DataGrid/GridItemsProviderResult.cs @@ -1,3 +1,7 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + namespace Microsoft.FluentUI.AspNetCore.Components; /// diff --git a/src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs b/src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs index 331f75700d..63aadb36fd 100644 --- a/src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs +++ b/src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs @@ -1,3 +1,7 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.DependencyInjection; diff --git a/src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs b/src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs index 161892eb90..b35f29ecea 100644 --- a/src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs +++ b/src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs @@ -1,3 +1,7 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + using System.ComponentModel; using Microsoft.AspNetCore.Components; @@ -52,9 +56,7 @@ public Task SetParametersAsync(ParameterView parameters) parameters.SetParameterProperties(this); return InternalGridContext.ColumnsFirstCollected.InvokeCallbacksAsync(null); } - else - { - return Task.CompletedTask; - } + + return Task.CompletedTask; } } diff --git a/src/Core/Components/DataGrid/Infrastructure/Defer.cs b/src/Core/Components/DataGrid/Infrastructure/Defer.cs index 245482be6c..9cf7d6027b 100644 --- a/src/Core/Components/DataGrid/Infrastructure/Defer.cs +++ b/src/Core/Components/DataGrid/Infrastructure/Defer.cs @@ -1,3 +1,7 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + using System.ComponentModel; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; diff --git a/src/Core/Components/DataGrid/Infrastructure/DisplayAttributeExtensions.cs b/src/Core/Components/DataGrid/Infrastructure/DisplayAttributeExtensions.cs deleted file mode 100644 index 0d69a8e0f3..0000000000 --- a/src/Core/Components/DataGrid/Infrastructure/DisplayAttributeExtensions.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.Diagnostics.CodeAnalysis; -using System.Reflection; - -namespace Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; -internal static class DisplayAttributeExtensions -{ - - public static string? GetDisplayAttributeString([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] this Type itemType, string propertyName) - { - PropertyInfo? propertyInfo = itemType.GetProperty(propertyName); - //if (PropertyInfo == null && typeof(ICustomTypeProvider).IsAssignableFrom(itemType)) - // PropertyInfo = ((ICustomTypeProvider)Item).GetCustomType().GetProperty(PropertyName); - if (propertyInfo == null) - { - return null; - } - - var displayAttribute = propertyInfo.GetCustomAttributes(typeof(DisplayAttribute), true).FirstOrDefault() as DisplayAttribute; - if (displayAttribute is not null) - { - return displayAttribute.GetName(); - } - else - { - if (itemType.GetCustomAttribute(typeof(MetadataTypeAttribute)) is MetadataTypeAttribute metadata) - { - return metadata.MetadataClassType.GetDisplayAttributeString(propertyName); - } - } - return null; - } - -} diff --git a/src/Core/Components/DataGrid/Infrastructure/IAsyncQueryExecutor.cs b/src/Core/Components/DataGrid/Infrastructure/IAsyncQueryExecutor.cs index 5e6f17d7fb..1fd73c1346 100644 --- a/src/Core/Components/DataGrid/Infrastructure/IAsyncQueryExecutor.cs +++ b/src/Core/Components/DataGrid/Infrastructure/IAsyncQueryExecutor.cs @@ -1,3 +1,7 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + namespace Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; /// diff --git a/src/Core/Components/DataGrid/Infrastructure/IBindableColumn.cs b/src/Core/Components/DataGrid/Infrastructure/IBindableColumn.cs index 8978d1ce2a..a5ac7603f9 100644 --- a/src/Core/Components/DataGrid/Infrastructure/IBindableColumn.cs +++ b/src/Core/Components/DataGrid/Infrastructure/IBindableColumn.cs @@ -1,3 +1,7 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + using System.Linq.Expressions; using System.Reflection; @@ -8,6 +12,9 @@ namespace Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; /// public interface IBindableColumn { + /// + /// The info for the property that this column binds to. + /// PropertyInfo? PropertyInfo { get; } } /// @@ -19,5 +26,4 @@ internal interface IBindableColumn : IBindableColumn { public Expression> Property { get; set; } - } diff --git a/src/Core/Components/DataGrid/Infrastructure/InternalGridContext.cs b/src/Core/Components/DataGrid/Infrastructure/InternalGridContext.cs index 832b68f59f..24e2806ef7 100644 --- a/src/Core/Components/DataGrid/Infrastructure/InternalGridContext.cs +++ b/src/Core/Components/DataGrid/Infrastructure/InternalGridContext.cs @@ -1,3 +1,7 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + using Microsoft.FluentUI.AspNetCore.Components.Infrastructure; namespace Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; @@ -6,9 +10,9 @@ namespace Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; // so that it doesn't show up by mistake in unrelated components. internal sealed class InternalGridContext { - private int _index = 0; - private int _rowId = 0; - private int _cellId = 0; + private int _index; + private int _rowId; + private int _cellId; public (ColumnBase? Column, DataGridSortDirection? Direction) DefaultSortColumn { get; set; } //public SortDirection? DefaultSortDirection { get; set; } @@ -49,7 +53,7 @@ internal void Register(FluentDataGridRow row) Rows.Add(row.RowId, row); if (!Grid.Virtualize) { - row.RowIndex = _index++; + row.SetRowIndex(_index++); } } diff --git a/src/Core/Components/Icons/CoreIcons.cs b/src/Core/Components/Icons/CoreIcons.cs index 7bb2829294..0ed69ac7a9 100644 --- a/src/Core/Components/Icons/CoreIcons.cs +++ b/src/Core/Components/Icons/CoreIcons.cs @@ -55,6 +55,10 @@ public class ArrowSortDown : Icon { public ArrowSortDown() : base("ArrowSortDown public class ArrowSortUp : Icon { public ArrowSortUp() : base("ArrowSortUp", IconVariant.Regular, IconSize.Size20, "") { } } + public class CheckboxUnchecked : Icon { public CheckboxUnchecked() : base("CheckboxUnchecked", IconVariant.Regular, IconSize.Size20, "") { } } + + public class Checkmark : Icon { public Checkmark() : base("Checkmark", IconVariant.Regular, IconSize.Size24, "") { } } + public class ChevronDown : Icon { public ChevronDown() : base("ChevronDown", IconVariant.Regular, IconSize.Size20, "") { } } public class ChevronDoubleLeft : Icon { public ChevronDoubleLeft() : base("ChevronDoubleLeft", IconVariant.Regular, IconSize.Size20, "") { } } @@ -71,6 +75,8 @@ public class LineHorizontal3 : Icon { public LineHorizontal3() : base("LineHoriz public class QuestionCircle : Icon { public QuestionCircle() : base("QuestionCircle", IconVariant.Regular, IconSize.Size20, "") { } }; + public class RadioButton : Icon { public RadioButton() : base("RadioButton", IconVariant.Regular, IconSize.Size20, "") { } }; + public class Subtract : Icon { public Subtract() : base("Subtract", IconVariant.Regular, IconSize.Size20, "") { } } public class TableResizeColumn : Icon { public TableResizeColumn() : base("TableResizeColumn", IconVariant.Regular, IconSize.Size20, "") { } } @@ -91,16 +97,17 @@ public class ChevronRight : Icon { public ChevronRight() : base("ChevronRight", /// internal static partial class Filled { - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static partial class Size20 { public class CheckmarkCircle : Icon { public CheckmarkCircle() : base("CheckmarkCircle", IconVariant.Filled, IconSize.Size20, "") { } } - + public class DismissCircle : Icon { public DismissCircle() : base("DismissCircle", IconVariant.Filled, IconSize.Size20, "") { } } public class Info : Icon { public Info() : base("Info", IconVariant.Filled, IconSize.Size20, "") { } } - public class Warning : Icon { public Warning() : base("Warning", IconVariant.Filled, IconSize.Size20, "") { } } - - public class DismissCircle : Icon { public DismissCircle() : base("DismissCircle", IconVariant.Filled, IconSize.Size20, "") { } } + public class CheckboxChecked : Icon { public CheckboxChecked() : base("CheckboxChecked", IconVariant.Filled, IconSize.Size20, "") { } } + public class CheckboxIndeterminate : Icon { public CheckboxIndeterminate() : base("CheckboxIndeterminate", IconVariant.Filled, IconSize.Size20, "") { } } + public class RadioButton : Icon { public RadioButton() : base("RadioButton", IconVariant.Regular, IconSize.Size20, "") { } }; + public class Star : Icon { public Star() : base("Star", IconVariant.Filled, IconSize.Size20, "") { } }; } } } diff --git a/src/Core/Events/EventHandlers.cs b/src/Core/Events/EventHandlers.cs index c9727c574c..cd91c4d6ed 100644 --- a/src/Core/Events/EventHandlers.cs +++ b/src/Core/Events/EventHandlers.cs @@ -35,6 +35,9 @@ namespace Microsoft.FluentUI.AspNetCore.Components; [EventHandler("ondropdownchange", typeof(DropdownEventArgs), enableStopPropagation: true, enablePreventDefault: true)] [EventHandler("ontreechanged", typeof(TreeItemChangedEventArgs), enableStopPropagation: true, enablePreventDefault: true)] [EventHandler("ontreetoggle", typeof(TreeItemToggleEventArgs), enableStopPropagation: true, enablePreventDefault: true)] +[EventHandler("onclosecolumnoptions", typeof(EventArgs), enableStopPropagation: true, enablePreventDefault: true)] +[EventHandler("onclosecolumnresize", typeof(EventArgs), enableStopPropagation: true, enablePreventDefault: true)] + public static class EventHandlers { } diff --git a/src/Core/Extensions/DisplayAttributeExtensions.cs b/src/Core/Extensions/DisplayAttributeExtensions.cs new file mode 100644 index 0000000000..a1573146a0 --- /dev/null +++ b/src/Core/Extensions/DisplayAttributeExtensions.cs @@ -0,0 +1,43 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; + +namespace Microsoft.FluentUI.AspNetCore.Components.Extensions; + +internal static class DisplayAttributeExtensions +{ + /// + /// Returns the Display attribute of a Type value if present. + /// + /// The type to investigate + /// The name of the property to get the Display attribute for + /// + [SuppressMessage("Trimming", "IL2075:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The return value of the source method does not have matching annotations.", + Justification = "In the context of the Enum, the 'Display' attribute will not be trimmed.")] + public static string? GetDisplayAttributeString([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] this Type itemType, string propertyName) + { + var propertyInfo = itemType.GetProperty(propertyName); + + if (propertyInfo == null) + { + return null; + } + + var displayAttribute = propertyInfo.GetCustomAttributes(typeof(DisplayAttribute), true).FirstOrDefault() as DisplayAttribute; + if (displayAttribute is not null) + { + return displayAttribute.GetName(); + } + + if (itemType.GetCustomAttribute() is MetadataTypeAttribute metadata) + { + return metadata.MetadataClassType.GetDisplayAttributeString(propertyName); + } + + return null; + } +} diff --git a/src/Core/Utilities/ZIndex.cs b/src/Core/Utilities/ZIndex.cs index 1ff87ba0c2..8bd5c1a9a0 100644 --- a/src/Core/Utilities/ZIndex.cs +++ b/src/Core/Utilities/ZIndex.cs @@ -28,4 +28,10 @@ public static class ZIndex /// ZIndex for the component. /// public static int InputFileDropZone { get; set; } = 990; + + /// + /// + /// ZIndex for the popup components in the . + /// + public static int DataGridHeaderPopup { get; set; } = 5; } From b1cf6800d38f3c0d2ce079ae45b7a3d019328c3e Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Thu, 12 Jun 2025 14:07:55 +0200 Subject: [PATCH 05/44] - Add Typical demo and flags - Remove ::deep - Change county codes to align with v4 --- .../DataGrid/Examples/DataGridTypical.razor | 145 + .../Examples/DataGridTypical.razor.css | 14 + .../Components/DataGrid/FluentDataGrid.md | 1 + .../Demo/FluentUI.Demo/FluentUI.Demo.csproj | 8 + .../FluentUI.Demo/wwwroot/flags/README.md | 27 + .../Demo/FluentUI.Demo/wwwroot/flags/ac.svg | 76 + .../Demo/FluentUI.Demo/wwwroot/flags/ad.svg | 150 + .../Demo/FluentUI.Demo/wwwroot/flags/ae.svg | 6 + .../Demo/FluentUI.Demo/wwwroot/flags/af.svg | 81 + .../Demo/FluentUI.Demo/wwwroot/flags/ag.svg | 14 + .../Demo/FluentUI.Demo/wwwroot/flags/ai.svg | 763 ++ .../Demo/FluentUI.Demo/wwwroot/flags/al.svg | 5 + .../Demo/FluentUI.Demo/wwwroot/flags/am.svg | 5 + .../Demo/FluentUI.Demo/wwwroot/flags/ao.svg | 13 + .../Demo/FluentUI.Demo/wwwroot/flags/aq.svg | 5 + .../Demo/FluentUI.Demo/wwwroot/flags/ar.svg | 33 + .../Demo/FluentUI.Demo/wwwroot/flags/as.svg | 80 + .../Demo/FluentUI.Demo/wwwroot/flags/at.svg | 6 + .../Demo/FluentUI.Demo/wwwroot/flags/au.svg | 16 + .../Demo/FluentUI.Demo/wwwroot/flags/aw.svg | 186 + .../Demo/FluentUI.Demo/wwwroot/flags/ax.svg | 18 + .../Demo/FluentUI.Demo/wwwroot/flags/az.svg | 8 + .../Demo/FluentUI.Demo/wwwroot/flags/ba.svg | 12 + .../Demo/FluentUI.Demo/wwwroot/flags/bb.svg | 6 + .../Demo/FluentUI.Demo/wwwroot/flags/bd.svg | 4 + .../Demo/FluentUI.Demo/wwwroot/flags/be.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/bf.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/bg.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/bh.svg | 9 + .../Demo/FluentUI.Demo/wwwroot/flags/bi.svg | 15 + .../Demo/FluentUI.Demo/wwwroot/flags/bj.svg | 14 + .../Demo/FluentUI.Demo/wwwroot/flags/bl.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/bm.svg | 99 + .../Demo/FluentUI.Demo/wwwroot/flags/bn.svg | 36 + .../Demo/FluentUI.Demo/wwwroot/flags/bo.svg | 676 ++ .../Demo/FluentUI.Demo/wwwroot/flags/bq.svg | 5 + .../Demo/FluentUI.Demo/wwwroot/flags/br.svg | 45 + .../Demo/FluentUI.Demo/wwwroot/flags/bs.svg | 13 + .../Demo/FluentUI.Demo/wwwroot/flags/bt.svg | 89 + .../Demo/FluentUI.Demo/wwwroot/flags/bv.svg | 13 + .../Demo/FluentUI.Demo/wwwroot/flags/bw.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/by.svg | 20 + .../Demo/FluentUI.Demo/wwwroot/flags/bz.svg | 145 + .../Demo/FluentUI.Demo/wwwroot/flags/ca.svg | 4 + .../Demo/FluentUI.Demo/wwwroot/flags/cc.svg | 19 + .../Demo/FluentUI.Demo/wwwroot/flags/cd.svg | 5 + .../FluentUI.Demo/wwwroot/flags/cefta.svg | 17 + .../Demo/FluentUI.Demo/wwwroot/flags/cf.svg | 15 + .../Demo/FluentUI.Demo/wwwroot/flags/cg.svg | 12 + .../Demo/FluentUI.Demo/wwwroot/flags/ch.svg | 9 + .../Demo/FluentUI.Demo/wwwroot/flags/ci.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/ck.svg | 9 + .../Demo/FluentUI.Demo/wwwroot/flags/cl.svg | 13 + .../Demo/FluentUI.Demo/wwwroot/flags/cm.svg | 15 + .../Demo/FluentUI.Demo/wwwroot/flags/cn.svg | 11 + .../Demo/FluentUI.Demo/wwwroot/flags/co.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/cp.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/cr.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/cu.svg | 13 + .../Demo/FluentUI.Demo/wwwroot/flags/cv.svg | 13 + .../Demo/FluentUI.Demo/wwwroot/flags/cw.svg | 14 + .../Demo/FluentUI.Demo/wwwroot/flags/cx.svg | 15 + .../Demo/FluentUI.Demo/wwwroot/flags/cy.svg | 6 + .../Demo/FluentUI.Demo/wwwroot/flags/cz.svg | 5 + .../Demo/FluentUI.Demo/wwwroot/flags/de.svg | 5 + .../Demo/FluentUI.Demo/wwwroot/flags/dg.svg | 134 + .../Demo/FluentUI.Demo/wwwroot/flags/dj.svg | 13 + .../Demo/FluentUI.Demo/wwwroot/flags/dk.svg | 5 + .../Demo/FluentUI.Demo/wwwroot/flags/dm.svg | 152 + .../Demo/FluentUI.Demo/wwwroot/flags/do.svg | 6745 +++++++++++++++++ .../Demo/FluentUI.Demo/wwwroot/flags/dz.svg | 5 + .../Demo/FluentUI.Demo/wwwroot/flags/ea.svg | 544 ++ .../Demo/FluentUI.Demo/wwwroot/flags/ec.svg | 138 + .../Demo/FluentUI.Demo/wwwroot/flags/ee.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/eg.svg | 38 + .../Demo/FluentUI.Demo/wwwroot/flags/eh.svg | 16 + .../Demo/FluentUI.Demo/wwwroot/flags/er.svg | 8 + .../FluentUI.Demo/wwwroot/flags/es-ct.svg | 4 + .../FluentUI.Demo/wwwroot/flags/es-ga.svg | 189 + .../Demo/FluentUI.Demo/wwwroot/flags/es.svg | 544 ++ .../Demo/FluentUI.Demo/wwwroot/flags/et.svg | 14 + .../Demo/FluentUI.Demo/wwwroot/flags/eu.svg | 28 + .../Demo/FluentUI.Demo/wwwroot/flags/fi.svg | 5 + .../Demo/FluentUI.Demo/wwwroot/flags/fj.svg | 122 + .../Demo/FluentUI.Demo/wwwroot/flags/fk.svg | 90 + .../Demo/FluentUI.Demo/wwwroot/flags/fm.svg | 11 + .../Demo/FluentUI.Demo/wwwroot/flags/fo.svg | 12 + .../Demo/FluentUI.Demo/wwwroot/flags/fr.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/ga.svg | 7 + .../FluentUI.Demo/wwwroot/flags/gb-eng.svg | 5 + .../FluentUI.Demo/wwwroot/flags/gb-nir.svg | 132 + .../FluentUI.Demo/wwwroot/flags/gb-sct.svg | 4 + .../FluentUI.Demo/wwwroot/flags/gb-wls.svg | 9 + .../Demo/FluentUI.Demo/wwwroot/flags/gb.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/gd.svg | 27 + .../Demo/FluentUI.Demo/wwwroot/flags/ge.svg | 6 + .../Demo/FluentUI.Demo/wwwroot/flags/gf.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/gg.svg | 9 + .../Demo/FluentUI.Demo/wwwroot/flags/gh.svg | 6 + .../Demo/FluentUI.Demo/wwwroot/flags/gi.svg | 32 + .../Demo/FluentUI.Demo/wwwroot/flags/gl.svg | 4 + .../Demo/FluentUI.Demo/wwwroot/flags/gm.svg | 14 + .../Demo/FluentUI.Demo/wwwroot/flags/gn.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/gp.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/gq.svg | 23 + .../Demo/FluentUI.Demo/wwwroot/flags/gr.svg | 16 + .../Demo/FluentUI.Demo/wwwroot/flags/gs.svg | 242 + .../Demo/FluentUI.Demo/wwwroot/flags/gt.svg | 204 + .../Demo/FluentUI.Demo/wwwroot/flags/gu.svg | 31 + .../Demo/FluentUI.Demo/wwwroot/flags/gw.svg | 13 + .../Demo/FluentUI.Demo/wwwroot/flags/gy.svg | 9 + .../Demo/FluentUI.Demo/wwwroot/flags/hk.svg | 30 + .../Demo/FluentUI.Demo/wwwroot/flags/hm.svg | 15 + .../Demo/FluentUI.Demo/wwwroot/flags/hn.svg | 18 + .../Demo/FluentUI.Demo/wwwroot/flags/hr.svg | 58 + .../Demo/FluentUI.Demo/wwwroot/flags/ht.svg | 116 + .../Demo/FluentUI.Demo/wwwroot/flags/hu.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/ic.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/id.svg | 6 + .../Demo/FluentUI.Demo/wwwroot/flags/ie.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/il.svg | 14 + .../Demo/FluentUI.Demo/wwwroot/flags/im.svg | 36 + .../Demo/FluentUI.Demo/wwwroot/flags/in.svg | 25 + .../Demo/FluentUI.Demo/wwwroot/flags/io.svg | 134 + .../Demo/FluentUI.Demo/wwwroot/flags/iq.svg | 10 + .../Demo/FluentUI.Demo/wwwroot/flags/ir.svg | 219 + .../Demo/FluentUI.Demo/wwwroot/flags/is.svg | 12 + .../Demo/FluentUI.Demo/wwwroot/flags/it.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/je.svg | 47 + .../Demo/FluentUI.Demo/wwwroot/flags/jm.svg | 8 + .../Demo/FluentUI.Demo/wwwroot/flags/jo.svg | 16 + .../Demo/FluentUI.Demo/wwwroot/flags/jp.svg | 11 + .../Demo/FluentUI.Demo/wwwroot/flags/ke.svg | 23 + .../Demo/FluentUI.Demo/wwwroot/flags/kg.svg | 15 + .../Demo/FluentUI.Demo/wwwroot/flags/kh.svg | 61 + .../Demo/FluentUI.Demo/wwwroot/flags/ki.svg | 36 + .../Demo/FluentUI.Demo/wwwroot/flags/km.svg | 16 + .../Demo/FluentUI.Demo/wwwroot/flags/kn.svg | 14 + .../Demo/FluentUI.Demo/wwwroot/flags/kp.svg | 15 + .../Demo/FluentUI.Demo/wwwroot/flags/kr.svg | 24 + .../Demo/FluentUI.Demo/wwwroot/flags/kw.svg | 13 + .../Demo/FluentUI.Demo/wwwroot/flags/ky.svg | 225 + .../Demo/FluentUI.Demo/wwwroot/flags/kz.svg | 23 + .../Demo/FluentUI.Demo/wwwroot/flags/la.svg | 12 + .../Demo/FluentUI.Demo/wwwroot/flags/lb.svg | 15 + .../Demo/FluentUI.Demo/wwwroot/flags/lc.svg | 8 + .../Demo/FluentUI.Demo/wwwroot/flags/li.svg | 43 + .../Demo/FluentUI.Demo/wwwroot/flags/lk.svg | 22 + .../Demo/FluentUI.Demo/wwwroot/flags/lr.svg | 14 + .../Demo/FluentUI.Demo/wwwroot/flags/ls.svg | 8 + .../Demo/FluentUI.Demo/wwwroot/flags/lt.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/lu.svg | 5 + .../Demo/FluentUI.Demo/wwwroot/flags/lv.svg | 6 + .../Demo/FluentUI.Demo/wwwroot/flags/ly.svg | 13 + .../Demo/FluentUI.Demo/wwwroot/flags/ma.svg | 4 + .../Demo/FluentUI.Demo/wwwroot/flags/mc.svg | 6 + .../Demo/FluentUI.Demo/wwwroot/flags/md.svg | 70 + .../Demo/FluentUI.Demo/wwwroot/flags/me.svg | 116 + .../Demo/FluentUI.Demo/wwwroot/flags/mf.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/mg.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/mh.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/mk.svg | 5 + .../Demo/FluentUI.Demo/wwwroot/flags/ml.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/mm.svg | 16 + .../Demo/FluentUI.Demo/wwwroot/flags/mn.svg | 13 + .../Demo/FluentUI.Demo/wwwroot/flags/mo.svg | 9 + .../Demo/FluentUI.Demo/wwwroot/flags/mp.svg | 86 + .../Demo/FluentUI.Demo/wwwroot/flags/mq.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/mr.svg | 6 + .../Demo/FluentUI.Demo/wwwroot/flags/ms.svg | 78 + .../Demo/FluentUI.Demo/wwwroot/flags/mt.svg | 49 + .../Demo/FluentUI.Demo/wwwroot/flags/mu.svg | 8 + .../Demo/FluentUI.Demo/wwwroot/flags/mv.svg | 6 + .../Demo/FluentUI.Demo/wwwroot/flags/mw.svg | 10 + .../Demo/FluentUI.Demo/wwwroot/flags/mx.svg | 382 + .../Demo/FluentUI.Demo/wwwroot/flags/my.svg | 11 + .../Demo/FluentUI.Demo/wwwroot/flags/mz.svg | 21 + .../Demo/FluentUI.Demo/wwwroot/flags/na.svg | 16 + .../Demo/FluentUI.Demo/wwwroot/flags/nc.svg | 13 + .../Demo/FluentUI.Demo/wwwroot/flags/ne.svg | 6 + .../Demo/FluentUI.Demo/wwwroot/flags/nf.svg | 9 + .../Demo/FluentUI.Demo/wwwroot/flags/ng.svg | 6 + .../Demo/FluentUI.Demo/wwwroot/flags/ni.svg | 129 + .../Demo/FluentUI.Demo/wwwroot/flags/nl.svg | 5 + .../Demo/FluentUI.Demo/wwwroot/flags/no.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/np.svg | 14 + .../Demo/FluentUI.Demo/wwwroot/flags/nr.svg | 12 + .../Demo/FluentUI.Demo/wwwroot/flags/nu.svg | 10 + .../Demo/FluentUI.Demo/wwwroot/flags/nz.svg | 42 + .../Demo/FluentUI.Demo/wwwroot/flags/om.svg | 115 + .../Demo/FluentUI.Demo/wwwroot/flags/pa.svg | 14 + .../Demo/FluentUI.Demo/wwwroot/flags/pe.svg | 244 + .../Demo/FluentUI.Demo/wwwroot/flags/pf.svg | 19 + .../Demo/FluentUI.Demo/wwwroot/flags/pg.svg | 9 + .../Demo/FluentUI.Demo/wwwroot/flags/ph.svg | 9 + .../Demo/FluentUI.Demo/wwwroot/flags/pk.svg | 15 + .../Demo/FluentUI.Demo/wwwroot/flags/pl.svg | 6 + .../Demo/FluentUI.Demo/wwwroot/flags/pm.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/pn.svg | 97 + .../Demo/FluentUI.Demo/wwwroot/flags/pr.svg | 13 + .../Demo/FluentUI.Demo/wwwroot/flags/ps.svg | 15 + .../Demo/FluentUI.Demo/wwwroot/flags/pt.svg | 57 + .../Demo/FluentUI.Demo/wwwroot/flags/pw.svg | 11 + .../Demo/FluentUI.Demo/wwwroot/flags/py.svg | 157 + .../Demo/FluentUI.Demo/wwwroot/flags/qa.svg | 4 + .../Demo/FluentUI.Demo/wwwroot/flags/re.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/ro.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/roc.svg | 63 + .../Demo/FluentUI.Demo/wwwroot/flags/rs.svg | 292 + .../Demo/FluentUI.Demo/wwwroot/flags/ru.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/rw.svg | 13 + .../Demo/FluentUI.Demo/wwwroot/flags/sa.svg | 26 + .../Demo/FluentUI.Demo/wwwroot/flags/sb.svg | 13 + .../Demo/FluentUI.Demo/wwwroot/flags/sc.svg | 14 + .../Demo/FluentUI.Demo/wwwroot/flags/sd.svg | 13 + .../Demo/FluentUI.Demo/wwwroot/flags/se.svg | 4 + .../Demo/FluentUI.Demo/wwwroot/flags/sg.svg | 13 + .../Demo/FluentUI.Demo/wwwroot/flags/sh.svg | 76 + .../Demo/FluentUI.Demo/wwwroot/flags/si.svg | 18 + .../Demo/FluentUI.Demo/wwwroot/flags/sj.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/sk.svg | 9 + .../Demo/FluentUI.Demo/wwwroot/flags/sl.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/sm.svg | 91 + .../Demo/FluentUI.Demo/wwwroot/flags/sn.svg | 8 + .../Demo/FluentUI.Demo/wwwroot/flags/so.svg | 11 + .../Demo/FluentUI.Demo/wwwroot/flags/sr.svg | 6 + .../Demo/FluentUI.Demo/wwwroot/flags/ss.svg | 8 + .../Demo/FluentUI.Demo/wwwroot/flags/st.svg | 16 + .../Demo/FluentUI.Demo/wwwroot/flags/sv.svg | 594 ++ .../Demo/FluentUI.Demo/wwwroot/flags/sx.svg | 56 + .../Demo/FluentUI.Demo/wwwroot/flags/sy.svg | 6 + .../Demo/FluentUI.Demo/wwwroot/flags/sz.svg | 45 + .../Demo/FluentUI.Demo/wwwroot/flags/ta.svg | 76 + .../Demo/FluentUI.Demo/wwwroot/flags/tc.svg | 52 + .../Demo/FluentUI.Demo/wwwroot/flags/td.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/tf.svg | 15 + .../Demo/FluentUI.Demo/wwwroot/flags/tg.svg | 14 + .../Demo/FluentUI.Demo/wwwroot/flags/th.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/tj.svg | 22 + .../Demo/FluentUI.Demo/wwwroot/flags/tk.svg | 5 + .../Demo/FluentUI.Demo/wwwroot/flags/tl.svg | 13 + .../Demo/FluentUI.Demo/wwwroot/flags/tm.svg | 206 + .../Demo/FluentUI.Demo/wwwroot/flags/tn.svg | 13 + .../Demo/FluentUI.Demo/wwwroot/flags/to.svg | 10 + .../Demo/FluentUI.Demo/wwwroot/flags/tpe.svg | 1 + .../Demo/FluentUI.Demo/wwwroot/flags/tr.svg | 8 + .../Demo/FluentUI.Demo/wwwroot/flags/tt.svg | 5 + .../Demo/FluentUI.Demo/wwwroot/flags/tv.svg | 16 + .../Demo/FluentUI.Demo/wwwroot/flags/tz.svg | 13 + .../Demo/FluentUI.Demo/wwwroot/flags/ua.svg | 6 + .../Demo/FluentUI.Demo/wwwroot/flags/ug.svg | 30 + .../Demo/FluentUI.Demo/wwwroot/flags/um.svg | 15 + .../Demo/FluentUI.Demo/wwwroot/flags/un.svg | 16 + .../Demo/FluentUI.Demo/wwwroot/flags/us.svg | 10 + .../Demo/FluentUI.Demo/wwwroot/flags/uy.svg | 28 + .../Demo/FluentUI.Demo/wwwroot/flags/uz.svg | 30 + .../Demo/FluentUI.Demo/wwwroot/flags/va.svg | 479 ++ .../Demo/FluentUI.Demo/wwwroot/flags/vc.svg | 8 + .../Demo/FluentUI.Demo/wwwroot/flags/ve.svg | 26 + .../Demo/FluentUI.Demo/wwwroot/flags/vg.svg | 127 + .../Demo/FluentUI.Demo/wwwroot/flags/vi.svg | 28 + .../Demo/FluentUI.Demo/wwwroot/flags/vn.svg | 11 + .../Demo/FluentUI.Demo/wwwroot/flags/vu.svg | 18 + .../Demo/FluentUI.Demo/wwwroot/flags/wf.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/ws.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/xk.svg | 16 + .../Demo/FluentUI.Demo/wwwroot/flags/xx.svg | 5 + .../Demo/FluentUI.Demo/wwwroot/flags/ye.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/yt.svg | 7 + .../Demo/FluentUI.Demo/wwwroot/flags/za.svg | 17 + .../Demo/FluentUI.Demo/wwwroot/flags/zm.svg | 27 + .../Demo/FluentUI.Demo/wwwroot/flags/zw.svg | 21 + .../FluentUI.Demo.SampleData/Olympics2024.cs | 182 +- .../DataGrid/FluentDataGrid.razor.css | 6 +- .../DataGrid/FluentDataGridCell.razor.css | 24 +- .../DataGrid/FluentDataGridRow.razor.css | 2 +- 276 files changed, 19155 insertions(+), 107 deletions(-) create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor.css create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/README.md create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ac.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ad.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ae.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/af.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ag.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ai.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/al.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/am.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ao.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/aq.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ar.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/as.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/at.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/au.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/aw.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ax.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/az.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ba.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bb.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bd.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/be.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bf.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bg.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bh.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bi.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bj.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bl.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bm.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bn.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bo.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bq.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/br.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bs.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bt.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bv.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bw.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/by.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bz.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ca.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cc.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cd.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cefta.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cf.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cg.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ch.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ci.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ck.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cl.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cm.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cn.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/co.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cp.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cr.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cu.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cv.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cw.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cx.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cy.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cz.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/de.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/dg.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/dj.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/dk.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/dm.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/do.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/dz.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ea.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ec.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ee.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/eg.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/eh.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/er.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/es-ct.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/es-ga.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/es.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/et.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/eu.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/fi.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/fj.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/fk.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/fm.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/fo.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/fr.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ga.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gb-eng.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gb-nir.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gb-sct.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gb-wls.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gb.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gd.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ge.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gf.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gg.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gh.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gi.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gl.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gm.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gn.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gp.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gq.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gr.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gs.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gt.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gu.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gw.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gy.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/hk.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/hm.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/hn.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/hr.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ht.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/hu.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ic.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/id.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ie.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/il.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/im.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/in.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/io.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/iq.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ir.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/is.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/it.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/je.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/jm.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/jo.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/jp.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ke.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/kg.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/kh.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ki.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/km.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/kn.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/kp.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/kr.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/kw.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ky.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/kz.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/la.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/lb.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/lc.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/li.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/lk.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/lr.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ls.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/lt.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/lu.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/lv.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ly.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ma.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mc.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/md.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/me.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mf.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mg.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mh.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mk.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ml.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mm.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mn.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mo.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mp.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mq.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mr.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ms.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mt.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mu.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mv.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mw.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mx.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/my.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mz.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/na.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/nc.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ne.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/nf.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ng.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ni.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/nl.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/no.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/np.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/nr.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/nu.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/nz.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/om.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/pa.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/pe.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/pf.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/pg.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ph.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/pk.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/pl.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/pm.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/pn.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/pr.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ps.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/pt.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/pw.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/py.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/qa.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/re.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ro.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/roc.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/rs.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ru.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/rw.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sa.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sb.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sc.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sd.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/se.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sg.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sh.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/si.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sj.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sk.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sl.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sm.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sn.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/so.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sr.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ss.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/st.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sv.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sx.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sy.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sz.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ta.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/tc.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/td.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/tf.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/tg.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/th.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/tj.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/tk.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/tl.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/tm.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/tn.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/to.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/tpe.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/tr.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/tt.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/tv.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/tz.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ua.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ug.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/um.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/un.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/us.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/uy.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/uz.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/va.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/vc.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ve.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/vg.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/vi.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/vn.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/vu.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/wf.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ws.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/xk.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/xx.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ye.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/yt.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/za.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/zm.svg create mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/zw.svg diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor new file mode 100644 index 0000000000..153f45fd3c --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor @@ -0,0 +1,145 @@ +@using static FluentUI.Demo.SampleData.Olympics2024 + + +

To test set ResizeType on the DataGrid to either DataGridResizeType.Discrete or DataGridResizeType.Exact

+

Remove the parameter completely to get the original behavior

+ +
+ + + Flag of @(context.Code) + + + + + + + + + + + +
+ + + +

+ + + +
+
+
+
+
+ + + + + + +@code { + FluentDataGrid? grid; + bool _clearItems = false; + IQueryable? items; + PaginationState pagination = new PaginationState { ItemsPerPage = 10 }; + string nameFilter = string.Empty; + int minMedals; + int maxMedals = 130; + + ColumnResizeUISettings resizeLabels = ColumnResizeUISettings.Default with + { + DiscreteLabel = "Width (+/- 10px)", + ResetAriaLabel = "Restore" + }; + + GridSort rankSort = GridSort + .ByDescending(x => x.Medals.Gold) + .ThenDescending(x => x.Medals.Silver) + .ThenDescending(x => x.Medals.Bronze); + + Func rowClass = x => x.Name.StartsWith("A") ? "highlighted" : null; + Func rowStyle = x => x.Name.StartsWith("Au") ? "background-color: var(--highlight-bg)" : null; + + //IQueryable? FilteredItems => items?.Where(x => x.Name.Contains(nameFilter, StringComparison.CurrentCultureIgnoreCase)); + + IQueryable? FilteredItems + { + get + { + var result = items?.Where(c => c.Medals.Total <= maxMedals); + + if (result is not null && !string.IsNullOrEmpty(nameFilter)) + { + result = result.Where(c => c.Name.Contains(nameFilter, StringComparison.CurrentCultureIgnoreCase)); + } + + if (result is not null && minMedals > 0) + { + result = result.Where(c => c.Medals.Total >= minMedals); + } + + return result; + } + } + + protected override void OnInitialized() + { + items = SampleData.Olympics2024.Countries.AsQueryable(); + } + + private void HandleCountryFilter(ChangeEventArgs args) + { + if (args.Value is string value) + { + nameFilter = value; + } + } + + private void HandleClear() + { + if (string.IsNullOrWhiteSpace(nameFilter)) + { + nameFilter = string.Empty; + } + } + + private async Task HandleCloseFilterAsync(KeyboardEventArgs args) + { + if (args.Key == "Escape") + { + nameFilter = string.Empty; + } + if (args.Key == "Enter" && grid is not null) + { + await grid.CloseColumnOptionsAsync(); + } + } + + private async Task ToggleItemsAsync() + { + if (_clearItems) + { + items = null; + } + else + { + items = SampleData.Olympics2024.Countries.AsQueryable(); + } + await Task.CompletedTask; + } +} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor.css b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor.css new file mode 100644 index 0000000000..4ec36fa5e4 --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor.css @@ -0,0 +1,14 @@ +/* Ensure all the flags are the same size, and centered */ +.flag { + height: 1rem; + margin-top: 4px; + border: 1px solid var(--neutral-layer-3); +} +.search-box { + min-width: 250px; + width: 100%; +} + + .search-box fluent-search { + width: 100%; + } diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md index 5ac04a4efc..0b7428ed7f 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md @@ -7,3 +7,4 @@ route: /DataGrid Overview page and examples to follow +{{ DataGridTypical }} diff --git a/examples/Demo/FluentUI.Demo/FluentUI.Demo.csproj b/examples/Demo/FluentUI.Demo/FluentUI.Demo.csproj index 9b9143214c..fdad3bcecf 100644 --- a/examples/Demo/FluentUI.Demo/FluentUI.Demo.csproj +++ b/examples/Demo/FluentUI.Demo/FluentUI.Demo.csproj @@ -8,6 +8,14 @@ true + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/README.md b/examples/Demo/FluentUI.Demo/wwwroot/flags/README.md new file mode 100644 index 0000000000..1dba35c4c9 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/README.md @@ -0,0 +1,27 @@ +These flag SVG files, except roc.svg, come from https://github.com/lipis/flag-icons +License: The MIT License (MIT) + +Copyright (c) 2013 Panayiotis Lipiridis + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +----------- + +roc.svg is from https://commons.wikimedia.org/wiki/File:Russian_Olympic_Committee_flag.svg +License: "Not an object of copyright" as per the above page diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ac.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ac.svg new file mode 100644 index 0000000000..ece63eaf8d --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ac.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ad.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ad.svg new file mode 100644 index 0000000000..726f981b00 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ad.svg @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ae.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ae.svg new file mode 100644 index 0000000000..b7acdbdb36 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ae.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/af.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/af.svg new file mode 100644 index 0000000000..6e755396fe --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/af.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ag.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ag.svg new file mode 100644 index 0000000000..69914138e0 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ag.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ai.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ai.svg new file mode 100644 index 0000000000..4080e86a4c --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ai.svg @@ -0,0 +1,763 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/al.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/al.svg new file mode 100644 index 0000000000..9ec80b808a --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/al.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/am.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/am.svg new file mode 100644 index 0000000000..99fa4dc597 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/am.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ao.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ao.svg new file mode 100644 index 0000000000..4dc39f6aaf --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ao.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/aq.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/aq.svg new file mode 100644 index 0000000000..53840cccb0 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/aq.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ar.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ar.svg new file mode 100644 index 0000000000..c9ed307d6a --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ar.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/as.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/as.svg new file mode 100644 index 0000000000..b5836f5854 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/as.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/at.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/at.svg new file mode 100644 index 0000000000..c28250887f --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/at.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/au.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/au.svg new file mode 100644 index 0000000000..264bfb0008 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/au.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/aw.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/aw.svg new file mode 100644 index 0000000000..32cabd5457 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/aw.svg @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ax.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ax.svg new file mode 100644 index 0000000000..0584d713b5 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ax.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/az.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/az.svg new file mode 100644 index 0000000000..8e56ef53c2 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/az.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ba.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ba.svg new file mode 100644 index 0000000000..fcd18914a8 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ba.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bb.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bb.svg new file mode 100644 index 0000000000..420a68852a --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/bb.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bd.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bd.svg new file mode 100644 index 0000000000..16b794debd --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/bd.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/be.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/be.svg new file mode 100644 index 0000000000..327f28fa2e --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/be.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bf.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bf.svg new file mode 100644 index 0000000000..4713822584 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/bf.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bg.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bg.svg new file mode 100644 index 0000000000..b100dd0dc6 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/bg.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bh.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bh.svg new file mode 100644 index 0000000000..dee203de59 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/bh.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bi.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bi.svg new file mode 100644 index 0000000000..1050838bc8 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/bi.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bj.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bj.svg new file mode 100644 index 0000000000..0846724d17 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/bj.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bl.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bl.svg new file mode 100644 index 0000000000..15803ff9ac --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/bl.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bm.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bm.svg new file mode 100644 index 0000000000..73906f3007 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/bm.svg @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bn.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bn.svg new file mode 100644 index 0000000000..19f15fa56b --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/bn.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bo.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bo.svg new file mode 100644 index 0000000000..bc55bc3a56 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/bo.svg @@ -0,0 +1,676 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bq.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bq.svg new file mode 100644 index 0000000000..0e6bc76e62 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/bq.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/br.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/br.svg new file mode 100644 index 0000000000..354a7013f3 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/br.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bs.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bs.svg new file mode 100644 index 0000000000..513be43ac4 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/bs.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bt.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bt.svg new file mode 100644 index 0000000000..cea6006c19 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/bt.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bv.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bv.svg new file mode 100644 index 0000000000..40e16d9482 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/bv.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bw.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bw.svg new file mode 100644 index 0000000000..a1c8db0af1 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/bw.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/by.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/by.svg new file mode 100644 index 0000000000..8d25ee3c1f --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/by.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bz.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bz.svg new file mode 100644 index 0000000000..fbc6d7cbe1 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/bz.svg @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ca.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ca.svg new file mode 100644 index 0000000000..496f1a1dac --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ca.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cc.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cc.svg new file mode 100644 index 0000000000..c4457dee99 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/cc.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cd.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cd.svg new file mode 100644 index 0000000000..e106ddd532 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/cd.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cefta.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cefta.svg new file mode 100644 index 0000000000..00d1ffeb79 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/cefta.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cf.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cf.svg new file mode 100644 index 0000000000..a6cd3670f2 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/cf.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cg.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cg.svg new file mode 100644 index 0000000000..9128715f61 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/cg.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ch.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ch.svg new file mode 100644 index 0000000000..9abeff4f5c --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ch.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ci.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ci.svg new file mode 100644 index 0000000000..e400f0c1cd --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ci.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ck.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ck.svg new file mode 100644 index 0000000000..5c4684611e --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ck.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cl.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cl.svg new file mode 100644 index 0000000000..01766fefd7 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/cl.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cm.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cm.svg new file mode 100644 index 0000000000..d06f6560ce --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/cm.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cn.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cn.svg new file mode 100644 index 0000000000..3660d8050d --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/cn.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/co.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/co.svg new file mode 100644 index 0000000000..ebd0a0fb2d --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/co.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cp.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cp.svg new file mode 100644 index 0000000000..b3efb07429 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/cp.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cr.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cr.svg new file mode 100644 index 0000000000..5a409eebb2 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/cr.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cu.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cu.svg new file mode 100644 index 0000000000..e8af888ed2 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/cu.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cv.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cv.svg new file mode 100644 index 0000000000..5c251da2a9 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/cv.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cw.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cw.svg new file mode 100644 index 0000000000..3af2bdf3cf --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/cw.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cx.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cx.svg new file mode 100644 index 0000000000..39fa9b070c --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/cx.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cy.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cy.svg new file mode 100644 index 0000000000..b72473ab1c --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/cy.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cz.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cz.svg new file mode 100644 index 0000000000..7913de3895 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/cz.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/de.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/de.svg new file mode 100644 index 0000000000..65c00ecf1f --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/de.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/dg.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/dg.svg new file mode 100644 index 0000000000..15a349da7a --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/dg.svg @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/dj.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/dj.svg new file mode 100644 index 0000000000..ebf2fc66f1 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/dj.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/dk.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/dk.svg new file mode 100644 index 0000000000..563277f81d --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/dk.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/dm.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/dm.svg new file mode 100644 index 0000000000..60457b7961 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/dm.svg @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/do.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/do.svg new file mode 100644 index 0000000000..7ff190b0d0 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/do.svg @@ -0,0 +1,6745 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/dz.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/dz.svg new file mode 100644 index 0000000000..5ff29a74a0 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/dz.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ea.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ea.svg new file mode 100644 index 0000000000..cb3feb059f --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ea.svg @@ -0,0 +1,544 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ec.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ec.svg new file mode 100644 index 0000000000..65b78858a6 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ec.svg @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ee.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ee.svg new file mode 100644 index 0000000000..36ea288ca8 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ee.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/eg.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/eg.svg new file mode 100644 index 0000000000..728538ba33 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/eg.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/eh.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/eh.svg new file mode 100644 index 0000000000..2848b6a4ae --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/eh.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/er.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/er.svg new file mode 100644 index 0000000000..2705295f27 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/er.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/es-ct.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/es-ct.svg new file mode 100644 index 0000000000..4d85911402 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/es-ct.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/es-ga.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/es-ga.svg new file mode 100644 index 0000000000..f571a8940b --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/es-ga.svg @@ -0,0 +1,189 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/es.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/es.svg new file mode 100644 index 0000000000..8060591980 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/es.svg @@ -0,0 +1,544 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/et.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/et.svg new file mode 100644 index 0000000000..a3378fd958 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/et.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/eu.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/eu.svg new file mode 100644 index 0000000000..1bb04ecb64 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/eu.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/fi.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/fi.svg new file mode 100644 index 0000000000..83fa02bacd --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/fi.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/fj.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/fj.svg new file mode 100644 index 0000000000..c1020ffa11 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/fj.svg @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/fk.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/fk.svg new file mode 100644 index 0000000000..c08ccd9413 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/fk.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/fm.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/fm.svg new file mode 100644 index 0000000000..85f4f47ece --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/fm.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/fo.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/fo.svg new file mode 100644 index 0000000000..717ee20b81 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/fo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/fr.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/fr.svg new file mode 100644 index 0000000000..1be61911a0 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/fr.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ga.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ga.svg new file mode 100644 index 0000000000..76edab429c --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ga.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gb-eng.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gb-eng.svg new file mode 100644 index 0000000000..12e3b67d56 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/gb-eng.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gb-nir.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gb-nir.svg new file mode 100644 index 0000000000..4179e8959d --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/gb-nir.svg @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gb-sct.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gb-sct.svg new file mode 100644 index 0000000000..f50cd322ac --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/gb-sct.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gb-wls.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gb-wls.svg new file mode 100644 index 0000000000..6e15fd0158 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/gb-wls.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gb.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gb.svg new file mode 100644 index 0000000000..dbac25eae4 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/gb.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gd.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gd.svg new file mode 100644 index 0000000000..dad1107faa --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/gd.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ge.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ge.svg new file mode 100644 index 0000000000..453898b020 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ge.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gf.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gf.svg new file mode 100644 index 0000000000..f8752d9ef4 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/gf.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gg.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gg.svg new file mode 100644 index 0000000000..e40a8387c1 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/gg.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gh.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gh.svg new file mode 100644 index 0000000000..a6497de880 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/gh.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gi.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gi.svg new file mode 100644 index 0000000000..64a69e8bf1 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/gi.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gl.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gl.svg new file mode 100644 index 0000000000..eb5a52e9e4 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/gl.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gm.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gm.svg new file mode 100644 index 0000000000..8fe9d66920 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/gm.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gn.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gn.svg new file mode 100644 index 0000000000..40d6ad4f03 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/gn.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gp.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gp.svg new file mode 100644 index 0000000000..1b38158882 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/gp.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gq.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gq.svg new file mode 100644 index 0000000000..ba2acf28d9 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/gq.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gr.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gr.svg new file mode 100644 index 0000000000..c74e4dd97c --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/gr.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gs.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gs.svg new file mode 100644 index 0000000000..e6ead0cefa --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/gs.svg @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gt.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gt.svg new file mode 100644 index 0000000000..24c5f3339e --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/gt.svg @@ -0,0 +1,204 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gu.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gu.svg new file mode 100644 index 0000000000..c010ca0f0d --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/gu.svg @@ -0,0 +1,31 @@ + + + + + + + + + + G + U + A + M + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gw.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gw.svg new file mode 100644 index 0000000000..9e0aeebd35 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/gw.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gy.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gy.svg new file mode 100644 index 0000000000..f4d9b8ab2b --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/gy.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/hk.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/hk.svg new file mode 100644 index 0000000000..603ec22496 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/hk.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/hm.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/hm.svg new file mode 100644 index 0000000000..af29c6c015 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/hm.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/hn.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/hn.svg new file mode 100644 index 0000000000..6f9295005f --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/hn.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/hr.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/hr.svg new file mode 100644 index 0000000000..70115ae9f2 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/hr.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ht.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ht.svg new file mode 100644 index 0000000000..9cddb29324 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ht.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/hu.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/hu.svg new file mode 100644 index 0000000000..baddf7f5ea --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/hu.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ic.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ic.svg new file mode 100644 index 0000000000..81e6ee2e13 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/id.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/id.svg new file mode 100644 index 0000000000..6a0a66be85 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/id.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ie.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ie.svg new file mode 100644 index 0000000000..049be14de1 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ie.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/il.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/il.svg new file mode 100644 index 0000000000..41fda79e30 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/il.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/im.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/im.svg new file mode 100644 index 0000000000..3d597a14be --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/im.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/in.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/in.svg new file mode 100644 index 0000000000..53c29b3a96 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/in.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/io.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/io.svg new file mode 100644 index 0000000000..15a349da7a --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/io.svg @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/iq.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/iq.svg new file mode 100644 index 0000000000..6891785379 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/iq.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ir.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ir.svg new file mode 100644 index 0000000000..6d5a2f578a --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ir.svg @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/is.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/is.svg new file mode 100644 index 0000000000..56cc977874 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/is.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/it.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/it.svg new file mode 100644 index 0000000000..20a8bfdcc8 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/it.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/je.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/je.svg new file mode 100644 index 0000000000..cabef52303 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/je.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/jm.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/jm.svg new file mode 100644 index 0000000000..e03a3422a6 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/jm.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/jo.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/jo.svg new file mode 100644 index 0000000000..50802915e4 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/jo.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/jp.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/jp.svg new file mode 100644 index 0000000000..a0a6791159 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/jp.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ke.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ke.svg new file mode 100644 index 0000000000..ad190f53e1 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ke.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/kg.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/kg.svg new file mode 100644 index 0000000000..f8626b6158 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/kg.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/kh.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/kh.svg new file mode 100644 index 0000000000..984e84e5d7 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/kh.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ki.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ki.svg new file mode 100644 index 0000000000..1697ffe8b7 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ki.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/km.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/km.svg new file mode 100644 index 0000000000..56d62c32e8 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/km.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/kn.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/kn.svg new file mode 100644 index 0000000000..01a3a0a2ab --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/kn.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/kp.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/kp.svg new file mode 100644 index 0000000000..94bc8e1ede --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/kp.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/kr.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/kr.svg new file mode 100644 index 0000000000..cd1aa611ac --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/kr.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/kw.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/kw.svg new file mode 100644 index 0000000000..7ff91a8459 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/kw.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ky.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ky.svg new file mode 100644 index 0000000000..3d77716380 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ky.svg @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/kz.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/kz.svg new file mode 100644 index 0000000000..64776c38c3 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/kz.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/la.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/la.svg new file mode 100644 index 0000000000..9723a781ad --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/la.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/lb.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/lb.svg new file mode 100644 index 0000000000..49650ad85c --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/lb.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/lc.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/lc.svg new file mode 100644 index 0000000000..46bbc6cc70 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/lc.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/li.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/li.svg new file mode 100644 index 0000000000..d557d31465 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/li.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/lk.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/lk.svg new file mode 100644 index 0000000000..416c0f07f4 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/lk.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/lr.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/lr.svg new file mode 100644 index 0000000000..a31377f975 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/lr.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ls.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ls.svg new file mode 100644 index 0000000000..e701650283 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ls.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/lt.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/lt.svg new file mode 100644 index 0000000000..90ec5d240e --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/lt.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/lu.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/lu.svg new file mode 100644 index 0000000000..c31d2bfa24 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/lu.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/lv.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/lv.svg new file mode 100644 index 0000000000..6a9e75ec97 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/lv.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ly.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ly.svg new file mode 100644 index 0000000000..14abcb2430 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ly.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ma.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ma.svg new file mode 100644 index 0000000000..7ce56eff70 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ma.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mc.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mc.svg new file mode 100644 index 0000000000..9cb6c9e8a0 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/mc.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/md.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/md.svg new file mode 100644 index 0000000000..a806572c2f --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/md.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/me.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/me.svg new file mode 100644 index 0000000000..b56cce0946 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/me.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mf.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mf.svg new file mode 100644 index 0000000000..0e5ae1127e --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/mf.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mg.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mg.svg new file mode 100644 index 0000000000..5fa2d2440d --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/mg.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mh.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mh.svg new file mode 100644 index 0000000000..46351e541e --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/mh.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mk.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mk.svg new file mode 100644 index 0000000000..4f5cae77ed --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/mk.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ml.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ml.svg new file mode 100644 index 0000000000..6f6b71695c --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ml.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mm.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mm.svg new file mode 100644 index 0000000000..b2590d96d4 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/mm.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mn.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mn.svg new file mode 100644 index 0000000000..c869cf771a --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/mn.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mo.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mo.svg new file mode 100644 index 0000000000..ec8a4e142c --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/mo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mp.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mp.svg new file mode 100644 index 0000000000..6696fdb839 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/mp.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mq.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mq.svg new file mode 100644 index 0000000000..750b396e1d --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/mq.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mr.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mr.svg new file mode 100644 index 0000000000..e9cc291678 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/mr.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ms.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ms.svg new file mode 100644 index 0000000000..2675022f75 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ms.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mt.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mt.svg new file mode 100644 index 0000000000..676e801c5c --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/mt.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mu.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mu.svg new file mode 100644 index 0000000000..82d7a3bec5 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/mu.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mv.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mv.svg new file mode 100644 index 0000000000..10450f9845 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/mv.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mw.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mw.svg new file mode 100644 index 0000000000..113aae543a --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/mw.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mx.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mx.svg new file mode 100644 index 0000000000..421919501d --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/mx.svg @@ -0,0 +1,382 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/my.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/my.svg new file mode 100644 index 0000000000..3cee70723f --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/my.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mz.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mz.svg new file mode 100644 index 0000000000..eb020058bc --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/mz.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/na.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/na.svg new file mode 100644 index 0000000000..799702e8cf --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/na.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/nc.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/nc.svg new file mode 100644 index 0000000000..a3375e4e98 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/nc.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ne.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ne.svg new file mode 100644 index 0000000000..39a82b8277 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ne.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/nf.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/nf.svg new file mode 100644 index 0000000000..ecdb4a3bd1 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/nf.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ng.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ng.svg new file mode 100644 index 0000000000..81eb35f78e --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ng.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ni.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ni.svg new file mode 100644 index 0000000000..79ff9a98e7 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ni.svg @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/nl.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/nl.svg new file mode 100644 index 0000000000..4faaf498e1 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/nl.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/no.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/no.svg new file mode 100644 index 0000000000..a5f2a152a9 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/no.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/np.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/np.svg new file mode 100644 index 0000000000..6d63ee14a2 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/np.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/nr.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/nr.svg new file mode 100644 index 0000000000..e71ddcd8db --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/nr.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/nu.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/nu.svg new file mode 100644 index 0000000000..4067bafff0 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/nu.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/nz.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/nz.svg new file mode 100644 index 0000000000..561745a576 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/nz.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/om.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/om.svg new file mode 100644 index 0000000000..1c76217996 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/om.svg @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/pa.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/pa.svg new file mode 100644 index 0000000000..8dc03bc61b --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/pa.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/pe.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/pe.svg new file mode 100644 index 0000000000..eeb29a321d --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/pe.svg @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/pf.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/pf.svg new file mode 100644 index 0000000000..16374f3629 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/pf.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/pg.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/pg.svg new file mode 100644 index 0000000000..1080add5bf --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/pg.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ph.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ph.svg new file mode 100644 index 0000000000..3a5b5de957 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ph.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/pk.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/pk.svg new file mode 100644 index 0000000000..fa02f6a8fc --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/pk.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/pl.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/pl.svg new file mode 100644 index 0000000000..0fa5145241 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/pl.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/pm.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/pm.svg new file mode 100644 index 0000000000..42bfcee023 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/pm.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/pn.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/pn.svg new file mode 100644 index 0000000000..d584def2fd --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/pn.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/pr.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/pr.svg new file mode 100644 index 0000000000..3cb403b5ca --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/pr.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ps.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ps.svg new file mode 100644 index 0000000000..82031486a8 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ps.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/pt.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/pt.svg new file mode 100644 index 0000000000..afd2e4a3eb --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/pt.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/pw.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/pw.svg new file mode 100644 index 0000000000..089cbceea2 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/pw.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/py.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/py.svg new file mode 100644 index 0000000000..bfbf01f1f9 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/py.svg @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/qa.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/qa.svg new file mode 100644 index 0000000000..bd493c381c --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/qa.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/re.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/re.svg new file mode 100644 index 0000000000..6c56aa41f6 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/re.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ro.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ro.svg new file mode 100644 index 0000000000..fda0f7bec9 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ro.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/roc.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/roc.svg new file mode 100644 index 0000000000..396843bbda --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/roc.svg @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/rs.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/rs.svg new file mode 100644 index 0000000000..ad1a76af33 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/rs.svg @@ -0,0 +1,292 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ru.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ru.svg new file mode 100644 index 0000000000..f4d27efc98 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ru.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/rw.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/rw.svg new file mode 100644 index 0000000000..2c6c5d9035 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/rw.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sa.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sa.svg new file mode 100644 index 0000000000..3018468eb1 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/sa.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sb.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sb.svg new file mode 100644 index 0000000000..a011360d5f --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/sb.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sc.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sc.svg new file mode 100644 index 0000000000..65091a5cc3 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/sc.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sd.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sd.svg new file mode 100644 index 0000000000..b8e4b97357 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/sd.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/se.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/se.svg new file mode 100644 index 0000000000..0e41780ef1 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/se.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sg.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sg.svg new file mode 100644 index 0000000000..c4dd4ac9eb --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/sg.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sh.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sh.svg new file mode 100644 index 0000000000..9bedb0811d --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/sh.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/si.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/si.svg new file mode 100644 index 0000000000..f2aea01689 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/si.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sj.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sj.svg new file mode 100644 index 0000000000..bb2799ce73 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/sj.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sk.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sk.svg new file mode 100644 index 0000000000..a1953fa67f --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/sk.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sl.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sl.svg new file mode 100644 index 0000000000..a07baf75b4 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/sl.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sm.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sm.svg new file mode 100644 index 0000000000..c9357bca6a --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/sm.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sn.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sn.svg new file mode 100644 index 0000000000..7c0673d6d6 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/sn.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/so.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/so.svg new file mode 100644 index 0000000000..ae582f198d --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/so.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sr.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sr.svg new file mode 100644 index 0000000000..5e71c40026 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/sr.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ss.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ss.svg new file mode 100644 index 0000000000..73804d80d5 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ss.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/st.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/st.svg new file mode 100644 index 0000000000..2259f318f9 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/st.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sv.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sv.svg new file mode 100644 index 0000000000..752dd3d499 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/sv.svg @@ -0,0 +1,594 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sx.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sx.svg new file mode 100644 index 0000000000..84844e0f2e --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/sx.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sy.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sy.svg new file mode 100644 index 0000000000..968f915769 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/sy.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sz.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sz.svg new file mode 100644 index 0000000000..f3393e5608 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/sz.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ta.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ta.svg new file mode 100644 index 0000000000..27bc3183d6 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ta.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/tc.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/tc.svg new file mode 100644 index 0000000000..09cce7b2f9 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/tc.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/td.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/td.svg new file mode 100644 index 0000000000..9fadf85a0b --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/td.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/tf.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/tf.svg new file mode 100644 index 0000000000..4572f4ee61 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/tf.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/tg.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/tg.svg new file mode 100644 index 0000000000..e20f40d8db --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/tg.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/th.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/th.svg new file mode 100644 index 0000000000..1e93a61e95 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/th.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/tj.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/tj.svg new file mode 100644 index 0000000000..563c97b630 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/tj.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/tk.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/tk.svg new file mode 100644 index 0000000000..65bab1372f --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/tk.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/tl.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/tl.svg new file mode 100644 index 0000000000..bcfc1612d7 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/tl.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/tm.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/tm.svg new file mode 100644 index 0000000000..871e4eed3b --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/tm.svg @@ -0,0 +1,206 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/tn.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/tn.svg new file mode 100644 index 0000000000..dc6d067c0d --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/tn.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/to.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/to.svg new file mode 100644 index 0000000000..d072337066 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/to.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/tpe.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/tpe.svg new file mode 100644 index 0000000000..3862833a6f --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/tpe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/tr.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/tr.svg new file mode 100644 index 0000000000..a92804f882 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/tr.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/tt.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/tt.svg new file mode 100644 index 0000000000..14adbe041e --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/tt.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/tv.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/tv.svg new file mode 100644 index 0000000000..aed967d292 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/tv.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/tz.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/tz.svg new file mode 100644 index 0000000000..751c167206 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/tz.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ua.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ua.svg new file mode 100644 index 0000000000..789f0166cf --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ua.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ug.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ug.svg new file mode 100644 index 0000000000..78252a42d1 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ug.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/um.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/um.svg new file mode 100644 index 0000000000..5f2822d56e --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/um.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/un.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/un.svg new file mode 100644 index 0000000000..b04c3c43d9 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/un.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/us.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/us.svg new file mode 100644 index 0000000000..3189d8e2dc --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/us.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/uy.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/uy.svg new file mode 100644 index 0000000000..1634d71b70 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/uy.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/uz.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/uz.svg new file mode 100644 index 0000000000..8c6a5324c9 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/uz.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/va.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/va.svg new file mode 100644 index 0000000000..6a03dc4680 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/va.svg @@ -0,0 +1,479 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/vc.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/vc.svg new file mode 100644 index 0000000000..450f6f0a26 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/vc.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ve.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ve.svg new file mode 100644 index 0000000000..77bb549e6d --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ve.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/vg.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/vg.svg new file mode 100644 index 0000000000..f18731d2f8 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/vg.svg @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/vi.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/vi.svg new file mode 100644 index 0000000000..8a0941fa0d --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/vi.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/vn.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/vn.svg new file mode 100644 index 0000000000..04433b989b --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/vn.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/vu.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/vu.svg new file mode 100644 index 0000000000..abd682c775 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/vu.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/wf.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/wf.svg new file mode 100644 index 0000000000..b0cc4c73d0 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/wf.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ws.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ws.svg new file mode 100644 index 0000000000..0e758a7a95 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ws.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/xk.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/xk.svg new file mode 100644 index 0000000000..e6a63325bf --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/xk.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/xx.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/xx.svg new file mode 100644 index 0000000000..24bfd4554c --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/xx.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ye.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ye.svg new file mode 100644 index 0000000000..61f0ed6100 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/ye.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/yt.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/yt.svg new file mode 100644 index 0000000000..e84f439aac --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/yt.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/za.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/za.svg new file mode 100644 index 0000000000..1e0b8b23bb --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/za.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/zm.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/zm.svg new file mode 100644 index 0000000000..b8fdd63cb7 --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/zm.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/zw.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/zw.svg new file mode 100644 index 0000000000..5bfd7dff4a --- /dev/null +++ b/examples/Demo/FluentUI.Demo/wwwroot/flags/zw.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Tools/FluentUI.Demo.SampleData/Olympics2024.cs b/examples/Tools/FluentUI.Demo.SampleData/Olympics2024.cs index 0886a6b710..f06551e1f0 100644 --- a/examples/Tools/FluentUI.Demo.SampleData/Olympics2024.cs +++ b/examples/Tools/FluentUI.Demo.SampleData/Olympics2024.cs @@ -19,97 +19,97 @@ public static IEnumerable Countries { get { - yield return new Country("ALB", "Albania", new Medals(Gold: 0, Silver: 0, Bronze: 2)); - yield return new Country("ALG", "Algeria", new Medals(Gold: 2, Silver: 0, Bronze: 1)); - yield return new Country("ARG", "Argentina", new Medals(Gold: 1, Silver: 1, Bronze: 1)); - yield return new Country("ARM", "Armenia", new Medals(Gold: 0, Silver: 3, Bronze: 1)); - yield return new Country("AUS", "Australia", new Medals(Gold: 18, Silver: 19, Bronze: 16)); - yield return new Country("AUT", "Austria", new Medals(Gold: 2, Silver: 0, Bronze: 3)); - yield return new Country("AZE", "Azerbaijan", new Medals(Gold: 2, Silver: 2, Bronze: 3)); - yield return new Country("BHR", "Bahrain", new Medals(Gold: 2, Silver: 1, Bronze: 1)); - yield return new Country("BEL", "Belgium", new Medals(Gold: 3, Silver: 1, Bronze: 6)); - yield return new Country("BOT", "Botswana", new Medals(Gold: 1, Silver: 1, Bronze: 0)); - yield return new Country("BRZ", "Brazil", new Medals(Gold: 3, Silver: 7, Bronze: 10)); - yield return new Country("BUL", "Bulgaria", new Medals(Gold: 3, Silver: 1, Bronze: 3)); - yield return new Country("CAN", "Canada", new Medals(Gold: 9, Silver: 7, Bronze: 11)); - yield return new Country("CPV", "Cape Verde", new Medals(Gold: 0, Silver: 0, Bronze: 1)); - yield return new Country("CHI", "Chile", new Medals(Gold: 1, Silver: 1, Bronze: 0)); - yield return new Country("CHN", "China", new Medals(Gold: 40, Silver: 27, Bronze: 24)); - yield return new Country("TPE", "Chinese Taipei", new Medals(Gold: 2, Silver: 0, Bronze: 5)); - yield return new Country("COL", "Colombia", new Medals(Gold: 0, Silver: 3, Bronze: 1)); - yield return new Country("CRO", "Croatia", new Medals(Gold: 2, Silver: 2, Bronze: 3)); - yield return new Country("CUB", "Cuba", new Medals(Gold: 2, Silver: 1, Bronze: 6)); - yield return new Country("CYP", "Cyprus", new Medals(Gold: 0, Silver: 1, Bronze: 0)); - yield return new Country("CZE", "Czech Republic", new Medals(Gold: 3, Silver: 0, Bronze: 2)); - yield return new Country("DEN", "Denmark", new Medals(Gold: 2, Silver: 2, Bronze: 5)); - yield return new Country("DMA", "Dominica", new Medals(Gold: 1, Silver: 0, Bronze: 0)); - yield return new Country("DOM", "Dominican Republic", new Medals(Gold: 1, Silver: 0, Bronze: 2)); - yield return new Country("ECU", "Ecuador", new Medals(Gold: 1, Silver: 2, Bronze: 2)); - yield return new Country("EGY", "Egypt", new Medals(Gold: 1, Silver: 1, Bronze: 1)); - yield return new Country("ETH", "Ethiopia", new Medals(Gold: 1, Silver: 3, Bronze: 0)); - yield return new Country("FIJ", "Fiji", new Medals(Gold: 0, Silver: 1, Bronze: 0)); - yield return new Country("FRA", "France", new Medals(Gold: 16, Silver: 26, Bronze: 22)); - yield return new Country("GEO", "Georgia", new Medals(Gold: 3, Silver: 3, Bronze: 1)); - yield return new Country("GER", "Germany", new Medals(Gold: 12, Silver: 13, Bronze: 8)); - yield return new Country("GBG", "Great Britain", new Medals(Gold: 14, Silver: 22, Bronze: 29)); - yield return new Country("GRE", "Greece", new Medals(Gold: 1, Silver: 1, Bronze: 6)); - yield return new Country("GRN", "Grenada", new Medals(Gold: 0, Silver: 0, Bronze: 2)); - yield return new Country("GUA", "Guatemala", new Medals(Gold: 1, Silver: 0, Bronze: 1)); - yield return new Country("HK", "Hong Kong", new Medals(Gold: 2, Silver: 0, Bronze: 2)); - yield return new Country("HUN", "Hungary", new Medals(Gold: 6, Silver: 7, Bronze: 6)); - yield return new Country("IND", "India", new Medals(Gold: 0, Silver: 1, Bronze: 5)); - yield return new Country("IDN", "Indonesia", new Medals(Gold: 2, Silver: 0, Bronze: 1)); - yield return new Country("IRN", "Iran", new Medals(Gold: 3, Silver: 6, Bronze: 3)); - yield return new Country("IRE", "Ireland", new Medals(Gold: 4, Silver: 0, Bronze: 3)); - yield return new Country("ISR", "Israel", new Medals(Gold: 1, Silver: 5, Bronze: 1)); - yield return new Country("ITA", "Italy", new Medals(Gold: 12, Silver: 13, Bronze: 15)); - yield return new Country("CIV", "Ivory Coast", new Medals(Gold: 0, Silver: 0, Bronze: 1)); - yield return new Country("JAM", "Jamaica", new Medals(Gold: 1, Silver: 3, Bronze: 2)); - yield return new Country("JPN", "Japan", new Medals(Gold: 20, Silver: 12, Bronze: 13)); - yield return new Country("JOR", "Jordan", new Medals(Gold: 0, Silver: 1, Bronze: 0)); - yield return new Country("KAZ", "Kazakhstan", new Medals(Gold: 1, Silver: 3, Bronze: 3)); - yield return new Country("KEN", "Kenya", new Medals(Gold: 4, Silver: 2, Bronze: 5)); - yield return new Country("KOS", "Kosovo", new Medals(Gold: 0, Silver: 1, Bronze: 1)); - yield return new Country("KGZ", "Kyrgyzstan", new Medals(Gold: 0, Silver: 2, Bronze: 4)); - yield return new Country("LTU", "Lithuania", new Medals(Gold: 0, Silver: 2, Bronze: 2)); - yield return new Country("MAS", "Malaysia", new Medals(Gold: 0, Silver: 0, Bronze: 2)); - yield return new Country("MEX", "Mexico", new Medals(Gold: 0, Silver: 3, Bronze: 2)); - yield return new Country("MDA", "Moldova", new Medals(Gold: 0, Silver: 1, Bronze: 3)); - yield return new Country("MGL", "Mongolia", new Medals(Gold: 0, Silver: 1, Bronze: 0)); - yield return new Country("MOR", "Morocco", new Medals(Gold: 1, Silver: 0, Bronze: 1)); - yield return new Country("NED", "Netherlands", new Medals(Gold: 15, Silver: 7, Bronze: 12)); - yield return new Country("NZ", "New Zealand", new Medals(Gold: 10, Silver: 7, Bronze: 3)); - yield return new Country("PRK", "North Korea", new Medals(Gold: 0, Silver: 2, Bronze: 4)); - yield return new Country("NOR", "Norway", new Medals(Gold: 4, Silver: 1, Bronze: 3)); - yield return new Country("PKN", "Pakistan", new Medals(Gold: 1, Silver: 0, Bronze: 0)); - yield return new Country("PAN", "Panama", new Medals(Gold: 0, Silver: 1, Bronze: 0)); - yield return new Country("PER", "Peru", new Medals(Gold: 0, Silver: 0, Bronze: 1)); - yield return new Country("PHI", "Philippines", new Medals(Gold: 2, Silver: 0, Bronze: 2)); - yield return new Country("POL", "Poland", new Medals(Gold: 1, Silver: 4, Bronze: 5)); - yield return new Country("POR", "Portugal", new Medals(Gold: 1, Silver: 2, Bronze: 1)); - yield return new Country("PUR", "Puerto Rico", new Medals(Gold: 0, Silver: 0, Bronze: 2)); - yield return new Country("QAT", "Qatar", new Medals(Gold: 0, Silver: 0, Bronze: 1)); - yield return new Country("EOR", "Refugee Olympic Team", new Medals(Gold: 0, Silver: 0, Bronze: 1)); - yield return new Country("ROM", "Romania", new Medals(Gold: 3, Silver: 4, Bronze: 2)); - yield return new Country("SER", "Serbia", new Medals(Gold: 3, Silver: 1, Bronze: 1)); - yield return new Country("SIN", "Singapore", new Medals(Gold: 0, Silver: 0, Bronze: 1)); - yield return new Country("SVK", "Slovakia", new Medals(Gold: 0, Silver: 0, Bronze: 1)); - yield return new Country("SLO", "Slovenia", new Medals(Gold: 2, Silver: 1, Bronze: 0)); - yield return new Country("SA", "South Africa", new Medals(Gold: 1, Silver: 3, Bronze: 2)); - yield return new Country("KOR", "South Korea", new Medals(Gold: 13, Silver: 9, Bronze: 10)); - yield return new Country("SPA", "Spain", new Medals(Gold: 5, Silver: 4, Bronze: 9)); - yield return new Country("LCA", "St Lucia", new Medals(Gold: 1, Silver: 1, Bronze: 0)); - yield return new Country("SWE", "Sweden", new Medals(Gold: 4, Silver: 4, Bronze: 3)); - yield return new Country("SWI", "Switzerland", new Medals(Gold: 1, Silver: 2, Bronze: 5)); - yield return new Country("TJK", "Tajikistan", new Medals(Gold: 0, Silver: 0, Bronze: 3)); - yield return new Country("THA", "Thailand", new Medals(Gold: 1, Silver: 3, Bronze: 2)); - yield return new Country("TUN", "Tunisia", new Medals(Gold: 1, Silver: 1, Bronze: 1)); - yield return new Country("TUR", "Turkey", new Medals(Gold: 0, Silver: 3, Bronze: 5)); - yield return new Country("UGA", "Uganda", new Medals(Gold: 1, Silver: 1, Bronze: 0)); - yield return new Country("UKR", "Ukraine", new Medals(Gold: 3, Silver: 5, Bronze: 4)); - yield return new Country("US", "United States", new Medals(Gold: 40, Silver: 44, Bronze: 42)); - yield return new Country("UZB", "Uzbekistan", new Medals(Gold: 8, Silver: 2, Bronze: 3)); - yield return new Country("ZAM", "Zambia", new Medals(Gold: 0, Silver: 0, Bronze: 1)); + yield return new Country("al", "Albania", new Medals(Gold: 0, Silver: 0, Bronze: 2)); + yield return new Country("dz", "Algeria", new Medals(Gold: 2, Silver: 0, Bronze: 1)); + yield return new Country("ar", "Argentina", new Medals(Gold: 1, Silver: 1, Bronze: 1)); + yield return new Country("am", "Armenia", new Medals(Gold: 0, Silver: 3, Bronze: 1)); + yield return new Country("au", "Australia", new Medals(Gold: 18, Silver: 19, Bronze: 16)); + yield return new Country("at", "Austria", new Medals(Gold: 2, Silver: 0, Bronze: 3)); + yield return new Country("az", "Azerbaijan", new Medals(Gold: 2, Silver: 2, Bronze: 3)); + yield return new Country("be", "Belgium", new Medals(Gold: 3, Silver: 1, Bronze: 6)); + yield return new Country("bh", "Bahrain", new Medals(Gold: 2, Silver: 1, Bronze: 1)); + yield return new Country("bw", "Botswana", new Medals(Gold: 1, Silver: 1, Bronze: 0)); + yield return new Country("br", "Brazil", new Medals(Gold: 3, Silver: 7, Bronze: 10)); + yield return new Country("bg", "Bulgaria", new Medals(Gold: 3, Silver: 1, Bronze: 3)); + yield return new Country("ca", "Canada", new Medals(Gold: 9, Silver: 7, Bronze: 11)); + yield return new Country("cl", "Chile", new Medals(Gold: 1, Silver: 1, Bronze: 0)); + yield return new Country("cn", "People's Republic of China", new Medals(Gold: 40, Silver: 27, Bronze: 24)); + yield return new Country("ci", "Ivory Coast", new Medals(Gold: 0, Silver: 0, Bronze: 1)); + yield return new Country("co", "Colombia", new Medals(Gold: 0, Silver: 3, Bronze: 1)); + yield return new Country("cv", "Cape Verde", new Medals(Gold: 0, Silver: 0, Bronze: 1)); + yield return new Country("hr", "Croatia", new Medals(Gold: 2, Silver: 2, Bronze: 3)); + yield return new Country("cu", "Cuba", new Medals(Gold: 2, Silver: 1, Bronze: 6)); + yield return new Country("cy", "Cyprus", new Medals(Gold: 0, Silver: 1, Bronze: 0)); + yield return new Country("cz", "Czech Republic", new Medals(Gold: 3, Silver: 0, Bronze: 2)); + yield return new Country("dk", "Denmark", new Medals(Gold: 2, Silver: 2, Bronze: 5)); + yield return new Country("dm", "Dominica", new Medals(Gold: 1, Silver: 0, Bronze: 0)); + yield return new Country("do", "Dominican Republic", new Medals(Gold: 1, Silver: 0, Bronze: 2)); + yield return new Country("ec", "Ecuador", new Medals(Gold: 1, Silver: 2, Bronze: 2)); + yield return new Country("eg", "Egypt", new Medals(Gold: 1, Silver: 1, Bronze: 1)); + yield return new Country("xx", "Refugee Olympic Team", new Medals(Gold: 0, Silver: 0, Bronze: 1)); + yield return new Country("et", "Ethiopia", new Medals(Gold: 1, Silver: 3, Bronze: 0)); + yield return new Country("fj", "Fiji", new Medals(Gold: 0, Silver: 1, Bronze: 0)); + yield return new Country("fr", "France", new Medals(Gold: 16, Silver: 26, Bronze: 22)); + yield return new Country("gb", "Great Britain", new Medals(Gold: 14, Silver: 22, Bronze: 29)); + yield return new Country("ge", "Georgia", new Medals(Gold: 3, Silver: 3, Bronze: 1)); + yield return new Country("de", "Germany", new Medals(Gold: 12, Silver: 13, Bronze: 8)); + yield return new Country("gr", "Greece", new Medals(Gold: 1, Silver: 1, Bronze: 6)); + yield return new Country("gd", "Grenada", new Medals(Gold: 0, Silver: 0, Bronze: 2)); + yield return new Country("gt", "Guatemala", new Medals(Gold: 1, Silver: 0, Bronze: 1)); + yield return new Country("hk", "Hong Kong", new Medals(Gold: 2, Silver: 0, Bronze: 2)); + yield return new Country("hu", "Hungary", new Medals(Gold: 6, Silver: 7, Bronze: 6)); + yield return new Country("id", "Indonesia", new Medals(Gold: 2, Silver: 0, Bronze: 1)); + yield return new Country("in", "India", new Medals(Gold: 0, Silver: 1, Bronze: 5)); + yield return new Country("ie", "Ireland", new Medals(Gold: 4, Silver: 0, Bronze: 3)); + yield return new Country("ir", "Iran", new Medals(Gold: 3, Silver: 6, Bronze: 3)); + yield return new Country("il", "Israel", new Medals(Gold: 1, Silver: 5, Bronze: 1)); + yield return new Country("it", "Italy", new Medals(Gold: 12, Silver: 13, Bronze: 15)); + yield return new Country("jm", "Jamaica", new Medals(Gold: 1, Silver: 3, Bronze: 2)); + yield return new Country("jo", "Jordan", new Medals(Gold: 0, Silver: 1, Bronze: 0)); + yield return new Country("jp", "Japan", new Medals(Gold: 20, Silver: 12, Bronze: 13)); + yield return new Country("kz", "Kazakhstan", new Medals(Gold: 1, Silver: 3, Bronze: 3)); + yield return new Country("ke", "Kenya", new Medals(Gold: 4, Silver: 2, Bronze: 5)); + yield return new Country("kg", "Kyrgyzstan", new Medals(Gold: 0, Silver: 2, Bronze: 4)); + yield return new Country("kr", "South Korea", new Medals(Gold: 13, Silver: 9, Bronze: 10)); + yield return new Country("xk", "Kosovo", new Medals(Gold: 0, Silver: 1, Bronze: 1)); + yield return new Country("lc", "St Lucia", new Medals(Gold: 1, Silver: 1, Bronze: 0)); + yield return new Country("lt", "Lithuania", new Medals(Gold: 0, Silver: 2, Bronze: 2)); + yield return new Country("my", "Malaysia", new Medals(Gold: 0, Silver: 0, Bronze: 2)); + yield return new Country("md", "Moldova", new Medals(Gold: 0, Silver: 1, Bronze: 3)); + yield return new Country("mx", "Mexico", new Medals(Gold: 0, Silver: 3, Bronze: 2)); + yield return new Country("mn", "Mongolia", new Medals(Gold: 0, Silver: 1, Bronze: 0)); + yield return new Country("ma", "Morocco", new Medals(Gold: 1, Silver: 0, Bronze: 1)); + yield return new Country("nl", "Netherlands", new Medals(Gold: 15, Silver: 7, Bronze: 12)); + yield return new Country("no", "Norway", new Medals(Gold: 4, Silver: 1, Bronze: 3)); + yield return new Country("nz", "New Zealand", new Medals(Gold: 10, Silver: 7, Bronze: 3)); + yield return new Country("pa", "Panama", new Medals(Gold: 0, Silver: 1, Bronze: 0)); + yield return new Country("pe", "Peru", new Medals(Gold: 0, Silver: 0, Bronze: 1)); + yield return new Country("ph", "Philippines", new Medals(Gold: 2, Silver: 0, Bronze: 2)); + yield return new Country("pk", "Pakistan", new Medals(Gold: 1, Silver: 0, Bronze: 0)); + yield return new Country("pl", "Poland", new Medals(Gold: 1, Silver: 4, Bronze: 5)); + yield return new Country("pt", "Portugal", new Medals(Gold: 1, Silver: 2, Bronze: 1)); + yield return new Country("kp", "North Korea", new Medals(Gold: 0, Silver: 2, Bronze: 4)); + yield return new Country("pr", "Puerto Rico", new Medals(Gold: 0, Silver: 0, Bronze: 2)); + yield return new Country("qa", "Qatar", new Medals(Gold: 0, Silver: 0, Bronze: 1)); + yield return new Country("ro", "Romania", new Medals(Gold: 3, Silver: 4, Bronze: 2)); + yield return new Country("za", "South Africa", new Medals(Gold: 1, Silver: 3, Bronze: 2)); + yield return new Country("rs", "Serbia", new Medals(Gold: 3, Silver: 1, Bronze: 1)); + yield return new Country("sg", "Singapore", new Medals(Gold: 0, Silver: 0, Bronze: 1)); + yield return new Country("si", "Slovenia", new Medals(Gold: 2, Silver: 1, Bronze: 0)); + yield return new Country("es", "Spain", new Medals(Gold: 5, Silver: 4, Bronze: 9)); + yield return new Country("sk", "Slovakia", new Medals(Gold: 0, Silver: 0, Bronze: 1)); + yield return new Country("se", "Sweden", new Medals(Gold: 4, Silver: 4, Bronze: 3)); + yield return new Country("ch", "Switzerland", new Medals(Gold: 1, Silver: 2, Bronze: 5)); + yield return new Country("th", "Thailand", new Medals(Gold: 1, Silver: 3, Bronze: 2)); + yield return new Country("tj", "Tajikistan", new Medals(Gold: 0, Silver: 0, Bronze: 3)); + yield return new Country("tpe", "Chinese Taipei", new Medals(Gold: 2, Silver: 0, Bronze: 5)); + yield return new Country("tn", "Tunisia", new Medals(Gold: 1, Silver: 1, Bronze: 1)); + yield return new Country("tr", "Turkey", new Medals(Gold: 0, Silver: 3, Bronze: 5)); + yield return new Country("ug", "Uganda", new Medals(Gold: 1, Silver: 1, Bronze: 0)); + yield return new Country("ua", "Ukraine", new Medals(Gold: 3, Silver: 5, Bronze: 4)); + yield return new Country("us", "United States of America", new Medals(Gold: 40, Silver: 44, Bronze: 42)); + yield return new Country("uz", "Uzbekistan", new Medals(Gold: 8, Silver: 2, Bronze: 3)); + yield return new Country("zm", "Zambia", new Medals(Gold: 0, Silver: 0, Bronze: 1)); } } diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.css b/src/Core/Components/DataGrid/FluentDataGrid.razor.css index fcd8d0bd5c..eac2360ae8 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.css +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.css @@ -19,7 +19,7 @@ display: contents; } -.grid ::deep tr { +.grid tr { display: contents; } @@ -65,7 +65,7 @@ align-items: center; } -::deep .resize-handle { +.resize-handle { position: absolute; top: 5px; right: 0; @@ -83,7 +83,7 @@ z-index: 3; } -::deep tr[row-type='sticky-header'] > th { +tr[row-type='sticky-header'] > th { position: sticky; top: 0; background-color: var(--neutral-fill-stealth-rest); diff --git a/src/Core/Components/DataGrid/FluentDataGridCell.razor.css b/src/Core/Components/DataGrid/FluentDataGridCell.razor.css index a7de410b1b..2ee3967b2b 100644 --- a/src/Core/Components/DataGrid/FluentDataGridCell.razor.css +++ b/src/Core/Components/DataGrid/FluentDataGridCell.razor.css @@ -53,56 +53,56 @@ td.grid-cell-placeholder:after { padding: calc((var(--design-unit) + var(--focus-stroke-width) - var(--stroke-width)) * 1px) 1px calc((var(--design-unit) + var(--focus-stroke-width) - var(--stroke-width)) * 1px); } -::deep .col-sort-button { +.col-sort-button { width: calc(100% - 20px); overflow: hidden; text-overflow: ellipsis; } - ::deep .col-sort-button::part(content) { + .col-sort-button::part(content) { overflow: hidden; } -::deep .col-options-button { +.col-options-button { padding-inline-start: 4px; } -.col-justify-start ::deep .col-sort-button::part(control) { +.col-justify-start .col-sort-button::part(control) { justify-content: start; overflow: hidden; opacity: 1 } -.col-justify-center ::deep .col-sort-button::part(control) { +.col-justify-center .col-sort-button::part(control) { justify-content: center; overflow: hidden; opacity: 1 } -.col-justify-end ::deep .col-sort-button::part(control) { +.col-justify-end .col-sort-button::part(control) { justify-content: end; overflow: hidden; opacity: 1 } -.col-justify-end ::deep .col-sort-button::part(start) { +.col-justify-end .col-sort-button::part(start) { margin-inline-end: 2px; } -.col-justify-start ::deep .col-sort-button::part(end), -.col-justify-center ::deep .col-sort-button::part(end) { +.col-justify-start .col-sort-button::part(end), +.col-justify-center .col-sort-button::part(end) { margin-inline-start: 2px; } -.col-justify-start ::deep .col-title { +.col-justify-start .col-title { text-align: left; } -.col-justify-center ::deep .col-title { +.col-justify-center .col-title { text-align: center; } -.col-justify-end ::deep .col-title { +.col-justify-end .col-title { text-align: end; } diff --git a/src/Core/Components/DataGrid/FluentDataGridRow.razor.css b/src/Core/Components/DataGrid/FluentDataGridRow.razor.css index 2fa3af2c47..6ed06f3147 100644 --- a/src/Core/Components/DataGrid/FluentDataGridRow.razor.css +++ b/src/Core/Components/DataGrid/FluentDataGridRow.razor.css @@ -2,7 +2,7 @@ z-index: 3; } -.hover:not([row-type='header'],[row-type='sticky-header'],.loading-content-row):hover ::deep td:not(.empty-content-cell) { +.hover:not([row-type='header'],[row-type='sticky-header'],.loading-content-row):hover td:not(.empty-content-cell) { cursor: pointer; background-color: var(--datagrid-hover-color, var(--neutral-fill-stealth-hover)); } From 8393e3532669bf4023dc4526e968d5a8903d76b3 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Thu, 12 Jun 2025 14:26:15 +0200 Subject: [PATCH 06/44] Work on review comments --- src/Core/Components/Icons/CoreIcons.cs | 20 ------- .../Pagination/FluentPaginator.razor.cs | 54 ++++++++++--------- .../Pagination/FluentPaginator.razor.css | 22 ++++---- 3 files changed, 40 insertions(+), 56 deletions(-) diff --git a/src/Core/Components/Icons/CoreIcons.cs b/src/Core/Components/Icons/CoreIcons.cs index 7bb2829294..bd9efd0a0a 100644 --- a/src/Core/Components/Icons/CoreIcons.cs +++ b/src/Core/Components/Icons/CoreIcons.cs @@ -14,29 +14,9 @@ internal static partial class CoreIcons * * Try to use only the Regular.Size20 and Filled.Size20 icons, * to avoid duplicating the same icon in different sizes. - * ************************************************************* */ - internal static partial class Regular - { - // TODO: We might need these for the FluentDataGrid header UI. Size 20 couldbe too big. Will delete if not needed - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static partial class Size16 - { - //public class ArrowSort : Icon { public ArrowSort() : base("ArrowSort", IconVariant.Regular, IconSize.Size16, "") { } } - //public class CheckmarkCircle : Icon { public CheckmarkCircle() : base("CheckmarkCircle", IconVariant.Regular, IconSize.Size16, "") { } } - //public class Dismiss : Icon { public Dismiss() : base("Dismiss", IconVariant.Regular, IconSize.Size16, "") { } } - //public class DismissCircle : Icon { public DismissCircle() : base("DismissCircle", IconVariant.Regular, IconSize.Size16, "") { } } - //public class Filter : Icon { public Filter() : base("Filter", IconVariant.Regular, IconSize.Size16, "") { } } - //public class Info : Icon { public Info() : base("Info", IconVariant.Regular, IconSize.Size16, "") { } } - //public class MoreVertical : Icon { public MoreVertical() : base("MoreVertical", IconVariant.Regular, IconSize.Size16, "") { } } - //public class TableResizeColumn : Icon { public TableResizeColumn() : base("TableResizeColumn", IconVariant.Regular, IconSize.Size16, "") { } } - //public class Warning : Icon { public Warning() : base("Warning", IconVariant.Regular, IconSize.Size16, "") { } } - //public class Search : Icon { public Search() : base("Search", IconVariant.Regular, IconSize.Size16, "") { } } - } - } - /// /// Regular icons /// diff --git a/src/Core/Components/Pagination/FluentPaginator.razor.cs b/src/Core/Components/Pagination/FluentPaginator.razor.cs index ae792e3ca6..6f79e95079 100644 --- a/src/Core/Components/Pagination/FluentPaginator.razor.cs +++ b/src/Core/Components/Pagination/FluentPaginator.razor.cs @@ -15,6 +15,16 @@ public partial class FluentPaginator : FluentComponentBase, IDisposable private readonly EventCallbackSubscriber _totalItemCountChanged; private readonly EventCallbackSubscriber _currentPageItemsChanged; + /// + /// Constructs an instance of . + /// + public FluentPaginator() + { + // The "total item count" handler doesn't need to do anything except cause this component to re-render + _totalItemCountChanged = new(new EventCallback(this, null)); + _currentPageItemsChanged = new(new EventCallback(this, null)); + } + /// /// Gets or sets the callback that is invoked when the current page index changes. /// @@ -33,7 +43,7 @@ public partial class FluentPaginator : FluentComponentBase, IDisposable /// Gets or sets the associated . This parameter is required. ///
[Parameter, EditorRequired] - public PaginationState State { get; set; } = default!; + public required PaginationState State { get; set; } /// /// Optionally supplies a template for rendering the page count summary. @@ -52,23 +62,31 @@ public partial class FluentPaginator : FluentComponentBase, IDisposable [Parameter] public RenderFragment? PaginationTextTemplate { get; set; } - /// - /// Constructs an instance of . - /// - public FluentPaginator() + /// + protected override void OnParametersSet() { - // The "total item count" handler doesn't need to do anything except cause this component to re-render - _totalItemCountChanged = new(new EventCallback(this, null)); - _currentPageItemsChanged = new(new EventCallback(this, null)); + _totalItemCountChanged.SubscribeOrMove(State.TotalItemCountChangedSubscribable); + _currentPageItemsChanged.SubscribeOrMove(State.CurrentPageItemsChanged); + } + + /// + public void Dispose() + { + _totalItemCountChanged.Dispose(); + _currentPageItemsChanged.Dispose(); } + private bool CanGoBack => State.CurrentPageIndex > 0; + + private bool CanGoForwards => State.CurrentPageIndex < State.LastPageIndex; + private Task GoFirstAsync() => GoToPageAsync(0); + private Task GoPreviousAsync() => GoToPageAsync(State.CurrentPageIndex - 1); + private Task GoNextAsync() => GoToPageAsync(State.CurrentPageIndex + 1); - private Task GoLastAsync() => GoToPageAsync(State.LastPageIndex.GetValueOrDefault(0)); - private bool CanGoBack => State.CurrentPageIndex > 0; - private bool CanGoForwards => State.CurrentPageIndex < State.LastPageIndex; + private Task GoLastAsync() => GoToPageAsync(State.LastPageIndex.GetValueOrDefault(0)); private async Task GoToPageAsync(int pageIndex) { @@ -78,18 +96,4 @@ private async Task GoToPageAsync(int pageIndex) await CurrentPageIndexChanged.InvokeAsync(State.CurrentPageIndex); } } - - /// - protected override void OnParametersSet() - { - _totalItemCountChanged.SubscribeOrMove(State.TotalItemCountChangedSubscribable); - _currentPageItemsChanged.SubscribeOrMove(State.CurrentPageItemsChanged); - } - - /// - public void Dispose() - { - _totalItemCountChanged.Dispose(); - _currentPageItemsChanged.Dispose(); - } } diff --git a/src/Core/Components/Pagination/FluentPaginator.razor.css b/src/Core/Components/Pagination/FluentPaginator.razor.css index c0724d89aa..50798b6529 100644 --- a/src/Core/Components/Pagination/FluentPaginator.razor.css +++ b/src/Core/Components/Pagination/FluentPaginator.razor.css @@ -1,4 +1,4 @@ -.paginator { +.fluent-paginator { display: flex; /*border-top: 1px solid var(--neutral-stroke-divider-rest);*/ margin-top: 0.5rem; @@ -6,20 +6,20 @@ align-items: center; } -.pagination-text { - margin: 0 0.5rem; +.fluent-paginator .pagination-text { + margin: 0 0.5rem; } -.paginator-nav { - padding: 0; - display: flex; - margin-inline-start: auto; - margin-inline-end: 0; - gap: 0.5rem; - align-items: center; +.fluent-paginator .paginator-nav { + padding: 0; + display: flex; + margin-inline-start: auto; + margin-inline-end: 0; + gap: 0.5rem; + align-items: center; } -[dir="rtl"] * ::deep fluent-button > svg { +[dir="rtl"] .fluent-paginator fluent-button > svg { transform: rotate(180deg); } From 202c10a186df8542a6205c184e124c5a821e70e3 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Thu, 12 Jun 2025 15:08:46 +0200 Subject: [PATCH 07/44] Process review comments --- spelling.dic | 54 +++++-------------- .../Pagination/FluentPaginator.razor | 34 +++++++----- .../Pagination/FluentPaginator.razor.cs | 6 +-- .../Pagination/FluentPaginator.razor.css | 39 +++++++------- .../Components/Pagination/PaginationState.cs | 14 +++-- src/Core/Enums/DataGridCellType.cs | 5 ++ src/Core/Enums/DataGridDisplayMode.cs | 5 +- src/Core/Enums/DataGridGeneratedHeaderType.cs | 5 +- src/Core/Enums/DataGridResizeType.cs | 3 ++ src/Core/Enums/DataGridRowSize.cs | 3 ++ src/Core/Enums/DataGridRowType.cs | 3 ++ src/Core/Enums/DataGridSelectMode.cs | 3 ++ src/Core/Enums/DataGridSortDirection.cs | 3 ++ .../EventCallbackSubscribable.cs | 7 +-- ...soft.FluentUI.AspNetCore.Components.csproj | 3 ++ 15 files changed, 103 insertions(+), 84 deletions(-) diff --git a/spelling.dic b/spelling.dic index c1e5200f58..40345dad07 100644 --- a/spelling.dic +++ b/spelling.dic @@ -1,19 +1,25 @@ + # *************************** # List of misspelled words # Please, sort the list alphabetically # *************************** - ansi appsettings blazor brotli +columnheader combobox cref csproj currentcolor +currentcolor +datagrid datalist +demopanel +dialogtoggle elementreference evenodd +eventargs gzip henkan heure @@ -21,13 +27,13 @@ heures inputfile javascript jours +keycapture keydown keyup maintenant menuchecked menuclicked menuitem -menuitem menuitemcheckbox menuitemcheckboxobsolete menuitemradio @@ -39,56 +45,24 @@ muhenkan myid noattribute nonfile +onaccordionchange onchange ondialogbeforetoggle -ondialogtoggle ondropdownchange onmenuitemchange ontabchange -ondropdownchange -ondialogtoggle -ondialogbeforetoggle -myid -menuclicked -menuchecked -onaccordionchange -demopanel -dialogtoggle +rendertree rightclick +rowheader rrggbb secondes +sortabillity sourcecode +Subscribable summarydata tabindex tablist tabpanel -textarea -sourcecode -summarydata -currentcolor -menuitem -menuitems -rightclick -menuitemcheckbox -menuitem -menuitemradio -menuitemcheckboxobsolete -menuitemradioobsolete -onmenuitemchange -ontabchange -ondropdownchange -ondialogtoggle -ondialogbeforetoggle -myid -menuclicked -menuchecked -rendertree testid -columnheader -rowheader -datagrid -keycapture +textarea wdelta -sortabillity -Subscribable -eventargs diff --git a/src/Core/Components/Pagination/FluentPaginator.razor b/src/Core/Components/Pagination/FluentPaginator.razor index 975959e8ff..b4e003aa59 100644 --- a/src/Core/Components/Pagination/FluentPaginator.razor +++ b/src/Core/Components/Pagination/FluentPaginator.razor @@ -1,6 +1,6 @@ @namespace Microsoft.FluentUI.AspNetCore.Components @inherits FluentComponentBase -
+
@if (State.TotalItemCount.HasValue) {
@@ -14,12 +14,16 @@ }
}
diff --git a/src/Core/Components/Pagination/FluentPaginator.razor.cs b/src/Core/Components/Pagination/FluentPaginator.razor.cs index 6f79e95079..82d84c462d 100644 --- a/src/Core/Components/Pagination/FluentPaginator.razor.cs +++ b/src/Core/Components/Pagination/FluentPaginator.razor.cs @@ -18,11 +18,11 @@ public partial class FluentPaginator : FluentComponentBase, IDisposable /// /// Constructs an instance of . /// - public FluentPaginator() + public FluentPaginator(LibraryConfiguration configuration) : base(configuration) { // The "total item count" handler doesn't need to do anything except cause this component to re-render - _totalItemCountChanged = new(new EventCallback(this, null)); - _currentPageItemsChanged = new(new EventCallback(this, null)); + _totalItemCountChanged = new(new EventCallback(this, @delegate: null)); + _currentPageItemsChanged = new(new EventCallback(this, @delegate: null)); } /// diff --git a/src/Core/Components/Pagination/FluentPaginator.razor.css b/src/Core/Components/Pagination/FluentPaginator.razor.css index 50798b6529..fed83a6371 100644 --- a/src/Core/Components/Pagination/FluentPaginator.razor.css +++ b/src/Core/Components/Pagination/FluentPaginator.razor.css @@ -1,25 +1,28 @@ .fluent-paginator { - display: flex; - /*border-top: 1px solid var(--neutral-stroke-divider-rest);*/ - margin-top: 0.5rem; - padding: 0.25rem 0; - align-items: center; -} - -.fluent-paginator .pagination-text { - margin: 0 0.5rem; -} - -.fluent-paginator .paginator-nav { - padding: 0; display: flex; - margin-inline-start: auto; - margin-inline-end: 0; - gap: 0.5rem; + /*border-top: 1px solid var(--neutral-stroke-divider-rest);*/ + margin-top: 0.5rem; + padding: 0.25rem 0; align-items: center; } - + .fluent-paginator .summary { + } + + .fluent-paginator .pagination-text { + margin: 0 0.5rem; + } + + .fluent-paginator .paginator-nav { + padding: 0; + display: flex; + margin-inline-start: auto; + margin-inline-end: 0; + gap: 0.5rem; + align-items: center; + } + + [dir="rtl"] .fluent-paginator fluent-button > svg { - transform: rotate(180deg); + transform: rotate(180deg); } diff --git a/src/Core/Components/Pagination/PaginationState.cs b/src/Core/Components/Pagination/PaginationState.cs index 8d80041320..4ad3b195f1 100644 --- a/src/Core/Components/Pagination/PaginationState.cs +++ b/src/Core/Components/Pagination/PaginationState.cs @@ -6,6 +6,8 @@ namespace Microsoft.FluentUI.AspNetCore.Components; +// ToDo: remove pragma after next PR +#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved /// /// Holds state to represent pagination in a . /// @@ -70,8 +72,9 @@ public async Task SetItemsPerPageAsync(int itemsPerPage) await CurrentPageItemsChanged.InvokeCallbacksAsync(this); if (TotalItemCount.HasValue) { - await SetTotalItemCountAsync(TotalItemCount.Value, true); + await SetTotalItemCountAsync(TotalItemCount.Value, force: true); } + return; } @@ -81,11 +84,11 @@ public async Task SetItemsPerPageAsync(int itemsPerPage) /// The total number of items /// If true, the total item count will be updated even if it is the same as the current value. /// - public Task SetTotalItemCountAsync(int totalItemCount, bool force = false) + public async Task SetTotalItemCountAsync(int totalItemCount, bool force = false) { if (totalItemCount == TotalItemCount && !force) { - return Task.CompletedTask; + return; } TotalItemCount = totalItemCount; @@ -94,11 +97,12 @@ public Task SetTotalItemCountAsync(int totalItemCount, bool force = false) { // If the number of items has reduced such that the current page index is no longer valid, move // automatically to the final valid page index and trigger a further data load. - _ = SetCurrentPageIndexAsync(LastPageIndex.Value); + await SetCurrentPageIndexAsync(LastPageIndex.Value); } // Under normal circumstances, we just want any associated pagination UI to update TotalItemCountChanged?.Invoke(this, new TotalItemCountChangedEventArgs(TotalItemCount)); - return TotalItemCountChangedSubscribable.InvokeCallbacksAsync(this); + await TotalItemCountChangedSubscribable.InvokeCallbacksAsync(this); } } +#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved diff --git a/src/Core/Enums/DataGridCellType.cs b/src/Core/Enums/DataGridCellType.cs index 72c20e6884..725c0a49c7 100644 --- a/src/Core/Enums/DataGridCellType.cs +++ b/src/Core/Enums/DataGridCellType.cs @@ -6,6 +6,9 @@ namespace Microsoft.FluentUI.AspNetCore.Components; +// ToDo: remove pragma after next PR +#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved + /// /// The type of in a . /// @@ -28,3 +31,5 @@ public enum DataGridCellType [Description("rowheader")] RowHeader, } +#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved + diff --git a/src/Core/Enums/DataGridDisplayMode.cs b/src/Core/Enums/DataGridDisplayMode.cs index 9050b71151..911c0d2015 100644 --- a/src/Core/Enums/DataGridDisplayMode.cs +++ b/src/Core/Enums/DataGridDisplayMode.cs @@ -4,6 +4,8 @@ namespace Microsoft.FluentUI.AspNetCore.Components; +// ToDo: remove pragma after next PR +#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved /// /// The type of rendering to use for the /// @@ -20,5 +22,6 @@ public enum DataGridDisplayMode /// With this mode fr units cannot be used to set the column widths. /// Table, - } +#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved + diff --git a/src/Core/Enums/DataGridGeneratedHeaderType.cs b/src/Core/Enums/DataGridGeneratedHeaderType.cs index f0a5d17799..436cb773ca 100644 --- a/src/Core/Enums/DataGridGeneratedHeaderType.cs +++ b/src/Core/Enums/DataGridGeneratedHeaderType.cs @@ -4,6 +4,8 @@ namespace Microsoft.FluentUI.AspNetCore.Components; +// ToDo: remove pragma after next PR +#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved /// /// The option for generating a header for the . /// @@ -22,5 +24,6 @@ public enum DataGridGeneratedHeaderType /// /// Generate a sticky header row. /// - Sticky + Sticky, } +#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved diff --git a/src/Core/Enums/DataGridResizeType.cs b/src/Core/Enums/DataGridResizeType.cs index 55e806bb31..45a1863dfc 100644 --- a/src/Core/Enums/DataGridResizeType.cs +++ b/src/Core/Enums/DataGridResizeType.cs @@ -4,6 +4,8 @@ namespace Microsoft.FluentUI.AspNetCore.Components; +// ToDo: remove pragma after next PR +#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved /// /// The type of in a . /// @@ -19,3 +21,4 @@ public enum DataGridResizeType ///
Exact, } +#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved diff --git a/src/Core/Enums/DataGridRowSize.cs b/src/Core/Enums/DataGridRowSize.cs index 6c9296a4ec..25b82f460e 100644 --- a/src/Core/Enums/DataGridRowSize.cs +++ b/src/Core/Enums/DataGridRowSize.cs @@ -4,6 +4,8 @@ namespace Microsoft.FluentUI.AspNetCore.Components; +// ToDo: remove pragma after next PR +#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved /// /// The height of each in a . /// Values are in pixels. @@ -30,3 +32,4 @@ public enum DataGridRowSize /// Large = 58, } +#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved diff --git a/src/Core/Enums/DataGridRowType.cs b/src/Core/Enums/DataGridRowType.cs index 84d838e678..4f66e11841 100644 --- a/src/Core/Enums/DataGridRowType.cs +++ b/src/Core/Enums/DataGridRowType.cs @@ -6,6 +6,8 @@ namespace Microsoft.FluentUI.AspNetCore.Components; +// ToDo: remove pragma after next PR +#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved /// /// The type of in a . /// @@ -27,3 +29,4 @@ public enum DataGridRowType [Description("sticky-header")] StickyHeader, } +#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved diff --git a/src/Core/Enums/DataGridSelectMode.cs b/src/Core/Enums/DataGridSelectMode.cs index e9a9c1204b..b0bb360960 100644 --- a/src/Core/Enums/DataGridSelectMode.cs +++ b/src/Core/Enums/DataGridSelectMode.cs @@ -4,6 +4,8 @@ namespace Microsoft.FluentUI.AspNetCore.Components; +// ToDo: remove pragma after next PR +#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved /// /// How rows can be selected in a when using a . /// @@ -24,3 +26,4 @@ public enum DataGridSelectMode ///
Multiple, } +#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved diff --git a/src/Core/Enums/DataGridSortDirection.cs b/src/Core/Enums/DataGridSortDirection.cs index 7f3d9fd615..173546140a 100644 --- a/src/Core/Enums/DataGridSortDirection.cs +++ b/src/Core/Enums/DataGridSortDirection.cs @@ -4,6 +4,8 @@ namespace Microsoft.FluentUI.AspNetCore.Components; +// ToDo: remove pragma after next PR +#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved /// /// Describes the direction in which a column is sorted. /// @@ -26,3 +28,4 @@ public enum DataGridSortDirection ///
Descending, } +#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved diff --git a/src/Core/Infrastructure/EventCallbackSubscribable.cs b/src/Core/Infrastructure/EventCallbackSubscribable.cs index a423e93912..a9788445d0 100644 --- a/src/Core/Infrastructure/EventCallbackSubscribable.cs +++ b/src/Core/Infrastructure/EventCallbackSubscribable.cs @@ -2,6 +2,7 @@ // MIT License - Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------------------ +using System.Collections.Concurrent; using Microsoft.AspNetCore.Components; namespace Microsoft.FluentUI.AspNetCore.Components.Infrastructure; @@ -14,7 +15,7 @@ namespace Microsoft.FluentUI.AspNetCore.Components.Infrastructure; /// A type for the eventargs. internal sealed class EventCallbackSubscribable { - private readonly Dictionary, EventCallback> _callbacks = []; + private readonly ConcurrentDictionary, EventCallback> _callbacks = []; /// /// Invokes all the registered callbacks sequentially, in an undefined order. @@ -29,9 +30,9 @@ public async Task InvokeCallbacksAsync(T eventArg) // Don't call this directly - it gets called by EventCallbackSubscription public void Subscribe(EventCallbackSubscriber owner, EventCallback callback) - => _callbacks.Add(owner, callback); + => _callbacks.TryAdd(owner, callback); // Don't call this directly - it gets called by EventCallbackSubscription public void Unsubscribe(EventCallbackSubscriber owner) - => _callbacks.Remove(owner); + => _callbacks.TryRemove(owner, out _); } diff --git a/src/Core/Microsoft.FluentUI.AspNetCore.Components.csproj b/src/Core/Microsoft.FluentUI.AspNetCore.Components.csproj index 15f6d4496c..655289b460 100644 --- a/src/Core/Microsoft.FluentUI.AspNetCore.Components.csproj +++ b/src/Core/Microsoft.FluentUI.AspNetCore.Components.csproj @@ -117,4 +117,7 @@ CS1591 + + + From 5aa2c653f214731845f0e46bc08dd1b9b1a198d2 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Thu, 12 Jun 2025 15:26:20 +0200 Subject: [PATCH 08/44] Add language resources --- .../Pagination/FluentPaginator.razor | 19 ++++++++++--------- src/Core/Localization/LanguageResource.resx | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/Core/Components/Pagination/FluentPaginator.razor b/src/Core/Components/Pagination/FluentPaginator.razor index b4e003aa59..d58b409751 100644 --- a/src/Core/Components/Pagination/FluentPaginator.razor +++ b/src/Core/Components/Pagination/FluentPaginator.razor @@ -1,4 +1,5 @@ @namespace Microsoft.FluentUI.AspNetCore.Components +@using System.Globalization @inherits FluentComponentBase
@if (State.TotalItemCount.HasValue) @@ -16,13 +17,13 @@ } diff --git a/src/Core/Localization/LanguageResource.resx b/src/Core/Localization/LanguageResource.resx index 0720e653f7..cda5e94a7a 100644 --- a/src/Core/Localization/LanguageResource.resx +++ b/src/Core/Localization/LanguageResource.resx @@ -228,4 +228,19 @@ An unhandled error has occurred. Please, contact your IT support. + + Go to first page + + + Go to previous page + + + Go to next page + + + Go to last page + + + Page {0} of {1} + \ No newline at end of file From 777e854ecaf0944779cb4083ef932a4af324738f Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Thu, 12 Jun 2025 17:02:14 +0200 Subject: [PATCH 09/44] Add Unit Tests for Paginator --- .../Pagination/FluentPaginator.razor | 7 +- .../Pagination/FluentPaginator.razor.cs | 9 + tests/Core/Components.Tests.csproj | 4 + .../Components/Base/ComponentBaseTests.cs | 1 + ...enTotalItemCountIsNull.verified.razor.html | 2 + ...PaginationTextTemplate.verified.razor.html | 31 ++ ..._CustomSummaryTemplate.verified.razor.html | 29 ++ ...nders_WithDefaultState.verified.razor.html | 29 ++ .../Paginator/FluentPaginatorTests.razor | 298 ++++++++++++++++++ 9 files changed, 408 insertions(+), 2 deletions(-) create mode 100644 tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_DoesNotRender_WhenTotalItemCountIsNull.verified.razor.html create mode 100644 tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_Renders_CustomPaginationTextTemplate.verified.razor.html create mode 100644 tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_Renders_CustomSummaryTemplate.verified.razor.html create mode 100644 tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_Renders_WithDefaultState.verified.razor.html create mode 100644 tests/Core/Components/Paginator/FluentPaginatorTests.razor diff --git a/src/Core/Components/Pagination/FluentPaginator.razor b/src/Core/Components/Pagination/FluentPaginator.razor index d58b409751..f0b6e13e8d 100644 --- a/src/Core/Components/Pagination/FluentPaginator.razor +++ b/src/Core/Components/Pagination/FluentPaginator.razor @@ -1,7 +1,10 @@ @namespace Microsoft.FluentUI.AspNetCore.Components @using System.Globalization @inherits FluentComponentBase -
+
@if (State.TotalItemCount.HasValue) {
@@ -32,7 +35,7 @@ } else { - string.Format(CultureInfo.InvariantCulture, Localizer[Localization.LanguageResource.Paginator_Status], $"{State.CurrentPageIndex + 1}", $"{State.LastPageIndex + 1}"); + @(string.Format(CultureInfo.InvariantCulture, Localizer[Localization.LanguageResource.Paginator_Status], $"{State.CurrentPageIndex + 1}", $"{State.LastPageIndex + 1}")) }
diff --git a/src/Core/Components/Pagination/FluentPaginator.razor.cs b/src/Core/Components/Pagination/FluentPaginator.razor.cs index 82d84c462d..046243153b 100644 --- a/src/Core/Components/Pagination/FluentPaginator.razor.cs +++ b/src/Core/Components/Pagination/FluentPaginator.razor.cs @@ -25,6 +25,15 @@ public FluentPaginator(LibraryConfiguration configuration) : base(configuration) _currentPageItemsChanged = new(new EventCallback(this, @delegate: null)); } + /// + protected string? ClassValue => DefaultClassBuilder + .AddClass("fluent-paginator") + .Build(); + + /// + protected string? StyleValue => DefaultStyleBuilder + .Build(); + /// /// Gets or sets the callback that is invoked when the current page index changes. /// diff --git a/tests/Core/Components.Tests.csproj b/tests/Core/Components.Tests.csproj index 0ffa3aaa3e..9886afa08a 100644 --- a/tests/Core/Components.Tests.csproj +++ b/tests/Core/Components.Tests.csproj @@ -73,4 +73,8 @@ FluentLocalizer.resx + + + + diff --git a/tests/Core/Components/Base/ComponentBaseTests.cs b/tests/Core/Components/Base/ComponentBaseTests.cs index 06b2dde632..92ca4740e7 100644 --- a/tests/Core/Components/Base/ComponentBaseTests.cs +++ b/tests/Core/Components/Base/ComponentBaseTests.cs @@ -40,6 +40,7 @@ public class ComponentBaseTests : Bunit.TestContext { typeof(FluentTooltip), Loader.Default.WithRequiredParameter("Anchor", "MyButton").WithRequiredParameter("UseTooltipService", false)}, { typeof(FluentHighlighter), Loader.Default.WithRequiredParameter("HighlightedText", "AB").WithRequiredParameter("Text", "ABCDEF")}, { typeof(FluentKeyCode), Loader.Default.WithRequiredParameter("ChildContent", (RenderFragment)(builder => builder.AddContent(0, "MyContent"))) }, + { typeof(FluentPaginator), Loader.Default.WithRequiredParameter("State", new PaginationState()) } }; /// diff --git a/tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_DoesNotRender_WhenTotalItemCountIsNull.verified.razor.html b/tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_DoesNotRender_WhenTotalItemCountIsNull.verified.razor.html new file mode 100644 index 0000000000..d996e72df3 --- /dev/null +++ b/tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_DoesNotRender_WhenTotalItemCountIsNull.verified.razor.html @@ -0,0 +1,2 @@ + +
\ No newline at end of file diff --git a/tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_Renders_CustomPaginationTextTemplate.verified.razor.html b/tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_Renders_CustomPaginationTextTemplate.verified.razor.html new file mode 100644 index 0000000000..8314536e7d --- /dev/null +++ b/tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_Renders_CustomPaginationTextTemplate.verified.razor.html @@ -0,0 +1,31 @@ + +
+
+ 100 + items
+ +
\ No newline at end of file diff --git a/tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_Renders_CustomSummaryTemplate.verified.razor.html b/tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_Renders_CustomSummaryTemplate.verified.razor.html new file mode 100644 index 0000000000..281e8215f4 --- /dev/null +++ b/tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_Renders_CustomSummaryTemplate.verified.razor.html @@ -0,0 +1,29 @@ + +
+
+ Total: 100 items +
+ +
\ No newline at end of file diff --git a/tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_Renders_WithDefaultState.verified.razor.html b/tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_Renders_WithDefaultState.verified.razor.html new file mode 100644 index 0000000000..d0c5f5a383 --- /dev/null +++ b/tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_Renders_WithDefaultState.verified.razor.html @@ -0,0 +1,29 @@ + +
+
+ 100 + items
+ +
\ No newline at end of file diff --git a/tests/Core/Components/Paginator/FluentPaginatorTests.razor b/tests/Core/Components/Paginator/FluentPaginatorTests.razor new file mode 100644 index 0000000000..1a847b049f --- /dev/null +++ b/tests/Core/Components/Paginator/FluentPaginatorTests.razor @@ -0,0 +1,298 @@ +@using Microsoft.FluentUI.AspNetCore.Components.Utilities +@using Xunit +@using Microsoft.FluentUI.AspNetCore.Components.Infrastructure +@using Microsoft.AspNetCore.Components.Web +@inherits Bunit.TestContext + +@code { + public FluentPaginatorTests() + { + JSInterop.Mode = JSRuntimeMode.Loose; + Services.AddFluentUIComponents(); + } + + [Fact] + public async Task FluentPaginator_Renders_WithDefaultState() + { + // Arrange + var state = new PaginationState(); + await state.SetTotalItemCountAsync(100); + + // Act + var cut = Render(@); + + // Assert + cut.Verify(); + Assert.NotNull(cut.Find(".fluent-paginator")); + Assert.NotNull(cut.Find(".summary")); + Assert.NotNull(cut.Find(".paginator-nav")); + Assert.Contains("100", cut.Find(".summary").InnerHtml); + } + + [Fact] + public void FluentPaginator_DoesNotRender_WhenTotalItemCountIsNull() + { + // Arrange + var state = new PaginationState(); + + // Act + var cut = Render(@); + + // Assert + cut.Verify(); + Assert.Empty(cut.Find(".fluent-paginator").Children); + Assert.Throws(() => cut.Find(".summary")); + Assert.Throws(() => cut.Find(".paginator-nav")); + } + + [Fact] + public async Task FluentPaginator_Renders_CustomSummaryTemplate() + { + // Arrange + var state = new PaginationState(); + await state.SetTotalItemCountAsync(100); + + // Act + var cut = Render(@ + + Total: @state.TotalItemCount items + + ); + + // Assert + cut.Verify(); + Assert.NotNull(cut.Find(".custom-summary")); + Assert.Contains("Total: 100 items", cut.Find(".custom-summary").InnerHtml); + } + + [Fact] + public async Task FluentPaginator_Renders_CustomPaginationTextTemplate() + { + // Arrange + var state = new PaginationState(); + await state.SetTotalItemCountAsync(100); + + // Act + var cut = Render(@ + + Page @(state.CurrentPageIndex + 1) of @(state.LastPageIndex + 1) + + ); + + // Assert + cut.Verify(); + Assert.NotNull(cut.Find(".custom-pagination-text")); + Assert.Contains("Page 1 of 10", cut.Find(".custom-pagination-text").InnerHtml); + } + + [Fact] + public async Task FluentPaginator_DisabledParameter_DisablesButtons() + { + // Arrange + var state = new PaginationState(); + await state.SetTotalItemCountAsync(100); + + // Act + var cut = Render(@); + + // Assert + var navButtons = cut.FindAll(".paginator-nav fluent-button"); + foreach (var button in navButtons) + { + Assert.NotNull(button.GetAttribute("disabled")); + } + } + + [Fact] + public async Task FluentPaginator_FirstPageButtons_DisabledWhenOnFirstPage() + { + // Arrange + var state = new PaginationState(); + await state.SetTotalItemCountAsync(100); + await state.SetCurrentPageIndexAsync(0); // First page + + // Act + var cut = Render(@); + + // Assert + var buttons = cut.FindAll(".paginator-nav fluent-button"); + + // First and Previous buttons should be disabled + Assert.NotNull(buttons[0].GetAttribute("disabled")); + Assert.NotNull(buttons[1].GetAttribute("disabled")); + + // Next and Last buttons should be enabled + Assert.Null(buttons[2].GetAttribute("disabled")); + Assert.Null(buttons[3].GetAttribute("disabled")); + } + + [Fact] + public async Task FluentPaginator_LastPageButtons_DisabledWhenOnLastPage() + { + // Arrange + var state = new PaginationState(); + await state.SetTotalItemCountAsync(100); + await state.SetCurrentPageIndexAsync(9); // Last page (with 10 items per page) + + // Act + var cut = Render(@); + + // Assert + var buttons = cut.FindAll(".paginator-nav fluent-button"); + + // First and Previous buttons should be enabled + Assert.Null(buttons[0].GetAttribute("disabled")); + Assert.Null(buttons[1].GetAttribute("disabled")); + + // Next and Last buttons should be disabled + Assert.NotNull(buttons[2].GetAttribute("disabled")); + Assert.NotNull(buttons[3].GetAttribute("disabled")); + } + + [Fact] + public async Task FluentPaginator_AllButtonsEnabled_WhenOnMiddlePage() + { + // Arrange + var state = new PaginationState(); + await state.SetTotalItemCountAsync(100); + await state.SetCurrentPageIndexAsync(5); // Middle page + + // Act + var cut = Render(@); + + // Assert + var buttons = cut.FindAll(".paginator-nav fluent-button"); + + // All buttons should be enabled + foreach (var button in buttons) + { + Assert.Null(button.GetAttribute("disabled")); + } + } + + [Fact] + public async Task FluentPaginator_FirstButton_NavigatesToFirstPage() + { + // Arrange + var state = new PaginationState(); + await state.SetTotalItemCountAsync(100); + await state.SetCurrentPageIndexAsync(5); // Start at middle page + + int newPageIndex = -1; + var cut = Render(@); + + // Act + var firstButton = cut.FindAll(".paginator-nav fluent-button")[0]; + await firstButton.ClickAsync(new MouseEventArgs()); + + // Assert + Assert.Equal(0, state.CurrentPageIndex); + Assert.Equal(0, newPageIndex); + } + + [Fact] + public async Task FluentPaginator_PreviousButton_NavigatesToPreviousPage() + { + // Arrange + var state = new PaginationState(); + await state.SetTotalItemCountAsync(100); + await state.SetCurrentPageIndexAsync(5); // Start at middle page + + int newPageIndex = -1; + var cut = Render(@); + + // Act + var previousButton = cut.FindAll(".paginator-nav fluent-button")[1]; + await previousButton.ClickAsync(new MouseEventArgs()); + + // Assert + Assert.Equal(4, state.CurrentPageIndex); + Assert.Equal(4, newPageIndex); + } + + [Fact] + public async Task FluentPaginator_NextButton_NavigatesToNextPage() + { + // Arrange + var state = new PaginationState(); + await state.SetTotalItemCountAsync(100); + await state.SetCurrentPageIndexAsync(5); // Start at middle page + + int newPageIndex = -1; + var cut = Render(@); + + // Act + var nextButton = cut.FindAll(".paginator-nav fluent-button")[2]; + await nextButton.ClickAsync(new MouseEventArgs()); + + // Assert + Assert.Equal(6, state.CurrentPageIndex); + Assert.Equal(6, newPageIndex); + } + + [Fact] + public async Task FluentPaginator_LastButton_NavigatesToLastPage() + { + // Arrange + var state = new PaginationState(); + await state.SetTotalItemCountAsync(100); + await state.SetCurrentPageIndexAsync(5); // Start at middle page + + int newPageIndex = -1; + var cut = Render(@); + + // Act + var lastButton = cut.FindAll(".paginator-nav fluent-button")[3]; + await lastButton.ClickAsync(new MouseEventArgs()); + + // Assert + Assert.Equal(9, state.CurrentPageIndex); // Last page (with 10 items per page) + Assert.Equal(9, newPageIndex); + } + + [Fact] + public async Task FluentPaginator_DisposesEventCallbackSubscribers() + { + // Arrange + var state = new PaginationState(); + await state.SetTotalItemCountAsync(100); + + // Create a component that we can dispose + var cut = Render(@); + + // Act + // Dispose the component (this should call Dispose() internally) + cut.Dispose(); + + // Assert - we can't directly test disposal, but we can verify that the component has been disposed + // by testing that the TestContext is disposed + Assert.True(cut.IsDisposed); + } + + + + [Fact] + public async Task FluentPaginator_NoCurrentPageIndexChangedCallback_WhenNoDelegate() + { + // Arrange + var state = new PaginationState(); + await state.SetTotalItemCountAsync(100); + + var cut = Render(@); + + // Act & Assert - No exception should be thrown when no CurrentPageIndexChanged delegate is provided + var firstButton = cut.FindAll(".paginator-nav fluent-button")[0]; + await firstButton.ClickAsync(new MouseEventArgs()); + + // Verify the page changed in the state + Assert.Equal(0, state.CurrentPageIndex); + } +} From 448c89ea151bcd904a86c6bdab6608ac569367c2 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Fri, 13 Jun 2025 10:44:15 +0200 Subject: [PATCH 10/44] More review comments --- src/Core/Components/Icons/CoreIcons.cs | 13 ++++--------- .../Components/Pagination/FluentPaginator.razor | 6 +++--- .../Microsoft.FluentUI.AspNetCore.Components.csproj | 3 --- tests/Core/Components.Tests.csproj | 4 ---- ...CustomPaginationTextTemplate.verified.razor.html | 8 ++++---- ...enders_CustomSummaryTemplate.verified.razor.html | 10 +++++----- ...tor_Renders_WithDefaultState.verified.razor.html | 10 +++++----- 7 files changed, 21 insertions(+), 33 deletions(-) diff --git a/src/Core/Components/Icons/CoreIcons.cs b/src/Core/Components/Icons/CoreIcons.cs index bd9efd0a0a..1d22b7781d 100644 --- a/src/Core/Components/Icons/CoreIcons.cs +++ b/src/Core/Components/Icons/CoreIcons.cs @@ -41,6 +41,10 @@ public class ChevronDoubleLeft : Icon { public ChevronDoubleLeft() : base("Chevr public class ChevronDoubleRight : Icon { public ChevronDoubleRight() : base("ChevronDoubleRight", IconVariant.Regular, IconSize.Size20, "") { } } + public class ChevronLeft : Icon { public ChevronLeft() : base("ChevronLeft", IconVariant.Regular, IconSize.Size20, "") { } } + + public class ChevronRight : Icon { public ChevronRight() : base("ChevronRight", IconVariant.Regular, IconSize.Size20, "") { } } + public class Dismiss : Icon { public Dismiss() : base("Dismiss", IconVariant.Regular, IconSize.Size20, "") { } }; public class Filter : Icon { public Filter() : base("Filter", IconVariant.Regular, IconSize.Size20, "") { } } @@ -57,15 +61,6 @@ public class TableResizeColumn : Icon { public TableResizeColumn() : base("Table } } - internal static partial class Regular - { - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal static partial class Size24 - { - public class ChevronLeft : Icon { public ChevronLeft() : base("ChevronLeft", IconVariant.Regular, IconSize.Size24, "") { } } - public class ChevronRight : Icon { public ChevronRight() : base("ChevronRight", IconVariant.Regular, IconSize.Size24, "") { } } - } - } /// /// Filled icons /// diff --git a/src/Core/Components/Pagination/FluentPaginator.razor b/src/Core/Components/Pagination/FluentPaginator.razor index f0b6e13e8d..2c39d9d36c 100644 --- a/src/Core/Components/Pagination/FluentPaginator.razor +++ b/src/Core/Components/Pagination/FluentPaginator.razor @@ -27,7 +27,7 @@ Disabled="@(!CanGoBack || Disabled)" Title="@Localizer[Localization.LanguageResource.Paginator_GoPreviousPage]" aria-label="@Localizer[Localization.LanguageResource.Paginator_GoPreviousPage]" - IconStart="@(new CoreIcons.Regular.Size24.ChevronLeft())" /> + IconStart="@(new CoreIcons.Regular.Size20.ChevronLeft())" />
@if (PaginationTextTemplate is not null) { @@ -35,7 +35,7 @@ } else { - @(string.Format(CultureInfo.InvariantCulture, Localizer[Localization.LanguageResource.Paginator_Status], $"{State.CurrentPageIndex + 1}", $"{State.LastPageIndex + 1}")) + @(string.Format(CultureInfo.InvariantCulture, Localizer[Localization.LanguageResource.Paginator_Status], State.CurrentPageIndex + 1, State.LastPageIndex + 1)) }
@@ -43,7 +43,7 @@ Disabled="@(!CanGoForwards || Disabled)" Title="@Localizer[Localization.LanguageResource.Paginator_GoNextPage]" aria-label="@Localizer[Localization.LanguageResource.Paginator_GoNextPage]" - IconStart="@(new CoreIcons.Regular.Size24.ChevronRight())" /> + IconStart="@(new CoreIcons.Regular.Size20.ChevronRight())" /> CS1591 - - - diff --git a/tests/Core/Components.Tests.csproj b/tests/Core/Components.Tests.csproj index 9886afa08a..0ffa3aaa3e 100644 --- a/tests/Core/Components.Tests.csproj +++ b/tests/Core/Components.Tests.csproj @@ -73,8 +73,4 @@ FluentLocalizer.resx - - - - diff --git a/tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_Renders_CustomPaginationTextTemplate.verified.razor.html b/tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_Renders_CustomPaginationTextTemplate.verified.razor.html index 8314536e7d..5d6292c33d 100644 --- a/tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_Renders_CustomPaginationTextTemplate.verified.razor.html +++ b/tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_Renders_CustomPaginationTextTemplate.verified.razor.html @@ -10,16 +10,16 @@ -
Page 1 of 10
- diff --git a/tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_Renders_CustomSummaryTemplate.verified.razor.html b/tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_Renders_CustomSummaryTemplate.verified.razor.html index 281e8215f4..676169d820 100644 --- a/tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_Renders_CustomSummaryTemplate.verified.razor.html +++ b/tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_Renders_CustomSummaryTemplate.verified.razor.html @@ -10,14 +10,14 @@ - -
Page <strong>1</strong> of <strong>10</strong>
+
Page 1 of 10
- diff --git a/tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_Renders_WithDefaultState.verified.razor.html b/tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_Renders_WithDefaultState.verified.razor.html index d0c5f5a383..486e394ef8 100644 --- a/tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_Renders_WithDefaultState.verified.razor.html +++ b/tests/Core/Components/Paginator/FluentPaginatorTests.FluentPaginator_Renders_WithDefaultState.verified.razor.html @@ -10,14 +10,14 @@ - -
Page <strong>1</strong> of <strong>10</strong>
+
Page 1 of 10
- From 3146aebcb9ad17668096e9bd70748c4ec1701c9a Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Fri, 13 Jun 2025 10:59:16 +0200 Subject: [PATCH 11/44] Update tests. Now 100% lc --- .../Paginator/FluentPaginatorTests.razor | 50 +++++++++++-------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/tests/Core/Components/Paginator/FluentPaginatorTests.razor b/tests/Core/Components/Paginator/FluentPaginatorTests.razor index 1a847b049f..82bf8b3878 100644 --- a/tests/Core/Components/Paginator/FluentPaginatorTests.razor +++ b/tests/Core/Components/Paginator/FluentPaginatorTests.razor @@ -116,11 +116,11 @@ // Assert var buttons = cut.FindAll(".paginator-nav fluent-button"); - + // First and Previous buttons should be disabled Assert.NotNull(buttons[0].GetAttribute("disabled")); Assert.NotNull(buttons[1].GetAttribute("disabled")); - + // Next and Last buttons should be enabled Assert.Null(buttons[2].GetAttribute("disabled")); Assert.Null(buttons[3].GetAttribute("disabled")); @@ -139,11 +139,11 @@ // Assert var buttons = cut.FindAll(".paginator-nav fluent-button"); - + // First and Previous buttons should be enabled Assert.Null(buttons[0].GetAttribute("disabled")); Assert.Null(buttons[1].GetAttribute("disabled")); - + // Next and Last buttons should be disabled Assert.NotNull(buttons[2].GetAttribute("disabled")); Assert.NotNull(buttons[3].GetAttribute("disabled")); @@ -162,7 +162,7 @@ // Assert var buttons = cut.FindAll(".paginator-nav fluent-button"); - + // All buttons should be enabled foreach (var button in buttons) { @@ -177,9 +177,9 @@ var state = new PaginationState(); await state.SetTotalItemCountAsync(100); await state.SetCurrentPageIndexAsync(5); // Start at middle page - + int newPageIndex = -1; - var cut = Render(@); @@ -199,9 +199,9 @@ var state = new PaginationState(); await state.SetTotalItemCountAsync(100); await state.SetCurrentPageIndexAsync(5); // Start at middle page - + int newPageIndex = -1; - var cut = Render(@); @@ -221,9 +221,9 @@ var state = new PaginationState(); await state.SetTotalItemCountAsync(100); await state.SetCurrentPageIndexAsync(5); // Start at middle page - + int newPageIndex = -1; - var cut = Render(@); @@ -243,11 +243,12 @@ var state = new PaginationState(); await state.SetTotalItemCountAsync(100); await state.SetCurrentPageIndexAsync(5); // Start at middle page - + int newPageIndex = -1; - var cut = Render(@); + CurrentPageIndexChanged="@(index => newPageIndex = index)" /> + ); // Act var lastButton = cut.FindAll(".paginator-nav fluent-button")[3]; @@ -263,21 +264,26 @@ { // Arrange var state = new PaginationState(); + FluentPaginator? paginator = default!; await state.SetTotalItemCountAsync(100); - + // Create a component that we can dispose - var cut = Render(@); - + var cut = Render(@); + // Act // Dispose the component (this should call Dispose() internally) + if (paginator is not null) + { + paginator?.Dispose(); + } cut.Dispose(); - + // Assert - we can't directly test disposal, but we can verify that the component has been disposed // by testing that the TestContext is disposed Assert.True(cut.IsDisposed); } - + [Fact] public async Task FluentPaginator_NoCurrentPageIndexChangedCallback_WhenNoDelegate() @@ -285,13 +291,13 @@ // Arrange var state = new PaginationState(); await state.SetTotalItemCountAsync(100); - + var cut = Render(@); - + // Act & Assert - No exception should be thrown when no CurrentPageIndexChanged delegate is provided var firstButton = cut.FindAll(".paginator-nav fluent-button")[0]; await firstButton.ClickAsync(new MouseEventArgs()); - + // Verify the page changed in the state Assert.Equal(0, state.CurrentPageIndex); } From 35e0334b449d587ff8546200d0a71bde6e558de6 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Fri, 13 Jun 2025 17:34:08 +0200 Subject: [PATCH 12/44] Replace tokens (WIP) --- .../DataGrid/FluentDataGrid.razor.cs | 10 +++- .../DataGrid/FluentDataGrid.razor.css | 48 +++++++++---------- .../DataGrid/FluentDataGridCell.razor.css | 4 +- .../DataGrid/FluentDataGridRow.razor.css | 4 +- 4 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index 0036b0c3e2..05128d1d9d 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -156,6 +156,14 @@ public FluentDataGrid(LibraryConfiguration configuration) : base(configuration) [Parameter] public bool ResizableColumns { get; set; } + /// + /// Gets or sets a value indicating whether column resize handles should extend the full height of the grid. + /// When true, columns can be resized by dragging from any row. When false, columns can only be resized + /// by dragging from the column header. Default is true. + /// + [Parameter] + public bool ResizeColumnOnAllRows { get; set; } = true; + /// /// To comply with WCAG 2.2, a one-click option should be offered to change column widths. We provide such an option through the /// ColumnOptions UI. This parameter allows you to enable or disable this resize UI.Enable it by setting the type of resize to perform @@ -525,7 +533,7 @@ private void FinishCollectingColumns() //if (ResizableColumns) //{ - // _ = JSModule.ObjectReference.InvokeVoidAsync("enableColumnResizing", _gridReference).AsTask(); + // _ = JSModule.ObjectReference.InvokeVoidAsync("enableColumnResizing", _gridReference, ResizeColumnOnAllRows).AsTask(); //} } diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.css b/src/Core/Components/DataGrid/FluentDataGrid.razor.css index eac2360ae8..fb24f08d3f 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.css +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.css @@ -1,13 +1,13 @@ .fluent-data-grid { - --fluent-data-grid-resize-handle-color: var(--accent-fill-rest); - --fluent-data-grid-resize-handle-width: 1px; - --fluent-data-grid-header-opacity: 0.5; - width: auto; - flex: 1; - border-collapse: collapse; - align-items: center; - height: max-content; - margin-bottom: 0px; + --fluent-data-grid-resize-handle-color: var(--colorBrandForeground1); + --fluent-data-grid-resize-handle-width: 1px; + --fluent-data-grid-header-opacity: 0.5; + width: auto; + flex: 1; + border-collapse: collapse; + align-items: center; + height: max-content; + margin-bottom: 0px; } .fluent-data-grid.grid { @@ -24,20 +24,20 @@ } .fluent-data-grid tbody tr .hover { - background: var(--neutral-fill-stealth-hover); + background: var(--colorNeutralBackground1Hover); } .col-options, .col-resize { - position: absolute; - min-width: 250px; - top: 2.7rem; - background: var(--neutral-layer-2); - border: 1px solid var(--neutral-layer-3); - border-radius: 0.3rem; - box-shadow: 0 3px 8px 1px var(--neutral-layer-4); - padding: 1rem; - visibility: hidden; - z-index: 1; + position: absolute; + min-width: 250px; + top: 2.7rem; + background: var(--colorNeutralStroke1); + border: 1px solid var(--colorNeutralStroke2); + border-radius: 0.3rem; + box-shadow: var(--shadow8); + padding: 1rem; + visibility: hidden; + z-index: 1; } [dir=rtl] .col-options { @@ -84,8 +84,8 @@ } tr[row-type='sticky-header'] > th { - position: sticky; - top: 0; - background-color: var(--neutral-fill-stealth-rest); - z-index: 2; + position: sticky; + top: 0; + background-color: var(--colorNeutralBackground4); + z-index: 2; } diff --git a/src/Core/Components/DataGrid/FluentDataGridCell.razor.css b/src/Core/Components/DataGrid/FluentDataGridCell.razor.css index 2ee3967b2b..244b7b2367 100644 --- a/src/Core/Components/DataGrid/FluentDataGridCell.razor.css +++ b/src/Core/Components/DataGrid/FluentDataGridCell.razor.css @@ -1,5 +1,5 @@ th, td { - border-bottom: calc(var(--stroke-width)* 1px) solid var(--neutral-stroke-divider-rest); + border-bottom: var(--strokeWidthThin) solid var(--colorNeutralStencil1); } td { @@ -50,7 +50,7 @@ td.grid-cell-placeholder:after { font-weight: 600; text-align: center; position: relative; - padding: calc((var(--design-unit) + var(--focus-stroke-width) - var(--stroke-width)) * 1px) 1px calc((var(--design-unit) + var(--focus-stroke-width) - var(--stroke-width)) * 1px); + padding: 5px 1px; } .col-sort-button { diff --git a/src/Core/Components/DataGrid/FluentDataGridRow.razor.css b/src/Core/Components/DataGrid/FluentDataGridRow.razor.css index 6ed06f3147..361826c2ce 100644 --- a/src/Core/Components/DataGrid/FluentDataGridRow.razor.css +++ b/src/Core/Components/DataGrid/FluentDataGridRow.razor.css @@ -3,8 +3,8 @@ } .hover:not([row-type='header'],[row-type='sticky-header'],.loading-content-row):hover td:not(.empty-content-cell) { - cursor: pointer; - background-color: var(--datagrid-hover-color, var(--neutral-fill-stealth-hover)); + cursor: pointer; + background-color: var(--datagrid-hover-color, var(--colorNeutralStroke2)); } From b92c83c99a11b2f203cbc24a84d53d5d3b8c4718 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Tue, 17 Jun 2025 10:24:21 +0200 Subject: [PATCH 13/44] Remove flags. These will be added to sample data project in separate PR --- .../Demo/FluentUI.Demo/FluentUI.Demo.csproj | 8 - .../FluentUI.Demo/wwwroot/flags/README.md | 27 - .../Demo/FluentUI.Demo/wwwroot/flags/ac.svg | 76 - .../Demo/FluentUI.Demo/wwwroot/flags/ad.svg | 150 - .../Demo/FluentUI.Demo/wwwroot/flags/ae.svg | 6 - .../Demo/FluentUI.Demo/wwwroot/flags/af.svg | 81 - .../Demo/FluentUI.Demo/wwwroot/flags/ag.svg | 14 - .../Demo/FluentUI.Demo/wwwroot/flags/ai.svg | 763 -- .../Demo/FluentUI.Demo/wwwroot/flags/al.svg | 5 - .../Demo/FluentUI.Demo/wwwroot/flags/am.svg | 5 - .../Demo/FluentUI.Demo/wwwroot/flags/ao.svg | 13 - .../Demo/FluentUI.Demo/wwwroot/flags/aq.svg | 5 - .../Demo/FluentUI.Demo/wwwroot/flags/ar.svg | 33 - .../Demo/FluentUI.Demo/wwwroot/flags/as.svg | 80 - .../Demo/FluentUI.Demo/wwwroot/flags/at.svg | 6 - .../Demo/FluentUI.Demo/wwwroot/flags/au.svg | 16 - .../Demo/FluentUI.Demo/wwwroot/flags/aw.svg | 186 - .../Demo/FluentUI.Demo/wwwroot/flags/ax.svg | 18 - .../Demo/FluentUI.Demo/wwwroot/flags/az.svg | 8 - .../Demo/FluentUI.Demo/wwwroot/flags/ba.svg | 12 - .../Demo/FluentUI.Demo/wwwroot/flags/bb.svg | 6 - .../Demo/FluentUI.Demo/wwwroot/flags/bd.svg | 4 - .../Demo/FluentUI.Demo/wwwroot/flags/be.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/bf.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/bg.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/bh.svg | 9 - .../Demo/FluentUI.Demo/wwwroot/flags/bi.svg | 15 - .../Demo/FluentUI.Demo/wwwroot/flags/bj.svg | 14 - .../Demo/FluentUI.Demo/wwwroot/flags/bl.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/bm.svg | 99 - .../Demo/FluentUI.Demo/wwwroot/flags/bn.svg | 36 - .../Demo/FluentUI.Demo/wwwroot/flags/bo.svg | 676 -- .../Demo/FluentUI.Demo/wwwroot/flags/bq.svg | 5 - .../Demo/FluentUI.Demo/wwwroot/flags/br.svg | 45 - .../Demo/FluentUI.Demo/wwwroot/flags/bs.svg | 13 - .../Demo/FluentUI.Demo/wwwroot/flags/bt.svg | 89 - .../Demo/FluentUI.Demo/wwwroot/flags/bv.svg | 13 - .../Demo/FluentUI.Demo/wwwroot/flags/bw.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/by.svg | 20 - .../Demo/FluentUI.Demo/wwwroot/flags/bz.svg | 145 - .../Demo/FluentUI.Demo/wwwroot/flags/ca.svg | 4 - .../Demo/FluentUI.Demo/wwwroot/flags/cc.svg | 19 - .../Demo/FluentUI.Demo/wwwroot/flags/cd.svg | 5 - .../FluentUI.Demo/wwwroot/flags/cefta.svg | 17 - .../Demo/FluentUI.Demo/wwwroot/flags/cf.svg | 15 - .../Demo/FluentUI.Demo/wwwroot/flags/cg.svg | 12 - .../Demo/FluentUI.Demo/wwwroot/flags/ch.svg | 9 - .../Demo/FluentUI.Demo/wwwroot/flags/ci.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/ck.svg | 9 - .../Demo/FluentUI.Demo/wwwroot/flags/cl.svg | 13 - .../Demo/FluentUI.Demo/wwwroot/flags/cm.svg | 15 - .../Demo/FluentUI.Demo/wwwroot/flags/cn.svg | 11 - .../Demo/FluentUI.Demo/wwwroot/flags/co.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/cp.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/cr.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/cu.svg | 13 - .../Demo/FluentUI.Demo/wwwroot/flags/cv.svg | 13 - .../Demo/FluentUI.Demo/wwwroot/flags/cw.svg | 14 - .../Demo/FluentUI.Demo/wwwroot/flags/cx.svg | 15 - .../Demo/FluentUI.Demo/wwwroot/flags/cy.svg | 6 - .../Demo/FluentUI.Demo/wwwroot/flags/cz.svg | 5 - .../Demo/FluentUI.Demo/wwwroot/flags/de.svg | 5 - .../Demo/FluentUI.Demo/wwwroot/flags/dg.svg | 134 - .../Demo/FluentUI.Demo/wwwroot/flags/dj.svg | 13 - .../Demo/FluentUI.Demo/wwwroot/flags/dk.svg | 5 - .../Demo/FluentUI.Demo/wwwroot/flags/dm.svg | 152 - .../Demo/FluentUI.Demo/wwwroot/flags/do.svg | 6745 ----------------- .../Demo/FluentUI.Demo/wwwroot/flags/dz.svg | 5 - .../Demo/FluentUI.Demo/wwwroot/flags/ea.svg | 544 -- .../Demo/FluentUI.Demo/wwwroot/flags/ec.svg | 138 - .../Demo/FluentUI.Demo/wwwroot/flags/ee.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/eg.svg | 38 - .../Demo/FluentUI.Demo/wwwroot/flags/eh.svg | 16 - .../Demo/FluentUI.Demo/wwwroot/flags/er.svg | 8 - .../FluentUI.Demo/wwwroot/flags/es-ct.svg | 4 - .../FluentUI.Demo/wwwroot/flags/es-ga.svg | 189 - .../Demo/FluentUI.Demo/wwwroot/flags/es.svg | 544 -- .../Demo/FluentUI.Demo/wwwroot/flags/et.svg | 14 - .../Demo/FluentUI.Demo/wwwroot/flags/eu.svg | 28 - .../Demo/FluentUI.Demo/wwwroot/flags/fi.svg | 5 - .../Demo/FluentUI.Demo/wwwroot/flags/fj.svg | 122 - .../Demo/FluentUI.Demo/wwwroot/flags/fk.svg | 90 - .../Demo/FluentUI.Demo/wwwroot/flags/fm.svg | 11 - .../Demo/FluentUI.Demo/wwwroot/flags/fo.svg | 12 - .../Demo/FluentUI.Demo/wwwroot/flags/fr.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/ga.svg | 7 - .../FluentUI.Demo/wwwroot/flags/gb-eng.svg | 5 - .../FluentUI.Demo/wwwroot/flags/gb-nir.svg | 132 - .../FluentUI.Demo/wwwroot/flags/gb-sct.svg | 4 - .../FluentUI.Demo/wwwroot/flags/gb-wls.svg | 9 - .../Demo/FluentUI.Demo/wwwroot/flags/gb.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/gd.svg | 27 - .../Demo/FluentUI.Demo/wwwroot/flags/ge.svg | 6 - .../Demo/FluentUI.Demo/wwwroot/flags/gf.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/gg.svg | 9 - .../Demo/FluentUI.Demo/wwwroot/flags/gh.svg | 6 - .../Demo/FluentUI.Demo/wwwroot/flags/gi.svg | 32 - .../Demo/FluentUI.Demo/wwwroot/flags/gl.svg | 4 - .../Demo/FluentUI.Demo/wwwroot/flags/gm.svg | 14 - .../Demo/FluentUI.Demo/wwwroot/flags/gn.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/gp.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/gq.svg | 23 - .../Demo/FluentUI.Demo/wwwroot/flags/gr.svg | 16 - .../Demo/FluentUI.Demo/wwwroot/flags/gs.svg | 242 - .../Demo/FluentUI.Demo/wwwroot/flags/gt.svg | 204 - .../Demo/FluentUI.Demo/wwwroot/flags/gu.svg | 31 - .../Demo/FluentUI.Demo/wwwroot/flags/gw.svg | 13 - .../Demo/FluentUI.Demo/wwwroot/flags/gy.svg | 9 - .../Demo/FluentUI.Demo/wwwroot/flags/hk.svg | 30 - .../Demo/FluentUI.Demo/wwwroot/flags/hm.svg | 15 - .../Demo/FluentUI.Demo/wwwroot/flags/hn.svg | 18 - .../Demo/FluentUI.Demo/wwwroot/flags/hr.svg | 58 - .../Demo/FluentUI.Demo/wwwroot/flags/ht.svg | 116 - .../Demo/FluentUI.Demo/wwwroot/flags/hu.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/ic.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/id.svg | 6 - .../Demo/FluentUI.Demo/wwwroot/flags/ie.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/il.svg | 14 - .../Demo/FluentUI.Demo/wwwroot/flags/im.svg | 36 - .../Demo/FluentUI.Demo/wwwroot/flags/in.svg | 25 - .../Demo/FluentUI.Demo/wwwroot/flags/io.svg | 134 - .../Demo/FluentUI.Demo/wwwroot/flags/iq.svg | 10 - .../Demo/FluentUI.Demo/wwwroot/flags/ir.svg | 219 - .../Demo/FluentUI.Demo/wwwroot/flags/is.svg | 12 - .../Demo/FluentUI.Demo/wwwroot/flags/it.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/je.svg | 47 - .../Demo/FluentUI.Demo/wwwroot/flags/jm.svg | 8 - .../Demo/FluentUI.Demo/wwwroot/flags/jo.svg | 16 - .../Demo/FluentUI.Demo/wwwroot/flags/jp.svg | 11 - .../Demo/FluentUI.Demo/wwwroot/flags/ke.svg | 23 - .../Demo/FluentUI.Demo/wwwroot/flags/kg.svg | 15 - .../Demo/FluentUI.Demo/wwwroot/flags/kh.svg | 61 - .../Demo/FluentUI.Demo/wwwroot/flags/ki.svg | 36 - .../Demo/FluentUI.Demo/wwwroot/flags/km.svg | 16 - .../Demo/FluentUI.Demo/wwwroot/flags/kn.svg | 14 - .../Demo/FluentUI.Demo/wwwroot/flags/kp.svg | 15 - .../Demo/FluentUI.Demo/wwwroot/flags/kr.svg | 24 - .../Demo/FluentUI.Demo/wwwroot/flags/kw.svg | 13 - .../Demo/FluentUI.Demo/wwwroot/flags/ky.svg | 225 - .../Demo/FluentUI.Demo/wwwroot/flags/kz.svg | 23 - .../Demo/FluentUI.Demo/wwwroot/flags/la.svg | 12 - .../Demo/FluentUI.Demo/wwwroot/flags/lb.svg | 15 - .../Demo/FluentUI.Demo/wwwroot/flags/lc.svg | 8 - .../Demo/FluentUI.Demo/wwwroot/flags/li.svg | 43 - .../Demo/FluentUI.Demo/wwwroot/flags/lk.svg | 22 - .../Demo/FluentUI.Demo/wwwroot/flags/lr.svg | 14 - .../Demo/FluentUI.Demo/wwwroot/flags/ls.svg | 8 - .../Demo/FluentUI.Demo/wwwroot/flags/lt.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/lu.svg | 5 - .../Demo/FluentUI.Demo/wwwroot/flags/lv.svg | 6 - .../Demo/FluentUI.Demo/wwwroot/flags/ly.svg | 13 - .../Demo/FluentUI.Demo/wwwroot/flags/ma.svg | 4 - .../Demo/FluentUI.Demo/wwwroot/flags/mc.svg | 6 - .../Demo/FluentUI.Demo/wwwroot/flags/md.svg | 70 - .../Demo/FluentUI.Demo/wwwroot/flags/me.svg | 116 - .../Demo/FluentUI.Demo/wwwroot/flags/mf.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/mg.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/mh.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/mk.svg | 5 - .../Demo/FluentUI.Demo/wwwroot/flags/ml.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/mm.svg | 16 - .../Demo/FluentUI.Demo/wwwroot/flags/mn.svg | 13 - .../Demo/FluentUI.Demo/wwwroot/flags/mo.svg | 9 - .../Demo/FluentUI.Demo/wwwroot/flags/mp.svg | 86 - .../Demo/FluentUI.Demo/wwwroot/flags/mq.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/mr.svg | 6 - .../Demo/FluentUI.Demo/wwwroot/flags/ms.svg | 78 - .../Demo/FluentUI.Demo/wwwroot/flags/mt.svg | 49 - .../Demo/FluentUI.Demo/wwwroot/flags/mu.svg | 8 - .../Demo/FluentUI.Demo/wwwroot/flags/mv.svg | 6 - .../Demo/FluentUI.Demo/wwwroot/flags/mw.svg | 10 - .../Demo/FluentUI.Demo/wwwroot/flags/mx.svg | 382 - .../Demo/FluentUI.Demo/wwwroot/flags/my.svg | 11 - .../Demo/FluentUI.Demo/wwwroot/flags/mz.svg | 21 - .../Demo/FluentUI.Demo/wwwroot/flags/na.svg | 16 - .../Demo/FluentUI.Demo/wwwroot/flags/nc.svg | 13 - .../Demo/FluentUI.Demo/wwwroot/flags/ne.svg | 6 - .../Demo/FluentUI.Demo/wwwroot/flags/nf.svg | 9 - .../Demo/FluentUI.Demo/wwwroot/flags/ng.svg | 6 - .../Demo/FluentUI.Demo/wwwroot/flags/ni.svg | 129 - .../Demo/FluentUI.Demo/wwwroot/flags/nl.svg | 5 - .../Demo/FluentUI.Demo/wwwroot/flags/no.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/np.svg | 14 - .../Demo/FluentUI.Demo/wwwroot/flags/nr.svg | 12 - .../Demo/FluentUI.Demo/wwwroot/flags/nu.svg | 10 - .../Demo/FluentUI.Demo/wwwroot/flags/nz.svg | 42 - .../Demo/FluentUI.Demo/wwwroot/flags/om.svg | 115 - .../Demo/FluentUI.Demo/wwwroot/flags/pa.svg | 14 - .../Demo/FluentUI.Demo/wwwroot/flags/pe.svg | 244 - .../Demo/FluentUI.Demo/wwwroot/flags/pf.svg | 19 - .../Demo/FluentUI.Demo/wwwroot/flags/pg.svg | 9 - .../Demo/FluentUI.Demo/wwwroot/flags/ph.svg | 9 - .../Demo/FluentUI.Demo/wwwroot/flags/pk.svg | 15 - .../Demo/FluentUI.Demo/wwwroot/flags/pl.svg | 6 - .../Demo/FluentUI.Demo/wwwroot/flags/pm.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/pn.svg | 97 - .../Demo/FluentUI.Demo/wwwroot/flags/pr.svg | 13 - .../Demo/FluentUI.Demo/wwwroot/flags/ps.svg | 15 - .../Demo/FluentUI.Demo/wwwroot/flags/pt.svg | 57 - .../Demo/FluentUI.Demo/wwwroot/flags/pw.svg | 11 - .../Demo/FluentUI.Demo/wwwroot/flags/py.svg | 157 - .../Demo/FluentUI.Demo/wwwroot/flags/qa.svg | 4 - .../Demo/FluentUI.Demo/wwwroot/flags/re.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/ro.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/roc.svg | 63 - .../Demo/FluentUI.Demo/wwwroot/flags/rs.svg | 292 - .../Demo/FluentUI.Demo/wwwroot/flags/ru.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/rw.svg | 13 - .../Demo/FluentUI.Demo/wwwroot/flags/sa.svg | 26 - .../Demo/FluentUI.Demo/wwwroot/flags/sb.svg | 13 - .../Demo/FluentUI.Demo/wwwroot/flags/sc.svg | 14 - .../Demo/FluentUI.Demo/wwwroot/flags/sd.svg | 13 - .../Demo/FluentUI.Demo/wwwroot/flags/se.svg | 4 - .../Demo/FluentUI.Demo/wwwroot/flags/sg.svg | 13 - .../Demo/FluentUI.Demo/wwwroot/flags/sh.svg | 76 - .../Demo/FluentUI.Demo/wwwroot/flags/si.svg | 18 - .../Demo/FluentUI.Demo/wwwroot/flags/sj.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/sk.svg | 9 - .../Demo/FluentUI.Demo/wwwroot/flags/sl.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/sm.svg | 91 - .../Demo/FluentUI.Demo/wwwroot/flags/sn.svg | 8 - .../Demo/FluentUI.Demo/wwwroot/flags/so.svg | 11 - .../Demo/FluentUI.Demo/wwwroot/flags/sr.svg | 6 - .../Demo/FluentUI.Demo/wwwroot/flags/ss.svg | 8 - .../Demo/FluentUI.Demo/wwwroot/flags/st.svg | 16 - .../Demo/FluentUI.Demo/wwwroot/flags/sv.svg | 594 -- .../Demo/FluentUI.Demo/wwwroot/flags/sx.svg | 56 - .../Demo/FluentUI.Demo/wwwroot/flags/sy.svg | 6 - .../Demo/FluentUI.Demo/wwwroot/flags/sz.svg | 45 - .../Demo/FluentUI.Demo/wwwroot/flags/ta.svg | 76 - .../Demo/FluentUI.Demo/wwwroot/flags/tc.svg | 52 - .../Demo/FluentUI.Demo/wwwroot/flags/td.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/tf.svg | 15 - .../Demo/FluentUI.Demo/wwwroot/flags/tg.svg | 14 - .../Demo/FluentUI.Demo/wwwroot/flags/th.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/tj.svg | 22 - .../Demo/FluentUI.Demo/wwwroot/flags/tk.svg | 5 - .../Demo/FluentUI.Demo/wwwroot/flags/tl.svg | 13 - .../Demo/FluentUI.Demo/wwwroot/flags/tm.svg | 206 - .../Demo/FluentUI.Demo/wwwroot/flags/tn.svg | 13 - .../Demo/FluentUI.Demo/wwwroot/flags/to.svg | 10 - .../Demo/FluentUI.Demo/wwwroot/flags/tpe.svg | 1 - .../Demo/FluentUI.Demo/wwwroot/flags/tr.svg | 8 - .../Demo/FluentUI.Demo/wwwroot/flags/tt.svg | 5 - .../Demo/FluentUI.Demo/wwwroot/flags/tv.svg | 16 - .../Demo/FluentUI.Demo/wwwroot/flags/tz.svg | 13 - .../Demo/FluentUI.Demo/wwwroot/flags/ua.svg | 6 - .../Demo/FluentUI.Demo/wwwroot/flags/ug.svg | 30 - .../Demo/FluentUI.Demo/wwwroot/flags/um.svg | 15 - .../Demo/FluentUI.Demo/wwwroot/flags/un.svg | 16 - .../Demo/FluentUI.Demo/wwwroot/flags/us.svg | 10 - .../Demo/FluentUI.Demo/wwwroot/flags/uy.svg | 28 - .../Demo/FluentUI.Demo/wwwroot/flags/uz.svg | 30 - .../Demo/FluentUI.Demo/wwwroot/flags/va.svg | 479 -- .../Demo/FluentUI.Demo/wwwroot/flags/vc.svg | 8 - .../Demo/FluentUI.Demo/wwwroot/flags/ve.svg | 26 - .../Demo/FluentUI.Demo/wwwroot/flags/vg.svg | 127 - .../Demo/FluentUI.Demo/wwwroot/flags/vi.svg | 28 - .../Demo/FluentUI.Demo/wwwroot/flags/vn.svg | 11 - .../Demo/FluentUI.Demo/wwwroot/flags/vu.svg | 18 - .../Demo/FluentUI.Demo/wwwroot/flags/wf.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/ws.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/xk.svg | 16 - .../Demo/FluentUI.Demo/wwwroot/flags/xx.svg | 5 - .../Demo/FluentUI.Demo/wwwroot/flags/ye.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/yt.svg | 7 - .../Demo/FluentUI.Demo/wwwroot/flags/za.svg | 17 - .../Demo/FluentUI.Demo/wwwroot/flags/zm.svg | 27 - .../Demo/FluentUI.Demo/wwwroot/flags/zw.svg | 21 - 269 files changed, 18888 deletions(-) delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/README.md delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ac.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ad.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ae.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/af.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ag.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ai.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/al.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/am.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ao.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/aq.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ar.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/as.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/at.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/au.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/aw.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ax.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/az.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ba.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bb.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bd.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/be.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bf.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bg.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bh.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bi.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bj.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bl.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bm.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bn.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bo.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bq.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/br.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bs.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bt.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bv.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bw.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/by.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/bz.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ca.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cc.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cd.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cefta.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cf.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cg.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ch.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ci.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ck.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cl.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cm.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cn.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/co.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cp.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cr.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cu.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cv.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cw.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cx.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cy.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/cz.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/de.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/dg.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/dj.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/dk.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/dm.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/do.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/dz.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ea.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ec.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ee.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/eg.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/eh.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/er.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/es-ct.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/es-ga.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/es.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/et.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/eu.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/fi.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/fj.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/fk.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/fm.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/fo.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/fr.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ga.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gb-eng.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gb-nir.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gb-sct.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gb-wls.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gb.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gd.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ge.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gf.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gg.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gh.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gi.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gl.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gm.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gn.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gp.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gq.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gr.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gs.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gt.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gu.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gw.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/gy.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/hk.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/hm.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/hn.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/hr.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ht.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/hu.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ic.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/id.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ie.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/il.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/im.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/in.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/io.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/iq.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ir.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/is.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/it.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/je.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/jm.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/jo.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/jp.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ke.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/kg.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/kh.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ki.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/km.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/kn.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/kp.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/kr.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/kw.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ky.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/kz.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/la.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/lb.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/lc.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/li.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/lk.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/lr.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ls.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/lt.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/lu.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/lv.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ly.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ma.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mc.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/md.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/me.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mf.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mg.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mh.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mk.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ml.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mm.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mn.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mo.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mp.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mq.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mr.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ms.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mt.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mu.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mv.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mw.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mx.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/my.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/mz.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/na.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/nc.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ne.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/nf.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ng.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ni.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/nl.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/no.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/np.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/nr.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/nu.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/nz.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/om.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/pa.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/pe.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/pf.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/pg.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ph.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/pk.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/pl.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/pm.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/pn.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/pr.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ps.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/pt.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/pw.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/py.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/qa.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/re.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ro.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/roc.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/rs.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ru.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/rw.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sa.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sb.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sc.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sd.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/se.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sg.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sh.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/si.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sj.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sk.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sl.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sm.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sn.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/so.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sr.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ss.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/st.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sv.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sx.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sy.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/sz.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ta.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/tc.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/td.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/tf.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/tg.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/th.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/tj.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/tk.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/tl.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/tm.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/tn.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/to.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/tpe.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/tr.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/tt.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/tv.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/tz.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ua.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ug.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/um.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/un.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/us.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/uy.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/uz.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/va.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/vc.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ve.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/vg.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/vi.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/vn.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/vu.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/wf.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ws.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/xk.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/xx.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/ye.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/yt.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/za.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/zm.svg delete mode 100644 examples/Demo/FluentUI.Demo/wwwroot/flags/zw.svg diff --git a/examples/Demo/FluentUI.Demo/FluentUI.Demo.csproj b/examples/Demo/FluentUI.Demo/FluentUI.Demo.csproj index fdad3bcecf..9b9143214c 100644 --- a/examples/Demo/FluentUI.Demo/FluentUI.Demo.csproj +++ b/examples/Demo/FluentUI.Demo/FluentUI.Demo.csproj @@ -8,14 +8,6 @@ true - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/README.md b/examples/Demo/FluentUI.Demo/wwwroot/flags/README.md deleted file mode 100644 index 1dba35c4c9..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/README.md +++ /dev/null @@ -1,27 +0,0 @@ -These flag SVG files, except roc.svg, come from https://github.com/lipis/flag-icons -License: The MIT License (MIT) - -Copyright (c) 2013 Panayiotis Lipiridis - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ------------ - -roc.svg is from https://commons.wikimedia.org/wiki/File:Russian_Olympic_Committee_flag.svg -License: "Not an object of copyright" as per the above page diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ac.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ac.svg deleted file mode 100644 index ece63eaf8d..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ac.svg +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ad.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ad.svg deleted file mode 100644 index 726f981b00..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ad.svg +++ /dev/null @@ -1,150 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ae.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ae.svg deleted file mode 100644 index b7acdbdb36..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ae.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/af.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/af.svg deleted file mode 100644 index 6e755396fe..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/af.svg +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ag.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ag.svg deleted file mode 100644 index 69914138e0..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ag.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ai.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ai.svg deleted file mode 100644 index 4080e86a4c..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ai.svg +++ /dev/null @@ -1,763 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/al.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/al.svg deleted file mode 100644 index 9ec80b808a..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/al.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/am.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/am.svg deleted file mode 100644 index 99fa4dc597..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/am.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ao.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ao.svg deleted file mode 100644 index 4dc39f6aaf..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ao.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/aq.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/aq.svg deleted file mode 100644 index 53840cccb0..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/aq.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ar.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ar.svg deleted file mode 100644 index c9ed307d6a..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ar.svg +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/as.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/as.svg deleted file mode 100644 index b5836f5854..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/as.svg +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/at.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/at.svg deleted file mode 100644 index c28250887f..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/at.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/au.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/au.svg deleted file mode 100644 index 264bfb0008..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/au.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/aw.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/aw.svg deleted file mode 100644 index 32cabd5457..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/aw.svg +++ /dev/null @@ -1,186 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ax.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ax.svg deleted file mode 100644 index 0584d713b5..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ax.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/az.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/az.svg deleted file mode 100644 index 8e56ef53c2..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/az.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ba.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ba.svg deleted file mode 100644 index fcd18914a8..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ba.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bb.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bb.svg deleted file mode 100644 index 420a68852a..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/bb.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bd.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bd.svg deleted file mode 100644 index 16b794debd..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/bd.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/be.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/be.svg deleted file mode 100644 index 327f28fa2e..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/be.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bf.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bf.svg deleted file mode 100644 index 4713822584..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/bf.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bg.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bg.svg deleted file mode 100644 index b100dd0dc6..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/bg.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bh.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bh.svg deleted file mode 100644 index dee203de59..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/bh.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bi.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bi.svg deleted file mode 100644 index 1050838bc8..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/bi.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bj.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bj.svg deleted file mode 100644 index 0846724d17..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/bj.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bl.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bl.svg deleted file mode 100644 index 15803ff9ac..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/bl.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bm.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bm.svg deleted file mode 100644 index 73906f3007..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/bm.svg +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bn.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bn.svg deleted file mode 100644 index 19f15fa56b..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/bn.svg +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bo.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bo.svg deleted file mode 100644 index bc55bc3a56..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/bo.svg +++ /dev/null @@ -1,676 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bq.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bq.svg deleted file mode 100644 index 0e6bc76e62..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/bq.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/br.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/br.svg deleted file mode 100644 index 354a7013f3..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/br.svg +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bs.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bs.svg deleted file mode 100644 index 513be43ac4..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/bs.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bt.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bt.svg deleted file mode 100644 index cea6006c19..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/bt.svg +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bv.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bv.svg deleted file mode 100644 index 40e16d9482..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/bv.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bw.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bw.svg deleted file mode 100644 index a1c8db0af1..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/bw.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/by.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/by.svg deleted file mode 100644 index 8d25ee3c1f..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/by.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/bz.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/bz.svg deleted file mode 100644 index fbc6d7cbe1..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/bz.svg +++ /dev/null @@ -1,145 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ca.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ca.svg deleted file mode 100644 index 496f1a1dac..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ca.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cc.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cc.svg deleted file mode 100644 index c4457dee99..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/cc.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cd.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cd.svg deleted file mode 100644 index e106ddd532..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/cd.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cefta.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cefta.svg deleted file mode 100644 index 00d1ffeb79..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/cefta.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cf.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cf.svg deleted file mode 100644 index a6cd3670f2..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/cf.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cg.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cg.svg deleted file mode 100644 index 9128715f61..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/cg.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ch.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ch.svg deleted file mode 100644 index 9abeff4f5c..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ch.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ci.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ci.svg deleted file mode 100644 index e400f0c1cd..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ci.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ck.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ck.svg deleted file mode 100644 index 5c4684611e..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ck.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cl.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cl.svg deleted file mode 100644 index 01766fefd7..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/cl.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cm.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cm.svg deleted file mode 100644 index d06f6560ce..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/cm.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cn.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cn.svg deleted file mode 100644 index 3660d8050d..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/cn.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/co.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/co.svg deleted file mode 100644 index ebd0a0fb2d..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/co.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cp.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cp.svg deleted file mode 100644 index b3efb07429..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/cp.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cr.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cr.svg deleted file mode 100644 index 5a409eebb2..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/cr.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cu.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cu.svg deleted file mode 100644 index e8af888ed2..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/cu.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cv.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cv.svg deleted file mode 100644 index 5c251da2a9..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/cv.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cw.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cw.svg deleted file mode 100644 index 3af2bdf3cf..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/cw.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cx.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cx.svg deleted file mode 100644 index 39fa9b070c..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/cx.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cy.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cy.svg deleted file mode 100644 index b72473ab1c..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/cy.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/cz.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/cz.svg deleted file mode 100644 index 7913de3895..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/cz.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/de.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/de.svg deleted file mode 100644 index 65c00ecf1f..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/de.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/dg.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/dg.svg deleted file mode 100644 index 15a349da7a..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/dg.svg +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/dj.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/dj.svg deleted file mode 100644 index ebf2fc66f1..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/dj.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/dk.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/dk.svg deleted file mode 100644 index 563277f81d..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/dk.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/dm.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/dm.svg deleted file mode 100644 index 60457b7961..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/dm.svg +++ /dev/null @@ -1,152 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/do.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/do.svg deleted file mode 100644 index 7ff190b0d0..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/do.svg +++ /dev/null @@ -1,6745 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/dz.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/dz.svg deleted file mode 100644 index 5ff29a74a0..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/dz.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ea.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ea.svg deleted file mode 100644 index cb3feb059f..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ea.svg +++ /dev/null @@ -1,544 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ec.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ec.svg deleted file mode 100644 index 65b78858a6..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ec.svg +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ee.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ee.svg deleted file mode 100644 index 36ea288ca8..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ee.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/eg.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/eg.svg deleted file mode 100644 index 728538ba33..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/eg.svg +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/eh.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/eh.svg deleted file mode 100644 index 2848b6a4ae..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/eh.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/er.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/er.svg deleted file mode 100644 index 2705295f27..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/er.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/es-ct.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/es-ct.svg deleted file mode 100644 index 4d85911402..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/es-ct.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/es-ga.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/es-ga.svg deleted file mode 100644 index f571a8940b..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/es-ga.svg +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/es.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/es.svg deleted file mode 100644 index 8060591980..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/es.svg +++ /dev/null @@ -1,544 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/et.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/et.svg deleted file mode 100644 index a3378fd958..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/et.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/eu.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/eu.svg deleted file mode 100644 index 1bb04ecb64..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/eu.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/fi.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/fi.svg deleted file mode 100644 index 83fa02bacd..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/fi.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/fj.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/fj.svg deleted file mode 100644 index c1020ffa11..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/fj.svg +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/fk.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/fk.svg deleted file mode 100644 index c08ccd9413..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/fk.svg +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/fm.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/fm.svg deleted file mode 100644 index 85f4f47ece..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/fm.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/fo.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/fo.svg deleted file mode 100644 index 717ee20b81..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/fo.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/fr.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/fr.svg deleted file mode 100644 index 1be61911a0..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/fr.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ga.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ga.svg deleted file mode 100644 index 76edab429c..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ga.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gb-eng.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gb-eng.svg deleted file mode 100644 index 12e3b67d56..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/gb-eng.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gb-nir.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gb-nir.svg deleted file mode 100644 index 4179e8959d..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/gb-nir.svg +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gb-sct.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gb-sct.svg deleted file mode 100644 index f50cd322ac..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/gb-sct.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gb-wls.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gb-wls.svg deleted file mode 100644 index 6e15fd0158..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/gb-wls.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gb.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gb.svg deleted file mode 100644 index dbac25eae4..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/gb.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gd.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gd.svg deleted file mode 100644 index dad1107faa..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/gd.svg +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ge.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ge.svg deleted file mode 100644 index 453898b020..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ge.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gf.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gf.svg deleted file mode 100644 index f8752d9ef4..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/gf.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gg.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gg.svg deleted file mode 100644 index e40a8387c1..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/gg.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gh.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gh.svg deleted file mode 100644 index a6497de880..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/gh.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gi.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gi.svg deleted file mode 100644 index 64a69e8bf1..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/gi.svg +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gl.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gl.svg deleted file mode 100644 index eb5a52e9e4..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/gl.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gm.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gm.svg deleted file mode 100644 index 8fe9d66920..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/gm.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gn.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gn.svg deleted file mode 100644 index 40d6ad4f03..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/gn.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gp.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gp.svg deleted file mode 100644 index 1b38158882..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/gp.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gq.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gq.svg deleted file mode 100644 index ba2acf28d9..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/gq.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gr.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gr.svg deleted file mode 100644 index c74e4dd97c..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/gr.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gs.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gs.svg deleted file mode 100644 index e6ead0cefa..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/gs.svg +++ /dev/null @@ -1,242 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gt.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gt.svg deleted file mode 100644 index 24c5f3339e..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/gt.svg +++ /dev/null @@ -1,204 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gu.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gu.svg deleted file mode 100644 index c010ca0f0d..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/gu.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - G - U - A - M - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gw.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gw.svg deleted file mode 100644 index 9e0aeebd35..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/gw.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/gy.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/gy.svg deleted file mode 100644 index f4d9b8ab2b..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/gy.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/hk.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/hk.svg deleted file mode 100644 index 603ec22496..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/hk.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/hm.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/hm.svg deleted file mode 100644 index af29c6c015..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/hm.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/hn.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/hn.svg deleted file mode 100644 index 6f9295005f..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/hn.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/hr.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/hr.svg deleted file mode 100644 index 70115ae9f2..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/hr.svg +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ht.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ht.svg deleted file mode 100644 index 9cddb29324..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ht.svg +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/hu.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/hu.svg deleted file mode 100644 index baddf7f5ea..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/hu.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ic.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ic.svg deleted file mode 100644 index 81e6ee2e13..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ic.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/id.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/id.svg deleted file mode 100644 index 6a0a66be85..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/id.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ie.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ie.svg deleted file mode 100644 index 049be14de1..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ie.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/il.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/il.svg deleted file mode 100644 index 41fda79e30..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/il.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/im.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/im.svg deleted file mode 100644 index 3d597a14be..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/im.svg +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/in.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/in.svg deleted file mode 100644 index 53c29b3a96..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/in.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/io.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/io.svg deleted file mode 100644 index 15a349da7a..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/io.svg +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/iq.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/iq.svg deleted file mode 100644 index 6891785379..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/iq.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ir.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ir.svg deleted file mode 100644 index 6d5a2f578a..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ir.svg +++ /dev/null @@ -1,219 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/is.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/is.svg deleted file mode 100644 index 56cc977874..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/is.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/it.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/it.svg deleted file mode 100644 index 20a8bfdcc8..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/it.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/je.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/je.svg deleted file mode 100644 index cabef52303..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/je.svg +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/jm.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/jm.svg deleted file mode 100644 index e03a3422a6..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/jm.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/jo.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/jo.svg deleted file mode 100644 index 50802915e4..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/jo.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/jp.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/jp.svg deleted file mode 100644 index a0a6791159..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/jp.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ke.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ke.svg deleted file mode 100644 index ad190f53e1..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ke.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/kg.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/kg.svg deleted file mode 100644 index f8626b6158..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/kg.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/kh.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/kh.svg deleted file mode 100644 index 984e84e5d7..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/kh.svg +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ki.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ki.svg deleted file mode 100644 index 1697ffe8b7..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ki.svg +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/km.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/km.svg deleted file mode 100644 index 56d62c32e8..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/km.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/kn.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/kn.svg deleted file mode 100644 index 01a3a0a2ab..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/kn.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/kp.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/kp.svg deleted file mode 100644 index 94bc8e1ede..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/kp.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/kr.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/kr.svg deleted file mode 100644 index cd1aa611ac..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/kr.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/kw.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/kw.svg deleted file mode 100644 index 7ff91a8459..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/kw.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ky.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ky.svg deleted file mode 100644 index 3d77716380..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ky.svg +++ /dev/null @@ -1,225 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/kz.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/kz.svg deleted file mode 100644 index 64776c38c3..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/kz.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/la.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/la.svg deleted file mode 100644 index 9723a781ad..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/la.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/lb.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/lb.svg deleted file mode 100644 index 49650ad85c..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/lb.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/lc.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/lc.svg deleted file mode 100644 index 46bbc6cc70..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/lc.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/li.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/li.svg deleted file mode 100644 index d557d31465..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/li.svg +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/lk.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/lk.svg deleted file mode 100644 index 416c0f07f4..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/lk.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/lr.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/lr.svg deleted file mode 100644 index a31377f975..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/lr.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ls.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ls.svg deleted file mode 100644 index e701650283..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ls.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/lt.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/lt.svg deleted file mode 100644 index 90ec5d240e..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/lt.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/lu.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/lu.svg deleted file mode 100644 index c31d2bfa24..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/lu.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/lv.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/lv.svg deleted file mode 100644 index 6a9e75ec97..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/lv.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ly.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ly.svg deleted file mode 100644 index 14abcb2430..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ly.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ma.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ma.svg deleted file mode 100644 index 7ce56eff70..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ma.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mc.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mc.svg deleted file mode 100644 index 9cb6c9e8a0..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/mc.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/md.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/md.svg deleted file mode 100644 index a806572c2f..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/md.svg +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/me.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/me.svg deleted file mode 100644 index b56cce0946..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/me.svg +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mf.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mf.svg deleted file mode 100644 index 0e5ae1127e..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/mf.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mg.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mg.svg deleted file mode 100644 index 5fa2d2440d..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/mg.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mh.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mh.svg deleted file mode 100644 index 46351e541e..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/mh.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mk.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mk.svg deleted file mode 100644 index 4f5cae77ed..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/mk.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ml.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ml.svg deleted file mode 100644 index 6f6b71695c..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ml.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mm.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mm.svg deleted file mode 100644 index b2590d96d4..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/mm.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mn.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mn.svg deleted file mode 100644 index c869cf771a..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/mn.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mo.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mo.svg deleted file mode 100644 index ec8a4e142c..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/mo.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mp.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mp.svg deleted file mode 100644 index 6696fdb839..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/mp.svg +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mq.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mq.svg deleted file mode 100644 index 750b396e1d..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/mq.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mr.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mr.svg deleted file mode 100644 index e9cc291678..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/mr.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ms.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ms.svg deleted file mode 100644 index 2675022f75..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ms.svg +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mt.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mt.svg deleted file mode 100644 index 676e801c5c..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/mt.svg +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mu.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mu.svg deleted file mode 100644 index 82d7a3bec5..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/mu.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mv.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mv.svg deleted file mode 100644 index 10450f9845..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/mv.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mw.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mw.svg deleted file mode 100644 index 113aae543a..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/mw.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mx.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mx.svg deleted file mode 100644 index 421919501d..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/mx.svg +++ /dev/null @@ -1,382 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/my.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/my.svg deleted file mode 100644 index 3cee70723f..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/my.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/mz.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/mz.svg deleted file mode 100644 index eb020058bc..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/mz.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/na.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/na.svg deleted file mode 100644 index 799702e8cf..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/na.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/nc.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/nc.svg deleted file mode 100644 index a3375e4e98..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/nc.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ne.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ne.svg deleted file mode 100644 index 39a82b8277..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ne.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/nf.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/nf.svg deleted file mode 100644 index ecdb4a3bd1..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/nf.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ng.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ng.svg deleted file mode 100644 index 81eb35f78e..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ng.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ni.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ni.svg deleted file mode 100644 index 79ff9a98e7..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ni.svg +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/nl.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/nl.svg deleted file mode 100644 index 4faaf498e1..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/nl.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/no.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/no.svg deleted file mode 100644 index a5f2a152a9..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/no.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/np.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/np.svg deleted file mode 100644 index 6d63ee14a2..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/np.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/nr.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/nr.svg deleted file mode 100644 index e71ddcd8db..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/nr.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/nu.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/nu.svg deleted file mode 100644 index 4067bafff0..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/nu.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/nz.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/nz.svg deleted file mode 100644 index 561745a576..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/nz.svg +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/om.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/om.svg deleted file mode 100644 index 1c76217996..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/om.svg +++ /dev/null @@ -1,115 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/pa.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/pa.svg deleted file mode 100644 index 8dc03bc61b..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/pa.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/pe.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/pe.svg deleted file mode 100644 index eeb29a321d..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/pe.svg +++ /dev/null @@ -1,244 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/pf.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/pf.svg deleted file mode 100644 index 16374f3629..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/pf.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/pg.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/pg.svg deleted file mode 100644 index 1080add5bf..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/pg.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ph.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ph.svg deleted file mode 100644 index 3a5b5de957..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ph.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/pk.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/pk.svg deleted file mode 100644 index fa02f6a8fc..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/pk.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/pl.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/pl.svg deleted file mode 100644 index 0fa5145241..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/pl.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/pm.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/pm.svg deleted file mode 100644 index 42bfcee023..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/pm.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/pn.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/pn.svg deleted file mode 100644 index d584def2fd..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/pn.svg +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/pr.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/pr.svg deleted file mode 100644 index 3cb403b5ca..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/pr.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ps.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ps.svg deleted file mode 100644 index 82031486a8..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ps.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/pt.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/pt.svg deleted file mode 100644 index afd2e4a3eb..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/pt.svg +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/pw.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/pw.svg deleted file mode 100644 index 089cbceea2..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/pw.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/py.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/py.svg deleted file mode 100644 index bfbf01f1f9..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/py.svg +++ /dev/null @@ -1,157 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/qa.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/qa.svg deleted file mode 100644 index bd493c381c..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/qa.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/re.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/re.svg deleted file mode 100644 index 6c56aa41f6..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/re.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ro.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ro.svg deleted file mode 100644 index fda0f7bec9..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ro.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/roc.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/roc.svg deleted file mode 100644 index 396843bbda..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/roc.svg +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/rs.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/rs.svg deleted file mode 100644 index ad1a76af33..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/rs.svg +++ /dev/null @@ -1,292 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ru.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ru.svg deleted file mode 100644 index f4d27efc98..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ru.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/rw.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/rw.svg deleted file mode 100644 index 2c6c5d9035..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/rw.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sa.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sa.svg deleted file mode 100644 index 3018468eb1..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/sa.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sb.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sb.svg deleted file mode 100644 index a011360d5f..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/sb.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sc.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sc.svg deleted file mode 100644 index 65091a5cc3..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/sc.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sd.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sd.svg deleted file mode 100644 index b8e4b97357..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/sd.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/se.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/se.svg deleted file mode 100644 index 0e41780ef1..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/se.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sg.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sg.svg deleted file mode 100644 index c4dd4ac9eb..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/sg.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sh.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sh.svg deleted file mode 100644 index 9bedb0811d..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/sh.svg +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/si.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/si.svg deleted file mode 100644 index f2aea01689..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/si.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sj.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sj.svg deleted file mode 100644 index bb2799ce73..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/sj.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sk.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sk.svg deleted file mode 100644 index a1953fa67f..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/sk.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sl.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sl.svg deleted file mode 100644 index a07baf75b4..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/sl.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sm.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sm.svg deleted file mode 100644 index c9357bca6a..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/sm.svg +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sn.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sn.svg deleted file mode 100644 index 7c0673d6d6..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/sn.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/so.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/so.svg deleted file mode 100644 index ae582f198d..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/so.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sr.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sr.svg deleted file mode 100644 index 5e71c40026..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/sr.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ss.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ss.svg deleted file mode 100644 index 73804d80d5..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ss.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/st.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/st.svg deleted file mode 100644 index 2259f318f9..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/st.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sv.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sv.svg deleted file mode 100644 index 752dd3d499..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/sv.svg +++ /dev/null @@ -1,594 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sx.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sx.svg deleted file mode 100644 index 84844e0f2e..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/sx.svg +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sy.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sy.svg deleted file mode 100644 index 968f915769..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/sy.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/sz.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/sz.svg deleted file mode 100644 index f3393e5608..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/sz.svg +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ta.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ta.svg deleted file mode 100644 index 27bc3183d6..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ta.svg +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/tc.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/tc.svg deleted file mode 100644 index 09cce7b2f9..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/tc.svg +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/td.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/td.svg deleted file mode 100644 index 9fadf85a0b..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/td.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/tf.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/tf.svg deleted file mode 100644 index 4572f4ee61..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/tf.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/tg.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/tg.svg deleted file mode 100644 index e20f40d8db..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/tg.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/th.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/th.svg deleted file mode 100644 index 1e93a61e95..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/th.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/tj.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/tj.svg deleted file mode 100644 index 563c97b630..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/tj.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/tk.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/tk.svg deleted file mode 100644 index 65bab1372f..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/tk.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/tl.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/tl.svg deleted file mode 100644 index bcfc1612d7..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/tl.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/tm.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/tm.svg deleted file mode 100644 index 871e4eed3b..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/tm.svg +++ /dev/null @@ -1,206 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/tn.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/tn.svg deleted file mode 100644 index dc6d067c0d..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/tn.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/to.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/to.svg deleted file mode 100644 index d072337066..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/to.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/tpe.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/tpe.svg deleted file mode 100644 index 3862833a6f..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/tpe.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/tr.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/tr.svg deleted file mode 100644 index a92804f882..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/tr.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/tt.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/tt.svg deleted file mode 100644 index 14adbe041e..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/tt.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/tv.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/tv.svg deleted file mode 100644 index aed967d292..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/tv.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/tz.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/tz.svg deleted file mode 100644 index 751c167206..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/tz.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ua.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ua.svg deleted file mode 100644 index 789f0166cf..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ua.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ug.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ug.svg deleted file mode 100644 index 78252a42d1..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ug.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/um.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/um.svg deleted file mode 100644 index 5f2822d56e..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/um.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/un.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/un.svg deleted file mode 100644 index b04c3c43d9..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/un.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/us.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/us.svg deleted file mode 100644 index 3189d8e2dc..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/us.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/uy.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/uy.svg deleted file mode 100644 index 1634d71b70..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/uy.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/uz.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/uz.svg deleted file mode 100644 index 8c6a5324c9..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/uz.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/va.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/va.svg deleted file mode 100644 index 6a03dc4680..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/va.svg +++ /dev/null @@ -1,479 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/vc.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/vc.svg deleted file mode 100644 index 450f6f0a26..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/vc.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ve.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ve.svg deleted file mode 100644 index 77bb549e6d..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ve.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/vg.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/vg.svg deleted file mode 100644 index f18731d2f8..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/vg.svg +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/vi.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/vi.svg deleted file mode 100644 index 8a0941fa0d..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/vi.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/vn.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/vn.svg deleted file mode 100644 index 04433b989b..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/vn.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/vu.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/vu.svg deleted file mode 100644 index abd682c775..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/vu.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/wf.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/wf.svg deleted file mode 100644 index b0cc4c73d0..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/wf.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ws.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ws.svg deleted file mode 100644 index 0e758a7a95..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ws.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/xk.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/xk.svg deleted file mode 100644 index e6a63325bf..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/xk.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/xx.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/xx.svg deleted file mode 100644 index 24bfd4554c..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/xx.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/ye.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/ye.svg deleted file mode 100644 index 61f0ed6100..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/ye.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/yt.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/yt.svg deleted file mode 100644 index e84f439aac..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/yt.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/za.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/za.svg deleted file mode 100644 index 1e0b8b23bb..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/za.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/zm.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/zm.svg deleted file mode 100644 index b8fdd63cb7..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/zm.svg +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/Demo/FluentUI.Demo/wwwroot/flags/zw.svg b/examples/Demo/FluentUI.Demo/wwwroot/flags/zw.svg deleted file mode 100644 index 5bfd7dff4a..0000000000 --- a/examples/Demo/FluentUI.Demo/wwwroot/flags/zw.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - From 430abd5b39d908eee29fb8b202b8ad20ef5baac7 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Tue, 17 Jun 2025 10:27:29 +0200 Subject: [PATCH 14/44] Remove DI abstractions package. Not needed --- Directory.Packages.props | 1 - src/Core/Microsoft.FluentUI.AspNetCore.Components.csproj | 1 - 2 files changed, 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 685273bde3..9110681974 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -51,7 +51,6 @@ - diff --git a/src/Core/Microsoft.FluentUI.AspNetCore.Components.csproj b/src/Core/Microsoft.FluentUI.AspNetCore.Components.csproj index 06160b12c2..15f6d4496c 100644 --- a/src/Core/Microsoft.FluentUI.AspNetCore.Components.csproj +++ b/src/Core/Microsoft.FluentUI.AspNetCore.Components.csproj @@ -77,7 +77,6 @@ - From 33af4626430c94b35dec93d532f6c43efb9beb1f Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Tue, 17 Jun 2025 10:49:10 +0200 Subject: [PATCH 15/44] Add IsFixed parameter --- .../DataGrid/FluentDataGrid.razor.cs | 67 +++++++------------ 1 file changed, 26 insertions(+), 41 deletions(-) diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index 05128d1d9d..7df3e091de 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -47,7 +47,6 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve private string? _internalGridTemplateColumns; private PaginationState? _lastRefreshedPaginationState; private IQueryable? _lastAssignedItems; - private int _lastAssignedItemsHashCode; private GridItemsProvider? _lastAssignedItemsProvider; private CancellationTokenSource? _pendingDataLoadCancellationTokenSource; private GridItemsProviderRequest? _lastRequest; @@ -158,7 +157,7 @@ public FluentDataGrid(LibraryConfiguration configuration) : base(configuration) /// /// Gets or sets a value indicating whether column resize handles should extend the full height of the grid. - /// When true, columns can be resized by dragging from any row. When false, columns can only be resized + /// When true, columns can be resized by dragging from any row. When false, columns can only be resized /// by dragging from the column header. Default is true. /// [Parameter] @@ -376,6 +375,14 @@ public FluentDataGrid(LibraryConfiguration configuration) : base(configuration) [Parameter] public bool AutoFocus { get; set; } = false; + /// + /// Gets or sets a value indicating whether the grid's dataset is not expected to change during its lifetime. + /// When set to true, reduces automatic refresh checks for better performance with static datasets. + /// Default is false to maintain backward compatibility. + /// + [Parameter] + public bool IsFixed { get; set; } + // Returns Loading if set (controlled). If not controlled, // we assume the grid is loading until the next data load completes internal bool EffectiveLoadingValue => Loading ?? ItemsProvider is not null; @@ -411,11 +418,8 @@ protected override async Task OnParametersSetAsync() throw new InvalidOperationException($"FluentDataGrid cannot use both {nameof(Virtualize)} and {nameof(MultiLine)} at the same time."); } - var currentItemsHash = FluentDataGrid.ComputeItemsHash(Items); - var itemsChanged = currentItemsHash != _lastAssignedItemsHashCode; - // Perform a re-query only if the data source or something else has changed - var dataSourceHasChanged = itemsChanged || !Equals(ItemsProvider, _lastAssignedItemsProvider); + var dataSourceHasChanged = !Equals(ItemsProvider, _lastAssignedItemsProvider) || !ReferenceEquals(Items, _lastAssignedItems); if (dataSourceHasChanged) { if (_scope.HasValue) @@ -426,7 +430,6 @@ protected override async Task OnParametersSetAsync() _scope = ScopeFactory.CreateAsyncScope(); _lastAssignedItemsProvider = ItemsProvider; _lastAssignedItems = Items; - _lastAssignedItemsHashCode = currentItemsHash; _asyncQueryExecutor = AsyncQueryExecutorSupplier.GetAsyncQueryExecutor(_scope.Value.ServiceProvider, Items); } @@ -739,12 +742,24 @@ private async Task RefreshDataCoreAsync() if (RefreshItems is not null) { - if (_forceRefreshData || _lastRequest == null || !_lastRequest.Value.IsSameRequest(request)) + if (IsFixed) { - _forceRefreshData = false; - _lastRequest = request; - await RefreshItems.Invoke(request); + if (_forceRefreshData || _lastRequest == null) + { + _lastRequest = request; + await RefreshItems.Invoke(request); + } } + else + { + if (_forceRefreshData || _lastRequest == null || !_lastRequest.Value.IsSameRequest(request)) + { + _lastRequest = request; + await RefreshItems.Invoke(request); + } + } + + _forceRefreshData = false; } var result = await ResolveItemsRequestAsync(request); @@ -761,7 +776,6 @@ private async Task RefreshDataCoreAsync() } _internalGridContext.ResetRowIndexes(startIndex); - StateHasChanged(); } @@ -1105,34 +1119,5 @@ public async Task ResetColumnWidthsAsync() await JSModule.ObjectReference.InvokeVoidAsync("resetColumnWidths", _gridReference); } } - - /// - /// Computes a hash code for the given items. - /// To limit the effect on performance, only the given maximum number (default 250) of items will be considered. - /// - private static int ComputeItemsHash(IEnumerable? items, int maxItems = 250) - { - if (items == null) - { - return 0; - } - - unchecked - { - var hash = 19; - var count = 0; - foreach (var item in items) - { - if (++count > maxItems) - { - break; - } - - hash = (hash * 31) + (item?.GetHashCode() ?? 0); - } - - return hash; - } - } } From 44f7d0d8f1ce002aedac31ac0452fb5f5738a3ef Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Tue, 17 Jun 2025 23:25:19 +0200 Subject: [PATCH 16/44] MAke example grid mor functional and look better --- .../DataGrid/Examples/DataGridTypical.razor | 12 ++-- .../Examples/DataGridTypical.razor.css | 1 - .../DataGrid/Columns/ColumnBase.razor | 56 ++++++++++--------- .../Columns/ColumnResizeOptions.razor | 7 +-- .../DataGrid/FluentDataGrid.razor.css | 8 +-- .../DataGrid/FluentDataGridCell.razor.cs | 3 +- src/Core/Components/Icons/CoreIcons.cs | 2 + 7 files changed, 43 insertions(+), 46 deletions(-) diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor index 58c6fb387e..d3a63efe5d 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor @@ -13,10 +13,11 @@ Pagination="@pagination" RowClass="@rowClass" RowStyle="@rowStyle" + ShowHover="true" HeaderCellAsButtonWithMenu="true" ColumnResizeUISettings="@resizeLabels"> - Flag of @(context.Code) + Flag of @(context.Code) @@ -31,13 +32,8 @@
- - - -

- - - + +
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor.css b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor.css index 4ec36fa5e4..6a81a1937d 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor.css +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor.css @@ -1,7 +1,6 @@ /* Ensure all the flags are the same size, and centered */ .flag { height: 1rem; - margin-top: 4px; border: 1px solid var(--neutral-layer-3); } .search-box { diff --git a/src/Core/Components/DataGrid/Columns/ColumnBase.razor b/src/Core/Components/DataGrid/Columns/ColumnBase.razor index f9792fc34d..b3f00a9f0f 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnBase.razor +++ b/src/Core/Components/DataGrid/Columns/ColumnBase.razor @@ -29,7 +29,7 @@ { string? tooltip = Tooltip ? (HeaderTooltip ?? Title) : null; - + @if (AnyColumnActionEnabled) { @@ -64,31 +64,33 @@
} - @if (Sortable.HasValue ? Sortable.Value : IsSortableByDefault()) - { - - @GetSortOptionText() + + @if (Sortable.HasValue ? Sortable.Value : IsSortableByDefault()) + { + + @GetSortOptionText() - - } - @if (Grid.ResizeType is not null && Grid.ResizableColumns) - { - - @Grid.ColumnResizeUISettings.ResizeMenu - - } - @if (ColumnOptions is not null) - { - - @Grid.ColumnOptionsUISettings.OptionsMenu - - } + + } + @if (Grid.ResizeType is not null && Grid.ResizableColumns) + { + + @Grid.ColumnResizeUISettings.ResizeMenu + + } + @if (ColumnOptions is not null) + { + + @Grid.ColumnOptionsUISettings.OptionsMenu + + } + } @@ -204,7 +206,7 @@ { return @ - - ; + + ; } } diff --git a/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor b/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor index 22f1ada4e9..b631fd7932 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor +++ b/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor @@ -25,11 +25,10 @@ Label="@Grid.ColumnResizeUISettings.ExactLabel" AutoComplete="off" Autofocus="true" - @onkeydown="@HandleColumnWidthKeyDownAsync" /> + @onkeydown="@HandleColumnWidthKeyDownAsync" + />
- - - + } diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.css b/src/Core/Components/DataGrid/FluentDataGrid.razor.css index fb24f08d3f..d71f7f0449 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.css +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.css @@ -24,18 +24,18 @@ } .fluent-data-grid tbody tr .hover { - background: var(--colorNeutralBackground1Hover); + background: var(--colorNeutralBackground5); } .col-options, .col-resize { position: absolute; min-width: 250px; top: 2.7rem; - background: var(--colorNeutralStroke1); - border: 1px solid var(--colorNeutralStroke2); + background: var(--colorNeutralBackground1Hover); + border: 1px solid var(--colorNeutralBackground1Pressed); border-radius: 0.3rem; box-shadow: var(--shadow8); - padding: 1rem; + padding: 12px; visibility: hidden; z-index: 1; } diff --git a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs index c0cbd3b1ae..377060c3de 100644 --- a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs @@ -6,7 +6,6 @@ using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; using Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; -using Microsoft.FluentUI.AspNetCore.Components.Extensions; using Microsoft.FluentUI.AspNetCore.Components.Utilities; namespace Microsoft.FluentUI.AspNetCore.Components; @@ -88,7 +87,7 @@ public FluentDataGridCell(LibraryConfiguration configuration) : base(configurati .AddStyle("padding-top", "calc(var(--design-unit) * 1.5px)", Column is SelectColumn && Grid.RowSize == DataGridRowSize.Small && Owner.RowType == DataGridRowType.Default) .AddStyle("width", Column?.Width, !string.IsNullOrEmpty(Column?.Width) && Grid.DisplayMode == DataGridDisplayMode.Table) .AddStyle("height", $"{Grid.ItemSize.ToString(CultureInfo.InvariantCulture):0}px", () => !Grid.EffectiveLoadingValue && Grid.Virtualize) - .AddStyle("height", $"{Grid.RowSize.ToAttributeValue()}px", () => !Grid.EffectiveLoadingValue && !Grid.Virtualize && !Grid.MultiLine && (Grid.Items is not null || Grid.ItemsProvider is not null)) + .AddStyle("height", $"{(int)Grid.RowSize}px", () => !Grid.EffectiveLoadingValue && !Grid.Virtualize && !Grid.MultiLine && (Grid.Items is not null || Grid.ItemsProvider is not null)) .AddStyle("height", "100%", Grid.MultiLine) .AddStyle("min-height", "44px", Owner.RowType != DataGridRowType.Default) .AddStyle("z-index", ZIndex.DataGridHeaderPopup.ToString(CultureInfo.InvariantCulture), CellType == DataGridCellType.ColumnHeader && Grid._columns.Count > 0 && Grid.UseMenuService) diff --git a/src/Core/Components/Icons/CoreIcons.cs b/src/Core/Components/Icons/CoreIcons.cs index c2aca5135d..0c6fc957ce 100644 --- a/src/Core/Components/Icons/CoreIcons.cs +++ b/src/Core/Components/Icons/CoreIcons.cs @@ -61,6 +61,8 @@ public class QuestionCircle : Icon { public QuestionCircle() : base("QuestionCir public class RadioButton : Icon { public RadioButton() : base("RadioButton", IconVariant.Regular, IconSize.Size20, "") { } }; + public class Search : Icon { public Search() : base("Search", IconVariant.Regular, IconSize.Size20, "") { } } + public class Subtract : Icon { public Subtract() : base("Subtract", IconVariant.Regular, IconSize.Size20, "") { } } public class TableResizeColumn : Icon { public TableResizeColumn() : base("TableResizeColumn", IconVariant.Regular, IconSize.Size20, "") { } } From 6fc0809cdc40d2632bcd014d3808b363bd418244 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Wed, 18 Jun 2025 11:37:09 +0200 Subject: [PATCH 17/44] Add initial tests. They are not passing yet --- spelling.dic | 2 + .../DataGrid/Columns/SelectColumn.cs | 2 +- tests/Core/Components.Tests.csproj | 15 + .../Components/Base/ComponentBaseTests.cs | 5 +- ...t_Customized_Rendering.verified.razor.html | 32 + ..._MultiSelect_Rendering.verified.razor.html | 50 ++ ...SingleSelect_Rendering.verified.razor.html | 44 ++ ...StickySelect_Rendering.verified.razor.html | 44 ++ .../FluentDataGridColumSelectTests.razor | 738 ++++++++++++++++++ .../FluentDataGridColumSelectTests.razor.cs | 33 + .../DataGrid/FluentDataGridIsFixedTests.razor | 168 ++++ ...yColumnIndex_Ascending.verified.razor.html | 44 ++ ...ColumnIndex_Descending.verified.razor.html | 44 ++ ...yColumnTitle_Ascending.verified.razor.html | 44 ++ ...ColumnTitle_Descending.verified.razor.html | 44 ++ .../DataGrid/FluentDataGridSortByTests.razor | 87 +++ ...FluentDataGrid_Default.verified.razor.html | 25 + .../DataGrid/FluentDataGridTests.razor | 189 +++++ .../Core/Components/DataGrid/GridSortTests.cs | 93 +++ 19 files changed, 1701 insertions(+), 2 deletions(-) create mode 100644 tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Customized_Rendering.verified.razor.html create mode 100644 tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Rendering.verified.razor.html create mode 100644 tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_SingleSelect_Rendering.verified.razor.html create mode 100644 tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_SingleStickySelect_Rendering.verified.razor.html create mode 100644 tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.razor create mode 100644 tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.razor.cs create mode 100644 tests/Core/Components/DataGrid/FluentDataGridIsFixedTests.razor create mode 100644 tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Ascending.verified.razor.html create mode 100644 tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Descending.verified.razor.html create mode 100644 tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Ascending.verified.razor.html create mode 100644 tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Descending.verified.razor.html create mode 100644 tests/Core/Components/DataGrid/FluentDataGridSortByTests.razor create mode 100644 tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_Default.verified.razor.html create mode 100644 tests/Core/Components/DataGrid/FluentDataGridTests.razor create mode 100644 tests/Core/Components/DataGrid/GridSortTests.cs diff --git a/spelling.dic b/spelling.dic index 3568152b81..c273e8babb 100644 --- a/spelling.dic +++ b/spelling.dic @@ -83,3 +83,5 @@ displaymode Overscan gipr gridcell +Voituron +beforetoggle diff --git a/src/Core/Components/DataGrid/Columns/SelectColumn.cs b/src/Core/Components/DataGrid/Columns/SelectColumn.cs index f82fd185eb..a563cebfca 100644 --- a/src/Core/Components/DataGrid/Columns/SelectColumn.cs +++ b/src/Core/Components/DataGrid/Columns/SelectColumn.cs @@ -70,7 +70,7 @@ public SelectColumn() /// Gets or sets a callback when list of selected items changed. ///
[Parameter] - public EventCallback> SelectedItemsChanged { get; set; } + public EventCallback> SelectedItemsChanged { get; set; } /// /// Gets or sets the selection mode (Single, SingleSticky or Multiple). diff --git a/tests/Core/Components.Tests.csproj b/tests/Core/Components.Tests.csproj index 0ffa3aaa3e..57b5f62edb 100644 --- a/tests/Core/Components.Tests.csproj +++ b/tests/Core/Components.Tests.csproj @@ -73,4 +73,19 @@ FluentLocalizer.resx + + + + FluentDataGridSortByTests.razor + + + FluentDataGridSortByTests.razor + + + FluentDataGridSortByTests.razor + + + FluentDataGridSortByTests.razor + + diff --git a/tests/Core/Components/Base/ComponentBaseTests.cs b/tests/Core/Components/Base/ComponentBaseTests.cs index 92ca4740e7..7a94a6dd75 100644 --- a/tests/Core/Components/Base/ComponentBaseTests.cs +++ b/tests/Core/Components/Base/ComponentBaseTests.cs @@ -40,7 +40,10 @@ public class ComponentBaseTests : Bunit.TestContext { typeof(FluentTooltip), Loader.Default.WithRequiredParameter("Anchor", "MyButton").WithRequiredParameter("UseTooltipService", false)}, { typeof(FluentHighlighter), Loader.Default.WithRequiredParameter("HighlightedText", "AB").WithRequiredParameter("Text", "ABCDEF")}, { typeof(FluentKeyCode), Loader.Default.WithRequiredParameter("ChildContent", (RenderFragment)(builder => builder.AddContent(0, "MyContent"))) }, - { typeof(FluentPaginator), Loader.Default.WithRequiredParameter("State", new PaginationState()) } + { typeof(FluentPaginator), Loader.Default.WithRequiredParameter("State", new PaginationState()) }, + { typeof(FluentDataGrid<>), Loader.MakeGenericType(typeof(string)) }, + { typeof(FluentDataGridRow<>), Loader.MakeGenericType(typeof(string)) }, + { typeof(FluentDataGridCell<>), Loader.Default.WithRequiredParameter("Owner", new FluentDataGridRow(LibraryConfiguration.Empty)) }, }; /// diff --git a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Customized_Rendering.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Customized_Rendering.verified.razor.html new file mode 100644 index 0000000000..b9e14ddad6 --- /dev/null +++ b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Customized_Rendering.verified.razor.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
Name
+
+
+
+ Jean Martin
Kenji Sato
Julie Smith
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Rendering.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Rendering.verified.razor.html new file mode 100644 index 0000000000..6b9faeb27b --- /dev/null +++ b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Rendering.verified.razor.html @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
Name
+
+
+
+ + Jean Martin
+ + Kenji Sato
+ + Julie Smith
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_SingleSelect_Rendering.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_SingleSelect_Rendering.verified.razor.html new file mode 100644 index 0000000000..713f83231e --- /dev/null +++ b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_SingleSelect_Rendering.verified.razor.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
Name
+
+
+
+ + Jean Martin
+ + Kenji Sato
+ + Julie Smith
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_SingleStickySelect_Rendering.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_SingleStickySelect_Rendering.verified.razor.html new file mode 100644 index 0000000000..713f83231e --- /dev/null +++ b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_SingleStickySelect_Rendering.verified.razor.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
Name
+
+
+
+ + Jean Martin
+ + Kenji Sato
+ + Julie Smith
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.razor b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.razor new file mode 100644 index 0000000000..128f191855 --- /dev/null +++ b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.razor @@ -0,0 +1,738 @@ +@using Xunit; +@inherits Bunit.TestContext + +@code +{ + [Fact] + public void FluentDataGrid_ColumSelect_SingleSelect_Rendering() + { + IList SelectedItems = new List { People.ElementAt(1) }; + + // Arrange + var cut = Render( + @ + + + + ); + + cut.Verify(); + } + + [Fact] + public async Task FluentDataGrid_ColumSelect_SingleSelect_SelectedItems() + { + IList SelectedItems = new List(); + + // Arrange + var cut = Render( + @ + + + + ); + + // Pre-Assert + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(SelectedItems); + + // Act - Click and select Row 0 + await ClickOnRowAsync(cut, row: 0); + Assert.Single(cut.FindAll("svg[row-selected]")); + Assert.Single(SelectedItems); + + // Act - Click and select Row 1 + await ClickOnRowAsync(cut, row: 1); + Assert.Single(cut.FindAll("svg[row-selected]")); + Assert.Single(SelectedItems); + } + + + + [Fact] + public async Task FluentDataGrid_ColumSelect_Selectable_SingleSelect_SelectedItems() + { + IList SelectedItems = new List(); + + // Arrange + var cut = Render( + @ + + + + ); + + // Pre-Assert + Assert.Single(cut.FindAll("svg")); + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(SelectedItems); + + // Act - Click and select Row 0 + await ClickOnRowAsync(cut, row: 0); + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(SelectedItems); + + // Act - Click and select Row 1 + await ClickOnRowAsync(cut, row: 1); + Assert.Single(cut.FindAll("svg[row-selected]")); + Assert.Single(SelectedItems); + } + + [Fact] + public async Task FluentDataGrid_ColumSelect_SingleSelect_Property() + { + var items = new List(People).AsQueryable(); + + // Arrange + var cut = Render( + @ + + + + ); + + // Pre-Assert + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(items.Where(i => i.Selected)); + + // Act - Click and select Row 0 + await ClickOnRowAsync(cut, row: 0); + Assert.Single(cut.FindAll("svg[row-selected]")); + Assert.Single(items.Where(i => i.Selected)); + + // Act - Click and select Row 1 + await ClickOnRowAsync(cut, row: 1); + Assert.Single(cut.FindAll("svg[row-selected]")); + Assert.Single(items.Where(i => i.Selected)); + } + + [Fact] + public async Task FluentDataGrid_ColumSelect_Selectable_SingleSelect_Property() + { + var items = new List(People).AsQueryable(); + + // Arrange + var cut = Render( + @ + + + + ); + + // Pre-Assert + Assert.Single(cut.FindAll("svg")); + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(items.Where(i => i.Selected)); + + // Act - Click and select Row 0 + await ClickOnRowAsync(cut, row: 0); + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(items.Where(i => i.Selected)); + + // Act - Click and select Row 1 + await ClickOnRowAsync(cut, row: 1); + Assert.Single(cut.FindAll("svg[row-selected]")); + Assert.Single(items.Where(i => i.Selected)); + } + + [Fact] + public void FluentDataGrid_ColumSelect_SingleStickySelect_Rendering() + { + IList SelectedItems = new List { People.ElementAt(1) }; + + // Arrange + var cut = Render( + @ + + + + ); + + cut.Verify(); + } + + [Fact] + public async Task FluentDataGrid_ColumSelect_SingleStickySelect_SelectedItems() + { + IList SelectedItems = new List(); + + // Arrange + var cut = Render( + @ + + + + ); + + // Pre-Assert + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(SelectedItems); + + // Act - Click and select Row 0 + await ClickOnRowAsync(cut, row: 0); + Assert.Single(cut.FindAll("svg[row-selected]")); + Assert.Single(SelectedItems); + + // Act - Click and select Row 1 + await ClickOnRowAsync(cut, row: 1); + Assert.Single(cut.FindAll("svg[row-selected]")); + Assert.Single(SelectedItems); + } + + [Fact] + public async Task FluentDataGrid_ColumSelect_SingleStickySameItemSelect_SelectedItems() + { + IList SelectedItems = new List(); + + // Arrange + var cut = Render( + @ + + + + ); + + // Pre-Assert + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(SelectedItems); + + // Act - Click and select Row 0 + await ClickOnRowAsync(cut, row: 0); + Assert.Single(cut.FindAll("svg[row-selected]")); + Assert.Single(SelectedItems); + + // Act - Click and select Row 0 a second time + await ClickOnRowAsync(cut, row: 0); + Assert.Single(cut.FindAll("svg[row-selected]")); + Assert.Single(SelectedItems); + } + + [Fact] + public async Task FluentDataGrid_ColumSelect_Selectable_SingleStickySelect_SelectedItems() + { + IList SelectedItems = new List(); + + // Arrange + var cut = Render( + @ + + + + ); + + // Pre-Assert + Assert.Single(cut.FindAll("svg")); + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(SelectedItems); + + // Act - Click and select Row 0 + await ClickOnRowAsync(cut, row: 0); + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(SelectedItems); + + // Act - Click and select Row 1 + await ClickOnRowAsync(cut, row: 1); + Assert.Single(cut.FindAll("svg[row-selected]")); + Assert.Single(SelectedItems); + } + + [Fact] + public async Task FluentDataGrid_ColumSelect_SingleStickySelect_Property() + { + var items = new List(People).AsQueryable(); + + // Arrange + var cut = Render( + @ + + + + ); + + // Pre-Assert + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(items.Where(i => i.Selected)); + + // Act - Click and select Row 0 + await ClickOnRowAsync(cut, row: 0); + Assert.Single(cut.FindAll("svg[row-selected]")); + Assert.Single(items.Where(i => i.Selected)); + + // Act - Click and select Row 1 + await ClickOnRowAsync(cut, row: 1); + Assert.Single(cut.FindAll("svg[row-selected]")); + Assert.Single(items.Where(i => i.Selected)); + } + + [Fact] + public async Task FluentDataGrid_ColumSelect_Selectable_SingleStickySelect_Property() + { + var items = new List(People).AsQueryable(); + + // Arrange + var cut = Render( + @ + + + + ); + + // Pre-Assert + Assert.Single(cut.FindAll("svg")); + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(items.Where(i => i.Selected)); + + // Act - Click and select Row 0 + await ClickOnRowAsync(cut, row: 0); + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(items.Where(i => i.Selected)); + + // Act - Click and select Row 1 + await ClickOnRowAsync(cut, row: 1); + Assert.Single(cut.FindAll("svg[row-selected]")); + Assert.Single(items.Where(i => i.Selected)); + } + + [Fact] + public void FluentDataGrid_ColumSelect_MultiSelect_Rendering() + { + IList SelectedItems = new List { People.ElementAt(1), People.ElementAt(2) }; + + // Arrange + var cut = Render( + @ + + + + ); + + cut.Verify(); + } + + [Fact] + public async Task FluentDataGrid_ColumSelect_MultiSelect_SelectedItems() + { + IList SelectedItems = new List(); + + // Arrange + var cut = Render( + @ + + + + ); + + // Pre-Assert + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(SelectedItems); + + // Act - Click and select Row 0 + await ClickOnRowAsync(cut, row: 0); + Assert.Single(cut.FindAll("svg[row-selected]")); + Assert.Single(SelectedItems); + + // Act - Click and select Row 1 + await ClickOnRowAsync(cut, row: 1); + Assert.Equal(2, cut.FindAll("svg[row-selected]").Count); + Assert.Equal(2, SelectedItems.Count()); + + // Act - Click and unselect Row 0 + await ClickOnRowAsync(cut, row: 0); + Assert.Single(cut.FindAll("svg[row-selected]")); + Assert.Single(SelectedItems); + } + + [Fact] + public async Task FluentDataGrid_ColumSelect_Selectable_MultiSelect_SelectedItems() + { + IList SelectedItems = new List(); + + // Arrange + var cut = Render( + @ + + + + ); + + // Pre-Assert + Assert.Equal(2, cut.FindAll("svg").Count); + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(SelectedItems); + + // Act - Click and select Row 0 + await ClickOnRowAsync(cut, row: 0); + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(SelectedItems); + + // Act - Click and select Row 1 + await ClickOnRowAsync(cut, row: 1); + Assert.Single(cut.FindAll("svg[row-selected]")); + Assert.Single(SelectedItems); + + // Act - Click and unselect Row 1 + await ClickOnRowAsync(cut, row: 1); + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(SelectedItems); + } + + [Fact] + public async Task FluentDataGrid_ColumSelect_MultiSelect_Property() + { + var items = new List(People).AsQueryable(); + + // Arrange + var cut = Render( + @ + + + + ); + + // Pre-Assert + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(items.Where(i => i.Selected)); + + // Act - Click and select Row 0 + await ClickOnRowAsync(cut, row: 0); + Assert.Single(cut.FindAll("svg[row-selected]")); + Assert.Single(items.Where(i => i.Selected)); + + // Act - Click and select Row 1 + await ClickOnRowAsync(cut, row: 1); + Assert.Equal(2, cut.FindAll("svg[row-selected]").Count); + Assert.Equal(2, items.Where(i => i.Selected).Count()); + + // Act - Click and unselect Row 0 + await ClickOnRowAsync(cut, row: 0); + Assert.Single(cut.FindAll("svg[row-selected]")); + Assert.Single(items.Where(i => i.Selected)); + } + + [Fact] + public async Task FluentDataGrid_ColumSelect_Selectable_MultiSelect_Property() + { + var items = new List(People).AsQueryable(); + + // Arrange + var cut = Render( + @ + + + + ); + + // Pre-Assert + Assert.Equal(2, cut.FindAll("svg").Count); + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(items.Where(i => i.Selected)); + + // Act - Click and select Row 0 + await ClickOnRowAsync(cut, row: 0); + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(items.Where(i => i.Selected)); + + // Act - Click and select Row 1 + await ClickOnRowAsync(cut, row: 1); + Assert.Single(cut.FindAll("svg[row-selected]")); + Assert.Single(items.Where(i => i.Selected)); + + // Act - Click and unselect Row 1 + await ClickOnRowAsync(cut, row: 1); + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(items.Where(i => i.Selected)); + } + + [Fact] + public async Task FluentDataGrid_ColumSelect_MultiSelect_SelectAll_SelectedItems() + { + IList SelectedItems = new List(); + + // Arrange + var cut = Render( + @ + + + + ); + + // Pre-Assert + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(SelectedItems); + + // Act - Click on All checkbox to select all + await ClickOnAllAsync(cut); + Assert.Equal(3, cut.FindAll("svg[row-selected]").Count); + Assert.Equal(3, SelectedItems.Count()); + + // Act - Click on All checkbox to unselect all + await ClickOnAllAsync(cut); + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(SelectedItems); + } + + [Fact] + public async Task FluentDataGrid_ColumSelect_MultiSelect_SelectAll_Property() + { + var items = new List(People).AsQueryable(); + + // Arrange + var cut = Render( + @ + + + + ); + + // Pre-Assert + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(items.Where(i => i.Selected)); + + // Act - Click on All checkbox to select all + await ClickOnAllAsync(cut); + Assert.Equal(3, cut.FindAll("svg[row-selected]").Count); + Assert.Equal(3, items.Where(i => i.Selected).Count()); + + // Act - Click on All checkbox to unselect all + await ClickOnAllAsync(cut); + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(items.Where(i => i.Selected)); + } + + // [Fact] + // public void FluentDataGrid_ColumSelect_SwitchMultiToSingleSelect() + // { + // IList selectedItems = new List { People.ElementAt(1), People.ElementAt(2) }; + + // // Arrange + // var cut = Render( + // @ + // + // + // + // ); + + // // Before the switch + // Assert.Equal(2, cut.FindAll("svg[row-selected]").Count); + // Assert.Equal(2, selectedItems.Count()); + + // // Act + // cut.FindComponent>().Instance.SelectMode = DataGridSelectMode.Single; + // cut.FindComponent>().Render(); + + // var x = cut.Markup; + + // // After the switch + // Assert.Single(cut.FindAll("svg[row-selected]")); + // Assert.Single(selectedItems); + // } + + [Fact] + public async Task FluentDataGrid_ColumSelect_SelectAll_Disabled() + { + IList SelectedItems = new List(); + + // Arrange + var cut = Render( + @ + + + + ); + + // Pre-Assert + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(SelectedItems); + + // Act - Click on All checkbox to select all => should not work + await ClickOnAllAsync(cut); + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(SelectedItems); + } + + [Fact] + public void FluentDataGrid_ColumSelect_MultiSelect_Customized_Rendering() + { + IList SelectedItems = new List() { People.ElementAt(1), People.ElementAt(2) }; + + // Arrange + var cut = Render( + @ + + + @(context.AllSelected == true ? "✅" : context.AllSelected == null ? "➖" : "⬜") + + + @(SelectedItems.Contains(context) ? "✅" : " ") + + + + + ); + + cut.Verify(); + } + + [Fact] + public async Task FluentDataGrid_ColumSelect_SingleSelect_NotSelectFromEntireRow() + { + IList SelectedItems = new List(); + + // Arrange + var cut = Render( + @ + + + + ); + + // Act - Click on the second cell => no selection + await ClickOnRowAsync(cut, row: 0, col: 1); + Assert.Empty(SelectedItems); + + // Act - Click on the first cell => select the row + await ClickOnRowAsync(cut, row: 0, col: 0); + Assert.Single(SelectedItems); + Assert.Equal(1, SelectedItems.First().PersonId); + + // Act - Click on the second cell => keep the selection + await ClickOnRowAsync(cut, row: 1, col: 1); + Assert.Single(SelectedItems); + Assert.Equal(1, SelectedItems.First().PersonId); + } + + [Fact] + public async Task FluentDataGrid_ColumSelect_MultiSelect_NotSelectFromEntireRow() + { + IList SelectedItems = new List(); + + // Arrange + var cut = Render( + @ + + + + ); + + // Act - Click on the second cell => no selection + await ClickOnRowAsync(cut, row: 0, col: 1); + Assert.Empty(SelectedItems); + + // Act - Click on the first cell => select the row + await ClickOnRowAsync(cut, row: 0, col: 0); + Assert.Single(SelectedItems); + Assert.Equal(1, SelectedItems.First().PersonId); + + // Act - Click on the second cell => keep the selection + await ClickOnRowAsync(cut, row: 1, col: 1); + Assert.Single(SelectedItems); + Assert.Equal(1, SelectedItems.First().PersonId); + + // Act - Click on the first cell => select another row + await ClickOnRowAsync(cut, row: 1, col: 0); + Assert.Equal(2, SelectedItems.Count()); + Assert.Equal(1, SelectedItems.ElementAt(0).PersonId); + Assert.Equal(2, SelectedItems.ElementAt(1).PersonId); + } + + /// + /// Simulate a click on the DataGrid row number . + /// + /// + /// + /// + /// + private async Task ClickOnRowAsync(IRenderedFragment cut, int row, int? col = null) + { + if (col == null) + { + var item = cut.FindComponents>().ElementAt(row + 1); + await item.Instance.HandleOnRowClickAsync(item.Instance.RowId); + cut.FindComponent>().Render(); + } + else + { + var item = cut.FindComponents>() + .Where(i => i.Instance.GridColumn == col + 1) + .ElementAt(row + 1); + await item.Instance.HandleOnCellClickAsync(); + cut.FindComponent>().Render(); + } + } + + /// + /// Simulate a click on the All Checkbox. + /// + /// + /// + private async Task ClickOnAllAsync(IRenderedFragment cut) + { + var col = cut.FindComponent>(); + await col.Instance.OnClickAllAsync(new MouseEventArgs()); + cut.FindComponent>().Render(); + } +} diff --git a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.razor.cs b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.razor.cs new file mode 100644 index 0000000000..de8c8df1c1 --- /dev/null +++ b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.razor.cs @@ -0,0 +1,33 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +using Bunit; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.FluentUI.AspNetCore.Components.Tests.Components.DataGrid; + +public partial class FluentDataGridColumSelectTests : TestContext +{ + public FluentDataGridColumSelectTests() + { + JSInterop.Mode = JSRuntimeMode.Loose; + Services.AddSingleton(LibraryConfiguration.Empty); + + // Register Service + var keycodeService = new KeyCodeService(); + Services.AddScoped(factory => keycodeService); + } + + private record Person(int PersonId, string Name, DateOnly BirthDate) + { + public bool Selected { get; set; } + }; + + private readonly IQueryable People = new[] + { + new Person(1, "Jean Martin", new DateOnly(1985, 3, 16)), + new Person(2, "Kenji Sato", new DateOnly(2004, 1, 9)), + new Person(3, "Julie Smith", new DateOnly(1958, 10, 10)), + }.AsQueryable(); +} diff --git a/tests/Core/Components/DataGrid/FluentDataGridIsFixedTests.razor b/tests/Core/Components/DataGrid/FluentDataGridIsFixedTests.razor new file mode 100644 index 0000000000..4e41d84faf --- /dev/null +++ b/tests/Core/Components/DataGrid/FluentDataGridIsFixedTests.razor @@ -0,0 +1,168 @@ +@using Xunit +@inherits Bunit.TestContext + +@code { + public FluentDataGridIsFixedTests() + { + var dataGridModule = JSInterop.SetupModule("./_content/Microsoft.FluentUI.AspNetCore.Components/Components/DataGrid/FluentDataGrid.razor.js"); + dataGridModule.SetupModule("init", _ => true); + + // Register services + Services.AddSingleton(LibraryConfiguration.Empty); + Services.AddScoped(factory => new KeyCodeService()); + } + + [Fact] + public void FluentDataGrid_IsFixed_Default_Value_Is_False() + { + // Arrange && Act + var cut = Render>( + @ + + + + ); + + // Assert + var dataGrid = cut.Instance; + Assert.False(dataGrid.IsFixed); + } + + [Fact] + public void FluentDataGrid_IsFixed_Can_Be_Set_To_True() + { + // Arrange && Act + var cut = Render>( + @ + + + + ); + + // Assert + var dataGrid = cut.Instance; + Assert.True(dataGrid.IsFixed); + } + + [Fact] + public void FluentDataGrid_IsFixed_True_Allows_Data_Changes_Without_Automatic_Refresh() + { + // Arrange + var items = GetCustomers().AsQueryable(); + + var cut = Render>( + @ + + + + ); + + var dataGrid = cut.Instance; + + // Act - Update items (simulating data change) + var newItems = GetCustomers().Concat(new[] { new Customer(4, "New Customer") }).AsQueryable(); + cut.SetParametersAndRender(parameters => parameters + .Add(p => p.Items, newItems)); + + // Assert - With IsFixed=true, the grid should still work correctly + Assert.True(dataGrid.IsFixed); + } + + [Fact] + public async Task FluentDataGrid_IsFixed_True_Still_Allows_Pagination() + { + // Arrange + var pagination = new PaginationState { ItemsPerPage = 2 }; + + var cut = Render>( + @ + + + + ); + + // Act - Change pagination + await cut.InvokeAsync(() => pagination.SetCurrentPageIndexAsync(1)); + + // Assert - Should still work with IsFixed=true + Assert.Equal(1, pagination.CurrentPageIndex); + } + + [Fact] + public async Task FluentDataGrid_IsFixed_False_Allows_Normal_Refresh_Behavior() + { + // Arrange + var refreshCallCount = 0; + async ValueTask> GetItems(GridItemsProviderRequest request) + { + refreshCallCount++; + await Task.Delay(1); // Simulate async work + return GridItemsProviderResult.From( + GetCustomers().ToArray(), + GetCustomers().Count()); + } + + var cut = Render>( + @ + + + + ); + + // Wait for initial load + await Task.Delay(100, Xunit.TestContext.Current.CancellationToken); + var dataGrid = cut.Instance; + + // Act - Explicitly refresh + await cut.InvokeAsync(() => dataGrid.RefreshDataAsync(force: true)); + await Task.Delay(100, Xunit.TestContext.Current.CancellationToken); + + // Assert - With IsFixed=false, explicit refresh should still work + Assert.True(refreshCallCount >= 2, + $"Expected at least 2 refresh calls (initial + explicit). Got {refreshCallCount} calls."); + } + + [Fact] + public async Task FluentDataGrid_IsFixed_True_Still_Allows_Explicit_Refresh() + { + // Arrange + var refreshCallCount = 0; + async ValueTask> GetItems(GridItemsProviderRequest request) + { + refreshCallCount++; + await Task.Delay(1); // Simulate async work + return GridItemsProviderResult.From( + GetCustomers().ToArray(), + GetCustomers().Count()); + } + + var cut = Render>( + @ + + + + ); + + // Wait for initial load + await Task.Delay(100, Xunit.TestContext.Current.CancellationToken); + var dataGrid = cut.Instance; + + // Act - Explicitly refresh even with IsFixed=true + await cut.InvokeAsync(() => dataGrid.RefreshDataAsync(force: true)); + await Task.Delay(100, Xunit.TestContext.Current.CancellationToken); + + // Assert - Explicit refresh should still work with IsFixed=true + Assert.True(refreshCallCount >= 2, + $"Expected at least 2 refresh calls (initial + explicit). Got {refreshCallCount} calls."); + } + + // Sample data... + private IEnumerable GetCustomers() + { + yield return new Customer(1, "Denis Voituron"); + yield return new Customer(2, "Vincent Baaij"); + yield return new Customer(3, "Bill Gates"); + } + + private record Customer(int Id, string Name); +} diff --git a/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Ascending.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Ascending.verified.razor.html new file mode 100644 index 0000000000..246053f255 --- /dev/null +++ b/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Ascending.verified.razor.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
Item1
+ +
+
+
+
+
+
+
Item2
+
+
+
AD
BC
CB
DA
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Descending.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Descending.verified.razor.html new file mode 100644 index 0000000000..4877c81b50 --- /dev/null +++ b/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Descending.verified.razor.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
Item1
+ +
+
+
+
+
+
+
Item2
+
+
+
DA
CB
BC
AD
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Ascending.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Ascending.verified.razor.html new file mode 100644 index 0000000000..246053f255 --- /dev/null +++ b/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Ascending.verified.razor.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
Item1
+ +
+
+
+
+
+
+
Item2
+
+
+
AD
BC
CB
DA
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Descending.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Descending.verified.razor.html new file mode 100644 index 0000000000..4877c81b50 --- /dev/null +++ b/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Descending.verified.razor.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
Item1
+ +
+
+
+
+
+
+
Item2
+
+
+
DA
CB
BC
AD
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridSortByTests.razor b/tests/Core/Components/DataGrid/FluentDataGridSortByTests.razor new file mode 100644 index 0000000000..27e0f41148 --- /dev/null +++ b/tests/Core/Components/DataGrid/FluentDataGridSortByTests.razor @@ -0,0 +1,87 @@ +@using Bunit +@using Xunit + +@inherits Bunit.TestContext + +@code { + + public FluentDataGridSortByTests() + { + JSInterop.Mode = JSRuntimeMode.Loose; + Services.AddSingleton(LibraryConfiguration.Empty); + + // Register Service + var keycodeService = new KeyCodeService(); + Services.AddScoped(factory => keycodeService); + } + + private readonly IQueryable<(string, string)> _items = new List<(string, string)> { ("B", "C"), ("A", "D"), ("D", "A"), ("C", "B") }.AsQueryable(); + + [Fact] + public async Task DataGridSortByTests_SortByColumnTitle_Ascending() + { + string[] expected = ["A", "B", "C", "D"]; + FluentDataGrid<(string, string)> _dataGrid = null!; + + var cut = Render( + @ + + + ); + + await cut.InvokeAsync(() => _dataGrid.SortByColumnAsync("Item1", DataGridSortDirection.Ascending)); + + cut.Verify(); + } + + [Fact] + public async Task DataGridSortByTests_SortByColumnTitle_Descending() + { + string[] expected = ["D", "C", "B", "A"]; + FluentDataGrid<(string, string)> _dataGrid = null!; + + var cut = Render( + @ + + + ); + + await cut.InvokeAsync(() => _dataGrid.SortByColumnAsync("Item1", DataGridSortDirection.Descending)); + + cut.Verify(); + } + + [Fact] + public async Task DataGridSortByTests_SortByColumnIndex_Ascending() + { + string[] expected = ["A", "B", "C", "D"]; + FluentDataGrid<(string, string)> _dataGrid = null!; + + var cut = Render( + @ + + + ); + + await cut.InvokeAsync(() => _dataGrid.SortByColumnAsync(0, DataGridSortDirection.Ascending)); + + cut.Verify(); + } + + [Fact] + public async Task DataGridSortByTests_SortByColumnIndex_Descending() + { + string[] expected = ["D", "C", "B", "A"]; + FluentDataGrid<(string, string)> _dataGrid = null!; + + var cut = Render( + @ + + + ); + + await cut.InvokeAsync(() => _dataGrid.SortByColumnAsync(0, DataGridSortDirection.Descending)); + + cut.Verify(); + } +} diff --git a/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_Default.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_Default.verified.razor.html new file mode 100644 index 0000000000..fd82a98758 --- /dev/null +++ b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_Default.verified.razor.html @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + +
+
+
+
Name
+
+
+
Denis Voituron
Vincent Baaij
Bill Gates
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridTests.razor b/tests/Core/Components/DataGrid/FluentDataGridTests.razor new file mode 100644 index 0000000000..82dcd9f0e9 --- /dev/null +++ b/tests/Core/Components/DataGrid/FluentDataGridTests.razor @@ -0,0 +1,189 @@ +@using Xunit +@inherits Bunit.TestContext + +@code { + public FluentDataGridTests() + { + var dataGridModule = JSInterop.SetupModule("./_content/Microsoft.FluentUI.AspNetCore.Components/Components/DataGrid/FluentDataGrid.razor.js"); + dataGridModule.SetupModule("init", _ => true); + + // Register services + Services.AddSingleton(LibraryConfiguration.Empty); + Services.AddScoped(factory => new KeyCodeService()); + } + + [Fact] + public void FluentDataGrid_Default() + { + // Arrange && Act + var cut = Render>( + @ + + + +

empty content

+
); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentDataGrid_ResizeColumnOnAllRows_Default() + { + // Arrange && Act + var cut = Render>( + @ + + + + ); + + // Assert + var component = cut.Instance; + Assert.True(component.ResizeColumnOnAllRows); // Default should be true + } + + [Fact] + public void FluentDataGrid_ResizeColumnOnAllRows_False() + { + // Arrange && Act + var cut = Render>( + @ + + + + ); + + // Assert + var component = cut.Instance; + Assert.False(component.ResizeColumnOnAllRows); + } + + [Fact] + public void FluentDataGrid_With_Empty_Items_Stays_Loading_Until_Changed() + { + // Arrange && Act + var cut = Render>( + @ +

empty content

+

loading content

+ + + +
); + + // Assert + Assert.NotNull(cut.Find("#loading-content")); + Assert.Throws(() => cut.Find("#empty-content")); + + cut.SetParametersAndRender(parameters => parameters + .Add(p => p.Loading, false)); + + Assert.Throws(() => cut.Find("#loading-content")); + Assert.NotNull(cut.Find("#empty-content")); + } + + [Fact] + public async Task FluentDataGrid_With_ItemProvider_Stays_Loading_Until_ChangedAsync() + { + ValueTask> GetItems(GridItemsProviderRequest request) + { + return ValueTask.FromResult(GridItemsProviderResult.From( + Array.Empty(), + 0)); + } + + var cut = Render>( + @ +

empty content

+

loading content

+ + +

@context.Name

+
+
+
); + + // Assert + var dataGrid = cut.Instance; + Assert.NotNull(cut.Find("#loading-content")); + + // should stay loading even after data refresh + await cut.InvokeAsync(() => dataGrid.RefreshDataAsync()); + Assert.NotNull(cut.Find("#loading-content")); + + // now not loading but still with 0 items, should render empty content + cut.SetParametersAndRender(parameters => parameters + .Add(p => p.Loading, false)); + + Assert.NotNull(cut.Find("#empty-content")); + } + + [Fact] + public async Task FluentDataGrid_With_ItemProvider_And_Uncontrolled_Loading_Starts_Loading() + { + var tcs = new TaskCompletionSource(); + async ValueTask> GetItems(GridItemsProviderRequest request) + { + await tcs.Task; + var numberOfItems = 1; + return GridItemsProviderResult.From( + GetCustomers().Take(numberOfItems).ToArray(), + numberOfItems); + } + + var cut = Render>( + @ +

empty content

+

loading content

+ + +

@context.Name

+
+
+
); + + // Assert + var dataGrid = cut.Instance; + + // Data is still loading, so loading content should be displayed + Assert.NotNull(cut.Find("#loading-content")); + + tcs.SetResult(); + + // Data is no longer loading, so loading content should not be displayed after re-render + // wait for re-render here + cut.WaitForState(() => cut.Find("p").TextContent == GetCustomers().First().Name); + + Assert.Throws(() => cut.Find("#loading-content")); + + // should stay not loading even after data refresh + await cut.InvokeAsync(() => dataGrid.RefreshDataAsync()); + Assert.Throws(() => cut.Find("#loading-content")); + + // if we explicitly set Loading back to null, we should see the same behaviors because data should + // be refreshed + tcs = new TaskCompletionSource(); + cut.SetParametersAndRender(parameters => parameters + .Add(p => p.Loading, null)); + Assert.NotNull(cut.Find("#loading-content")); + + tcs.SetResult(); + + cut.WaitForState(() => cut.Find("p").TextContent == GetCustomers().First().Name); + Assert.Throws(() => cut.Find("#loading-content")); + } + + // Sample data... + private IEnumerable GetCustomers() + { + yield return new Customer(1, "Denis Voituron"); + yield return new Customer(2, "Vincent Baaij"); + yield return new Customer(3, "Bill Gates"); + } + + private record Customer(int Id, string Name); +} diff --git a/tests/Core/Components/DataGrid/GridSortTests.cs b/tests/Core/Components/DataGrid/GridSortTests.cs new file mode 100644 index 0000000000..2730460f55 --- /dev/null +++ b/tests/Core/Components/DataGrid/GridSortTests.cs @@ -0,0 +1,93 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ +using Xunit; + +namespace Microsoft.FluentUI.AspNetCore.Components.Tests.DataGrid; + +public class GridSortTests : Bunit.TestContext +{ + private static readonly GridRow[] _gridData = [ + new(2, "B"), + new(1, "A"), + new(4, "B"), + new(3, "A") + ]; + +#pragma warning disable CA1861 // Avoid constant arrays as arguments + + [Theory] + [InlineData(true, new int[] { 1, 2, 3, 4 })] + [InlineData(false, new int[] { 4, 3, 2, 1 })] + public void GridSortTests_SortBy_Number(bool ascending, IList expected) + { + var sort = GridSort.ByAscending(x => x.Number); + var ordered = sort.Apply(_gridData.AsQueryable(), ascending); + + Assert.True(ordered.Select(x => x.Number).SequenceEqual(expected)); + } + + [Theory] + [InlineData(true, new int[] { 1, 3, 2, 4 })] + [InlineData(false, new int[] { 4, 2, 3, 1 })] + public void GridSortTests_SortBy_GroupThenNumberAscending(bool ascending, IList expected) + { + var sort = GridSort + .ByAscending(x => x.Group) + .ThenAscending(x => x.Number); + + var ordered = sort.Apply(_gridData.AsQueryable(), ascending); + + Assert.True(ordered.Select(x => x.Number).SequenceEqual(expected)); + } + + [Theory] + [InlineData(true, new int[] { 3, 1, 4, 2 })] + [InlineData(false, new int[] { 2, 4, 1, 3 })] + public void GridSortTests_SortBy_GroupThenNumberDescending(bool ascending, IList expected) + { + var sort = GridSort + .ByAscending(x => x.Group) + .ThenDescending(x => x.Number); + + var ordered = sort.Apply(_gridData.AsQueryable(), ascending); + + Assert.True(ordered.Select(x => x.Number).SequenceEqual(expected)); + } + + [Theory] + [InlineData(true, new int[] { 1, 3, 2, 4 })] + [InlineData(false, new int[] { 2, 4, 1, 3 })] + public void GridSortTests_SortBy_GroupThenNumberAlwaysAscending(bool ascending, IList expected) + { + var sort = GridSort + .ByAscending(x => x.Group) + .ThenAlwaysAscending(x => x.Number); + + var ordered = sort.Apply(_gridData.AsQueryable(), ascending); + + Assert.True(ordered.Select(x => x.Number).SequenceEqual(expected)); + } + + [Theory] + [InlineData(true, new int[] { 3, 1, 4, 2 })] + [InlineData(false, new int[] { 4, 2, 3, 1 })] + public void GridSortTests_SortBy_GroupThenNumberAlwaysDescending(bool ascending, IList expected) + { + var sort = GridSort + .ByAscending(x => x.Group) + .ThenAlwaysDescending(x => x.Number); + + var ordered = sort.Apply(_gridData.AsQueryable(), ascending); + + Assert.True(ordered.Select(x => x.Number).SequenceEqual(expected)); + } + +#pragma warning restore CA1861 // Avoid constant arrays as arguments + + public class GridRow(int number, string group) + { + public int Number { get; } = number; + public string Group { get; } = group; + } +} From 5bcb9fa14b86967039caed0c011346def6653df8 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Wed, 18 Jun 2025 12:04:46 +0200 Subject: [PATCH 18/44] Add surpression atribute, fix other analyzer warnings --- src/Core/Components/DataGrid/FluentDataGrid.razor.cs | 4 ++++ src/Core/Components/DataGrid/FluentDataGridCell.razor.cs | 2 +- src/Core/Components/DataGrid/GridItemsProviderRequest.cs | 4 ++-- .../DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs | 2 +- tests/Core/Components/DataGrid/GridSortTests.cs | 2 +- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index 7df3e091de..fb1521dc22 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -18,6 +18,8 @@ namespace Microsoft.FluentUI.AspNetCore.Components; ///
/// The type of data represented by each row in the grid. [CascadingTypeParameter(nameof(TGridItem))] + +[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "MA0040:Forward the CancellationToken parameter to methods that take one", Justification = "The available cancellation token are not appropriate to pass along.")] public partial class FluentDataGrid : FluentComponentBase, IHandleEvent, IAsyncDisposable { private const string JAVASCRIPT_FILE = FluentJSModule.JAVASCRIPT_ROOT + "DataGrid/FluentDataGrid.razor.js"; @@ -446,6 +448,7 @@ protected override async Task OnParametersSetAsync() } /// + protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender && _gridReference is not null) @@ -458,6 +461,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) _jsEventDisposable = await JSModule.ObjectReference.InvokeAsync("init", _gridReference, AutoFocus); if (AutoItemsPerPage) { + await JSModule.ObjectReference.InvokeVoidAsync("dynamicItemsPerPage", _gridReference, DotNetObjectReference.Create(this), (int)RowSize); } } diff --git a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs index 377060c3de..786fd227c9 100644 --- a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs @@ -87,7 +87,7 @@ public FluentDataGridCell(LibraryConfiguration configuration) : base(configurati .AddStyle("padding-top", "calc(var(--design-unit) * 1.5px)", Column is SelectColumn && Grid.RowSize == DataGridRowSize.Small && Owner.RowType == DataGridRowType.Default) .AddStyle("width", Column?.Width, !string.IsNullOrEmpty(Column?.Width) && Grid.DisplayMode == DataGridDisplayMode.Table) .AddStyle("height", $"{Grid.ItemSize.ToString(CultureInfo.InvariantCulture):0}px", () => !Grid.EffectiveLoadingValue && Grid.Virtualize) - .AddStyle("height", $"{(int)Grid.RowSize}px", () => !Grid.EffectiveLoadingValue && !Grid.Virtualize && !Grid.MultiLine && (Grid.Items is not null || Grid.ItemsProvider is not null)) + .AddStyle("height", $"{((int)Grid.RowSize).ToString(CultureInfo.InvariantCulture)}px", () => !Grid.EffectiveLoadingValue && !Grid.Virtualize && !Grid.MultiLine && (Grid.Items is not null || Grid.ItemsProvider is not null)) .AddStyle("height", "100%", Grid.MultiLine) .AddStyle("min-height", "44px", Owner.RowType != DataGridRowType.Default) .AddStyle("z-index", ZIndex.DataGridHeaderPopup.ToString(CultureInfo.InvariantCulture), CellType == DataGridCellType.ColumnHeader && Grid._columns.Count > 0 && Grid.UseMenuService) diff --git a/src/Core/Components/DataGrid/GridItemsProviderRequest.cs b/src/Core/Components/DataGrid/GridItemsProviderRequest.cs index c03ac236af..e1f6e74db1 100644 --- a/src/Core/Components/DataGrid/GridItemsProviderRequest.cs +++ b/src/Core/Components/DataGrid/GridItemsProviderRequest.cs @@ -29,7 +29,7 @@ public readonly struct GridItemsProviderRequest public ColumnBase? SortByColumn { get; init; } /// - /// Gets or sets thecurrent sort direction. + /// Gets or sets the current sort direction. /// /// Rather than inferring the sort rules manually, you should normally call either /// or , since they also account for and automatically. @@ -65,7 +65,7 @@ public IQueryable ApplySorting(IQueryable source) => /// /// A collection of (property name, direction) pairs representing the sorting rules public IReadOnlyCollection GetSortByProperties() => - SortByColumn?.SortBy?.ToPropertyList(SortByAscending) ?? Array.Empty(); + SortByColumn?.SortBy?.ToPropertyList(SortByAscending) ?? []; /// /// Determines whether the specified request is equivalent to the current request. diff --git a/src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs b/src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs index 63aadb36fd..ad2844277b 100644 --- a/src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs +++ b/src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs @@ -60,5 +60,5 @@ internal static class AsyncQueryExecutorSupplier // reference the adapter. Trimming won't cause us any problems because this is only a way of detecting misconfiguration // so it's sufficient if it can detect the misconfiguration in development. private static bool IsEntityFrameworkProviderType([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type queryableProviderType) - => queryableProviderType.GetInterfaces().Any(x => string.Equals(x.FullName, "Microsoft.EntityFrameworkCore.Query.IAsyncQueryProvider", StringComparison.Ordinal)) == true; + => queryableProviderType.GetInterfaces().Any(x => string.Equals(x.FullName, "Microsoft.EntityFrameworkCore.Query.IAsyncQueryProvider", StringComparison.Ordinal)); } diff --git a/tests/Core/Components/DataGrid/GridSortTests.cs b/tests/Core/Components/DataGrid/GridSortTests.cs index 2730460f55..8218cef762 100644 --- a/tests/Core/Components/DataGrid/GridSortTests.cs +++ b/tests/Core/Components/DataGrid/GridSortTests.cs @@ -3,7 +3,7 @@ // ------------------------------------------------------------------------ using Xunit; -namespace Microsoft.FluentUI.AspNetCore.Components.Tests.DataGrid; +namespace Microsoft.FluentUI.AspNetCore.Components.Tests.Components.DataGrid; public class GridSortTests : Bunit.TestContext { From a30dabf3ed35b33fa3a1725b07a7ed2599e1f84b Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Wed, 18 Jun 2025 14:37:09 +0200 Subject: [PATCH 19/44] Use localized strings --- .../DataGrid/Examples/DataGridTypical.razor | 9 +---- .../Components/DataGrid/FluentDataGrid.md | 18 +++++++++ .../Migration/MigrationFluentDataGrid.md | 4 ++ examples/Demo/FluentUI.Demo/MyLocalizer.cs | 9 ++++- spelling.dic | 1 + .../DataGrid/Columns/ColumnBase.razor | 14 +++---- .../DataGrid/Columns/ColumnBase.razor.cs | 9 +++-- .../Columns/ColumnOptionsUISettings.cs | 11 ++--- .../Columns/ColumnResizeOptions.razor | 17 ++++---- .../Columns/ColumnResizeOptions.razor.cs | 4 ++ .../Columns/ColumnResizeUISettings.cs | 40 +------------------ .../DataGrid/Columns/ColumnSortUISettings.cs | 22 ++-------- .../DataGrid/FluentDataGrid.razor.cs | 9 +++-- src/Core/Localization/LanguageResource.resx | 33 +++++++++++++++ 14 files changed, 103 insertions(+), 97 deletions(-) diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor index d3a63efe5d..81d683d11d 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor @@ -14,8 +14,7 @@ RowClass="@rowClass" RowStyle="@rowStyle" ShowHover="true" - HeaderCellAsButtonWithMenu="true" - ColumnResizeUISettings="@resizeLabels"> + HeaderCellAsButtonWithMenu="true"> Flag of @(context.Code) @@ -57,12 +56,6 @@ int minMedals; int maxMedals = 130; - ColumnResizeUISettings resizeLabels = ColumnResizeUISettings.Default with - { - DiscreteLabel = "Width (+/- 10px)", - ResetAriaLabel = "Restore" - }; - GridSort rankSort = GridSort .ByDescending(x => x.Medals.Gold) .ThenDescending(x => x.Medals.Silver) diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md index 0b7428ed7f..60a43b0329 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md @@ -7,4 +7,22 @@ route: /DataGrid Overview page and examples to follow +## Typical usage +Here is an example of a data grid that uses in-memory data and enables features including pagination, sorting, filtering, column options, row highlighting and column resizing. + +All columns, except 'Bronze', have a `Tooltip` parameter value of `true`. + +- When using this for a `TemplateColumn` (like 'Rank' here), you need to also supply a value for the `TooltipText` parameter. **No value given equals no tooltip shown**. +- When using this for a `PropertyColumn`, a value for the `TooltipText` is **not** required. By default, the value given for `Property` +will be re-used for the tooltip. If you do supply a value for `TooltipText` its outcome will be used instead. + +`TooltipText` is a lambda function that takes the current item as input and returns the text to show in the tooltip (and `aria-label`). +Look at the Razor tab to see how this is done and how it can be customized. + +The Country filter option can be used to quickly filter the list of countries shown. Pressing the ESC key just closes the option popup without changing the filtering currently being used. +Pressing enter finishes the filter action by the current input to filter on and closes the option popup. + +The resize options UI is using a customized string for the label ('Width (+/- 10px)' instead of the normal 'Column width'). This is done through +the custom localizer which is registered in the Server project's `Program.cs` file. + {{ DataGridTypical }} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/GetStarted/Migration/MigrationFluentDataGrid.md b/examples/Demo/FluentUI.Demo.Client/Documentation/GetStarted/Migration/MigrationFluentDataGrid.md index e84f84971f..2ed090259d 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/GetStarted/Migration/MigrationFluentDataGrid.md +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/GetStarted/Migration/MigrationFluentDataGrid.md @@ -3,6 +3,10 @@ - `ColumnResizeLabels` has been renamed to `ColumnResizeUISettings` - `ColumnSortLabels` has been renamed to `ColumnSortUISettings` +These `...UISettings` parameters are now only used to set a custom icon and icon position. All labels that could be set in earlier versions +have now been replaced with our standard Localization capabilities. You can use a custom localizer to set custom labels for these UI settings. +An example of this can be found in the `Server` project of the demo application, where a custom localizer is registered in the `Program.cs` file. + ### Enum changes - `Align` has been renamed to `HorizontalAlignment` - `GenerateHeaderOption` has been renamed to `DataGridGeneratedHeaderType` diff --git a/examples/Demo/FluentUI.Demo/MyLocalizer.cs b/examples/Demo/FluentUI.Demo/MyLocalizer.cs index 4c9c36c986..7957461620 100644 --- a/examples/Demo/FluentUI.Demo/MyLocalizer.cs +++ b/examples/Demo/FluentUI.Demo/MyLocalizer.cs @@ -31,9 +31,14 @@ internal class MyLocalizer : IFluentLocalizer _ => IFluentLocalizer.GetDefault(key, arguments), }; } + // Provide custom translations based on the key + return key switch + { + "DataGrid_ResizeDiscrete" => "Width (+/- 10px)", - // By default, returns the English version of the string - return IFluentLocalizer.GetDefault(key, arguments); + // Fallback to the Default/English if no translation is found + _ => IFluentLocalizer.GetDefault(key, arguments), + }; } } } diff --git a/spelling.dic b/spelling.dic index c273e8babb..f523fe7494 100644 --- a/spelling.dic +++ b/spelling.dic @@ -85,3 +85,4 @@ gipr gridcell Voituron beforetoggle +resx diff --git a/src/Core/Components/DataGrid/Columns/ColumnBase.razor b/src/Core/Components/DataGrid/Columns/ColumnBase.razor index b3f00a9f0f..42933b280f 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnBase.razor +++ b/src/Core/Components/DataGrid/Columns/ColumnBase.razor @@ -69,7 +69,7 @@ { + IconEnd="@(Grid.ColumnSortUISettings.Icon is not null && !Grid.ColumnSortUISettings.IconPositionStart ? Grid.ColumnSortUISettings.Icon : null)"> @GetSortOptionText() @@ -78,16 +78,16 @@ { - @Grid.ColumnResizeUISettings.ResizeMenu + IconEnd="@(Grid.ColumnResizeUISettings.Icon is not null && !Grid.ColumnResizeUISettings.IconPositionStart ? Grid.ColumnResizeUISettings.Icon : null)"> + @Localizer[Localization.LanguageResource.DataGrid_ResizeMenu] } @if (ColumnOptions is not null) { - @Grid.ColumnOptionsUISettings.OptionsMenu + IconEnd="@(Grid.ColumnOptionsUISettings.Icon is not null && !Grid.ColumnOptionsUISettings.IconPositionStart ? Grid.ColumnOptionsUISettings.Icon : null)"> + @Localizer[Localization.LanguageResource.DataGrid_OptionsMenu] } @@ -197,7 +197,7 @@ private RenderFragment OptionsButton() { return - @ + @ ; } @@ -205,7 +205,7 @@ private RenderFragment FilterButton() { return - @ + @ ; } diff --git a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs index f9557dbeac..9b1f3fa0d5 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs @@ -22,6 +22,9 @@ public abstract partial class ColumnBase private readonly string _columnId = Identifier.NewId(); private FluentMenu? _menu; + [Inject] + private IFluentLocalizer Localizer { get; set; } = default!; + [CascadingParameter] internal InternalGridContext InternalGridContext { get; set; } = default!; @@ -347,12 +350,12 @@ private string GetSortOptionText() { if (Grid.SortByAscending is true) { - return Grid.ColumnSortUISettings.SortMenuAscendingLabel; + return Localizer[Localization.LanguageResource.DataGrid_SortMenuAscending]; } - return Grid.ColumnSortUISettings.SortMenuDescendingLabel; + return Localizer[Localization.LanguageResource.DataGrid_SortMenuDescending]; } - return Grid.ColumnSortUISettings.SortMenu; + return Localizer[Localization.LanguageResource.DataGrid_SortMenu]; } } diff --git a/src/Core/Components/DataGrid/Columns/ColumnOptionsUISettings.cs b/src/Core/Components/DataGrid/Columns/ColumnOptionsUISettings.cs index 2de7dc1443..095f65eecf 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnOptionsUISettings.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnOptionsUISettings.cs @@ -4,17 +4,12 @@ namespace Microsoft.FluentUI.AspNetCore.Components; /// -/// Represents the UI settings for column options (usually filtering), including labels, aria attributes, and menu options in the . +/// Represents the UI settings for column options (usually filtering). /// -/// This type provides customizable settings for the column options menu, including the menu text, icon, +/// This type provides customizable settings for the column options menu icon, /// and icon positioning. It also includes a default configuration that can be used as a baseline. -public record ColumnOptionsUISettings +public record ColumnOptionsUISettings() { - /// - /// Gets or sets the text shown in the column menu - /// - public string OptionsMenu { get; set; } = "Filter"; - /// /// Gets or sets the icon to show in the column menu /// diff --git a/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor b/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor index b631fd7932..46dc654f0d 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor +++ b/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor @@ -4,12 +4,12 @@ @if (ResizeType == DataGridResizeType.Discrete) { - @Grid.ColumnResizeUISettings.DiscreteLabel + @Localizer[Localization.LanguageResource.DataGrid_ResizeDiscrete] - - - + + + } else { @@ -22,14 +22,13 @@ InputMode="TextInputMode.Numeric" Immediate="true" ImmediateDelay="200" - Label="@Grid.ColumnResizeUISettings.ExactLabel" + Label="@Localizer[Localization.LanguageResource.DataGrid_ResizeDiscrete]" AutoComplete="off" Autofocus="true" - @onkeydown="@HandleColumnWidthKeyDownAsync" - /> + @onkeydown="@HandleColumnWidthKeyDownAsync" /> - - + + } diff --git a/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor.cs b/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor.cs index 82ad09ac04..86af83683f 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor.cs @@ -19,6 +19,10 @@ public partial class ColumnResizeOptions { private string? _width; + /// + [Inject] + public IFluentLocalizer Localizer { get; set; } = default!; + [CascadingParameter] internal InternalGridContext InternalGridContext { get; set; } = default!; diff --git a/src/Core/Components/DataGrid/Columns/ColumnResizeUISettings.cs b/src/Core/Components/DataGrid/Columns/ColumnResizeUISettings.cs index 2e765df254..12a913bc1d 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnResizeUISettings.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnResizeUISettings.cs @@ -5,48 +5,12 @@ namespace Microsoft.FluentUI.AspNetCore.Components; /// -/// Represents the UI settings for column resizing, including labels, aria attributes, and menu options in the . +/// Represents the UI settings for column resizing. /// -/// This record provides customizable settings for the column resize UI, such as menu text, labels for -/// different resize modes, aria labels for accessibility, and icon configuration. Use this type to configure the +/// This record provides customizable settings for the column resize UI, such as icon configuration. Use this type to configure the /// appearance and behavior of the column resize functionality in your application. public record ColumnResizeUISettings { - /// - /// Gets or sets the text shown in the column menu - /// - public string ResizeMenu { get; set; } = "Resize"; - - /// - /// Gets or sets the label in the discrete mode resize UI - /// - public string DiscreteLabel { get; set; } = "Column width"; - - /// - /// Gets or sets the label in the exact mode resize UI - /// - public string ExactLabel { get; set; } = "Column width (in pixels)"; - - /// - /// Gets or sets the aria label for the grow button in the discrete resize UI - /// - public string? GrowAriaLabel { get; set; } = "Grow column width"; - - /// - /// Gets or sets the aria label for the shrink button in the discrete resize UI - /// - public string? ShrinkAriaLabel { get; set; } = "Shrink column width"; - - /// - /// Gets or sets the aria label for the reset button in the resize UI - /// - public string? ResetAriaLabel { get; set; } = "Reset column widths"; - - /// - /// Gets or sets the aria label for the submit button in the resize UI - /// - public string? SubmitAriaLabel { get; set; } = "Set column widths"; - /// /// Gets or sets the icon to show in the column menu /// diff --git a/src/Core/Components/DataGrid/Columns/ColumnSortUISettings.cs b/src/Core/Components/DataGrid/Columns/ColumnSortUISettings.cs index 172cbd50d4..6c11f8c43c 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnSortUISettings.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnSortUISettings.cs @@ -4,27 +4,11 @@ namespace Microsoft.FluentUI.AspNetCore.Components; /// -/// Represents the UI settings for column sorting, including labels, aria attributes, and menu options in the . +/// Represents the UI settings for column sorting. /// -/// This type provides customizable options for the text and icons displayed in the column sorting menu, -/// including labels for ascending and descending sort orders, and the position of the sort icon. +/// This type provides customizable options for the icon displayed in the column sorting menu public record ColumnSortUISettings { - /// - /// Gets or sets the text shown in the column menu - /// - public string SortMenu { get; set; } = "Sort"; - - /// - /// Gets or sets the text shown in the column menu when in ascending order - /// - public string SortMenuAscendingLabel { get; set; } = "Sort (ascending)"; - - /// - /// Gets or sets the text shown in the column menu when in descending order - /// - public string SortMenuDescendingLabel { get; set; } = "Sort (descending)"; - /// /// Gets or sets the icon to show in the column menu /// @@ -37,7 +21,7 @@ public record ColumnSortUISettings public bool IconPositionStart { get; set; } = true; /// - /// Gets the default labels for the sort UI. + /// Gets the default settings for the sort UI. /// public static ColumnSortUISettings Default => new(); diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index fb1521dc22..4b5ac8a203 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -176,19 +176,22 @@ public FluentDataGrid(LibraryConfiguration configuration) : base(configuration) public DataGridResizeType? ResizeType { get; set; } /// - /// (Aria) Labels used in the column resize UI. + /// Settings (icon, icon position) used in the column resize UI. + /// (aria) labels are controlled through /// [Parameter] public ColumnResizeUISettings ColumnResizeUISettings { get; set; } = ColumnResizeUISettings.Default; /// - /// Labels used in the column sort UI. + /// Settings (icon, icon position) used in the column sorting UI. + /// (aria) labels are controlled through /// [Parameter] public ColumnSortUISettings ColumnSortUISettings { get; set; } = ColumnSortUISettings.Default; /// - /// Labels used in the column options UI. + /// Settings (icon, icon position) used in the column options UI. + /// (aria) labels are controlled through /// [Parameter] public ColumnOptionsUISettings ColumnOptionsUISettings { get; set; } = ColumnOptionsUISettings.Default; diff --git a/src/Core/Localization/LanguageResource.resx b/src/Core/Localization/LanguageResource.resx index cda5e94a7a..b7135171a4 100644 --- a/src/Core/Localization/LanguageResource.resx +++ b/src/Core/Localization/LanguageResource.resx @@ -243,4 +243,37 @@ Page {0} of {1} + + Sort (ascending) + + + Sort (descending) + + + Sort + + + Resize + + + Filter + + + Column width + + + Column width (in pixels) + + + Grow column width + + + Shrink column width + + + Reset column widths + + + Set column widths + \ No newline at end of file From 70c342bf87602ebcf5cd35b362c90208da3d529f Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Wed, 18 Jun 2025 22:12:13 +0200 Subject: [PATCH 20/44] Fix tests --- .../Examples/DataGridMultiSelect.razor | 90 +++++++++++++++++++ .../Components/DataGrid/FluentDataGrid.md | 2 + .../DataGrid/Columns/SelectColumn.cs | 54 +++++++---- .../DataGrid/FluentDataGrid.razor.cs | 4 +- .../DataGrid/FluentDataGridCell.razor | 6 +- .../DataGrid/FluentDataGridCell.razor.cs | 5 +- .../DataGrid/FluentDataGridRow.razor | 3 +- .../DataGrid/FluentDataGridRow.razor.cs | 5 +- .../Components/Base/ComponentBaseTests.cs | 6 ++ ...t_Customized_Rendering.verified.razor.html | 42 +++++---- ..._MultiSelect_Rendering.verified.razor.html | 60 ++++++------- ...SingleSelect_Rendering.verified.razor.html | 52 ++++++----- ...StickySelect_Rendering.verified.razor.html | 52 ++++++----- .../FluentDataGridColumSelectTests.razor | 74 +++++++++------ .../FluentDataGridColumSelectTests.razor.cs | 33 ------- .../DataGrid/FluentDataGridIsFixedTests.razor | 2 +- ...yColumnIndex_Ascending.verified.razor.html | 52 +++++------ ...ColumnIndex_Descending.verified.razor.html | 52 +++++------ ...yColumnTitle_Ascending.verified.razor.html | 52 +++++------ ...ColumnTitle_Descending.verified.razor.html | 52 +++++------ .../DataGrid/FluentDataGridSortByTests.razor | 2 +- ...FluentDataGrid_Default.verified.razor.html | 28 +++--- .../DataGrid/FluentDataGridTests.razor | 2 +- 23 files changed, 429 insertions(+), 301 deletions(-) create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridMultiSelect.razor delete mode 100644 tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.razor.cs diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridMultiSelect.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridMultiSelect.razor new file mode 100644 index 0000000000..086e12d61d --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridMultiSelect.razor @@ -0,0 +1,90 @@ + + + + + + + +@if (UseSelectedItems) +{ + @* Sample using SelectedItems *@ +
Using SelectedItems
+ + + + + + + + +
+ SelectedItems: + @String.Join("; ", SelectedItems.Select(p => p.Name)) +
+} +else +{ + @* Sample using Property and OnSelect *@ +
Using Property and OnSelect
+ + + + + + + + +
+ Peoples: + @String.Join("; ", People.Where(p => p.Selected).Select(p => p.Name)) +
+} + +@code { + bool UseSelectedItems = true; + bool SelectFromEntireRow = true; + bool SelectableBefore2000 = false; + DataGridSelectMode Mode = DataGridSelectMode.Single; + + IEnumerable SelectedItems = People.Where(p => p.Selected); + + record Person(int PersonId, string Name, DateOnly BirthDate) + { + public bool Selected { get; set; } + }; + + static IQueryable People = new[] + { + new Person(10895, "Jean Martin", new DateOnly(1985, 3, 16)) { Selected = true }, + new Person(10944, "António Langa", new DateOnly(1991, 12, 1)), + new Person(11203, "Julie Smith", new DateOnly(1958, 10, 10)), + new Person(11205, "Nur Sari", new DateOnly(1922, 4, 27)), + new Person(11898, "Jose Hernandez", new DateOnly(2011, 5, 3)), + new Person(12130, "Kenji Sato", new DateOnly(2004, 1, 9)), + }.AsQueryable(); + + private void ResetSelectItems() + { + People.ToList().ForEach(i => i.Selected = false); + People.First().Selected = true; + SelectedItems = People.Where(p => p.Selected); + } +} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md index 60a43b0329..88c8de7a0e 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md @@ -26,3 +26,5 @@ The resize options UI is using a customized string for the label ('Width (+/- 10 the custom localizer which is registered in the Server project's `Program.cs` file. {{ DataGridTypical }} + +{{ DataGridMultiSelect }} diff --git a/src/Core/Components/DataGrid/Columns/SelectColumn.cs b/src/Core/Components/DataGrid/Columns/SelectColumn.cs index a563cebfca..0022aaa0fa 100644 --- a/src/Core/Components/DataGrid/Columns/SelectColumn.cs +++ b/src/Core/Components/DataGrid/Columns/SelectColumn.cs @@ -26,6 +26,8 @@ public class SelectColumn : ColumnBase private readonly Icon IconUnselectedSingle = new CoreIcons.Regular.Size20.RadioButton().WithColor(Color.Lightweight); private readonly Icon IconSelectedSingle = new CoreIcons.Filled.Size20.RadioButton(); + private readonly List _selectedItems = []; + /// /// Initializes a new instance of . /// @@ -64,13 +66,13 @@ public SelectColumn() /// Gets or sets the list of selected items. ///
[Parameter] - public IList SelectedItems { get; set; } = []; + public IEnumerable SelectedItems { get; set; } = []; /// /// Gets or sets a callback when list of selected items changed. /// [Parameter] - public EventCallback> SelectedItemsChanged { get; set; } + public EventCallback> SelectedItemsChanged { get; set; } /// /// Gets or sets the selection mode (Single, SingleSticky or Multiple). @@ -175,7 +177,9 @@ public SelectColumn() /// public void ClearSelection() { - SelectedItems?.Clear(); + _selectedItems.Clear(); + SelectedItems = _selectedItems; + RefreshHeaderContent(); } @@ -254,6 +258,18 @@ protected internal override Task OnCellKeyDownAsync(FluentDataGridCell value) + //{ + // if (_selectedItems != value) + // { + // _selectedItems.Clear(); + // _selectedItems.AddRange(value); + // SelectAll = false; + + // SelectedItems = _selectedItems; + // } + //} + /// private async Task AddOrRemoveSelectedItemAsync(TGridItem? item) { @@ -264,7 +280,7 @@ private async Task AddOrRemoveSelectedItemAsync(TGridItem? item) return; } - if (SelectedItems.Remove(item)) + if (_selectedItems.Remove(item)) { SelectAll = false; await CallOnSelectAsync(item, isSelected: false); @@ -278,13 +294,14 @@ private async Task AddOrRemoveSelectedItemAsync(TGridItem? item) await CallOnSelectAsync(previous, isSelected: false); } - SelectedItems.Clear(); + _selectedItems.Clear(); } - SelectedItems.Add(item); + _selectedItems.Add(item); await CallOnSelectAsync(item, isSelected: true); } + SelectedItems = _selectedItems; if (SelectedItemsChanged.HasDelegate) { await SelectedItemsChanged.InvokeAsync(SelectedItems); @@ -363,14 +380,20 @@ private RenderFragment GetDefaultChildContent() var selected = SelectedItems.Contains(item) || Property.Invoke(item); // Sync with SelectedItems list - if (selected && !SelectedItems.Contains(item)) + if (selected && !_selectedItems.Contains(item)) { - SelectedItems.Add(item); + _selectedItems.Add(item); RefreshHeaderContent(); } - else if (!selected && SelectedItems.Contains(item)) + else if (!selected && _selectedItems.Contains(item)) + { + _selectedItems.Remove(item); + } + + SelectedItems = _selectedItems; + if (SelectedItemsChanged.HasDelegate) { - SelectedItems.Remove(item); + _ = SelectedItemsChanged.InvokeAsync(SelectedItems); } builder.OpenComponent>(0); @@ -462,7 +485,7 @@ private void RefreshHeaderContent() return false; } - if (SelectedItems.Count == InternalGridContext.TotalItemCount || SelectAll == true) + if (SelectedItems.Take(InternalGridContext.TotalItemCount + 1).Count() == InternalGridContext.TotalItemCount || SelectAll == true) { return true; } @@ -501,20 +524,21 @@ internal async Task OnClickAllAsync(MouseEventArgs e) await SelectAllChanged.InvokeAsync(SelectAll); } - var count = SelectedItems.Count; + var count = SelectedItems.Count(); // SelectedItems - SelectedItems.Clear(); + _selectedItems.Clear(); if (SelectAll == true && count != InternalGridContext.TotalItemCount) { // Only add selectable items - _ = SelectedItems.Concat((InternalGridContext.Grid.Items?.ToList() ?? InternalGridContext.Items) + _selectedItems.AddRange((InternalGridContext.Grid.Items?.ToList() ?? InternalGridContext.Items) .Where(item => Selectable?.Invoke(item) ?? true) ); } + SelectedItems = _selectedItems; if (SelectedItemsChanged.HasDelegate) { - await SelectedItemsChanged.InvokeAsync(SelectedItems); + await SelectedItemsChanged.InvokeAsync(_selectedItems); } RefreshHeaderContent(); diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index 4b5ac8a203..ad1af1b97c 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -898,7 +898,7 @@ private string AriaSortValue(ColumnBase column) ? (_sortByAscending ? "ascending" : "descending") : "none"; - private string? StyleValue => new StyleBuilder(Style) + private string? StyleValue => DefaultStyleBuilder .AddStyle("grid-template-columns", _internalGridTemplateColumns, !string.IsNullOrWhiteSpace(_internalGridTemplateColumns) && DisplayMode == DataGridDisplayMode.Grid) .AddStyle("grid-template-rows", "auto 1fr", (_internalGridContext.Items.Count == 0 || Items is null || EffectiveLoadingValue) && DisplayMode == DataGridDisplayMode.Grid) .AddStyle("height", "100%", _internalGridContext.TotalItemCount == 0 || EffectiveLoadingValue) @@ -918,7 +918,7 @@ private string AriaSortValue(ColumnBase column) private string? GridClass() { - return new CssBuilder(Class) + return DefaultClassBuilder .AddClass("fluent-data-grid") .AddClass("grid", DisplayMode == DataGridDisplayMode.Grid) .AddClass("auto-fit", AutoFit) diff --git a/src/Core/Components/DataGrid/FluentDataGridCell.razor b/src/Core/Components/DataGrid/FluentDataGridCell.razor index ecdf60e81e..b0efdc6226 100644 --- a/src/Core/Components/DataGrid/FluentDataGridCell.razor +++ b/src/Core/Components/DataGrid/FluentDataGridCell.razor @@ -4,7 +4,8 @@ @if (CellType == DataGridCellType.Default) { - Grid => InternalGridContext.Grid; /// - protected string? ClassValue => new CssBuilder(Class) + protected string? ClassValue => DefaultClassBuilder .AddClass("column-header", when: CellType == DataGridCellType.ColumnHeader) .AddClass("select-all", when: CellType == DataGridCellType.ColumnHeader && Column is SelectColumn) .AddClass("multiline-text", when: Grid.MultiLine && (Grid.Items is not null || Grid.ItemsProvider is not null) && CellType != DataGridCellType.ColumnHeader) @@ -78,7 +77,7 @@ public FluentDataGridCell(LibraryConfiguration configuration) : base(configurati .Build(); /// - protected string? StyleValue => new StyleBuilder(Style) + protected string? StyleValue => DefaultStyleBuilder .AddStyle("grid-column", GridColumn.ToString(CultureInfo.InvariantCulture), () => !Grid.EffectiveLoadingValue && (Grid.Items is not null || Grid.ItemsProvider is not null) && Grid.DisplayMode == DataGridDisplayMode.Grid) .AddStyle("text-align", "center", Column is SelectColumn) .AddStyle("align-content", "center", Column is SelectColumn) diff --git a/src/Core/Components/DataGrid/FluentDataGridRow.razor b/src/Core/Components/DataGrid/FluentDataGridRow.razor index c8ee41ca60..152b74aea0 100644 --- a/src/Core/Components/DataGrid/FluentDataGridRow.razor +++ b/src/Core/Components/DataGrid/FluentDataGridRow.razor @@ -4,7 +4,8 @@ @typeparam TGridItem @attribute [CascadingTypeParameter(nameof(TGridItem))] - Grid => InternalGridContext.Grid; /// - protected string? ClassValue => new CssBuilder(Class) + protected string? ClassValue => DefaultClassBuilder .AddClass("fluent-data-grid-row") .AddClass("hover", when: Grid.ShowHover) .Build(); /// - protected string? StyleValue => new StyleBuilder(Style) + protected string? StyleValue => DefaultStyleBuilder .Build(); /// diff --git a/tests/Core/Components/Base/ComponentBaseTests.cs b/tests/Core/Components/Base/ComponentBaseTests.cs index 310e8f095a..6dda5be15d 100644 --- a/tests/Core/Components/Base/ComponentBaseTests.cs +++ b/tests/Core/Components/Base/ComponentBaseTests.cs @@ -8,6 +8,7 @@ using Bunit; using Microsoft.AspNetCore.Components; using Microsoft.Extensions.DependencyInjection; +using Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; using Microsoft.JSInterop; using Xunit; @@ -40,6 +41,11 @@ public class ComponentBaseTests : Bunit.TestContext { typeof(FluentHighlighter), Loader.Default.WithRequiredParameter("HighlightedText", "AB").WithRequiredParameter("Text", "ABCDEF")}, { typeof(FluentKeyCode), Loader.Default.WithRequiredParameter("ChildContent", (RenderFragment)(builder => builder.AddContent(0, "MyContent"))) }, { typeof(FluentPaginator), Loader.Default.WithRequiredParameter("State", new PaginationState()) }, + { typeof(FluentDataGrid<>), Loader.MakeGenericType(typeof(string)) }, + { typeof(FluentDataGridRow<>), Loader.MakeGenericType(typeof(string)).WithCascadingValue(new InternalGridContext(new FluentDataGrid(new LibraryConfiguration()))) }, + { typeof(FluentDataGridCell<>), Loader.MakeGenericType(typeof(string)) + .WithCascadingValue(new InternalGridContext(new FluentDataGrid(new LibraryConfiguration()))) + .WithCascadingValue("OwningRow", new FluentDataGridRow(new LibraryConfiguration()) { InternalGridContext = new InternalGridContext(new FluentDataGrid(new LibraryConfiguration())) }) }, }; /// diff --git a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Customized_Rendering.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Customized_Rendering.verified.razor.html index b9e14ddad6..acb1cb4bd5 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Customized_Rendering.verified.razor.html +++ b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Customized_Rendering.verified.razor.html @@ -1,32 +1,36 @@ - - - -
-
+ + + + - - - - + + - + - - - + + + - - - + + +
+
+
+
+
+
-
-
-
Name
+
+
+
+
Name
+
Jean MartinJean Martin
Kenji Sato
Kenji Sato
Julie Smith
Julie Smith
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Rendering.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Rendering.verified.razor.html index 6b9faeb27b..8c52ea1a1f 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Rendering.verified.razor.html +++ b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Rendering.verified.razor.html @@ -1,50 +1,50 @@ - - - -
- + + + + - - - -
+
+
+
+
+
-
-
-
Name
+
+
+
+
Name
-
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_SingleSelect_Rendering.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_SingleSelect_Rendering.verified.razor.html index 713f83231e..44c7078151 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_SingleSelect_Rendering.verified.razor.html +++ b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_SingleSelect_Rendering.verified.razor.html @@ -1,44 +1,50 @@ - - - - -
-
-
-
Name
+ + + + + - - -
+
+
+
+
+
+
+
+
+
Name
-
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_SingleStickySelect_Rendering.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_SingleStickySelect_Rendering.verified.razor.html index 713f83231e..44c7078151 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_SingleStickySelect_Rendering.verified.razor.html +++ b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_SingleStickySelect_Rendering.verified.razor.html @@ -1,44 +1,50 @@ - - - - -
-
-
-
Name
+ + + + + - - -
+
+
+
+
+
+
+
+
+
Name
-
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.razor b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.razor index 128f191855..8e8a482fde 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.razor +++ b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.razor @@ -3,10 +3,32 @@ @code { + private record Person(int PersonId, string Name, DateOnly BirthDate) + { + public bool Selected { get; set; } + }; + + private readonly IQueryable People = new[] + { + new Person(1, "Jean Martin", new DateOnly(1985, 3, 16)), + new Person(2, "Kenji Sato", new DateOnly(2004, 1, 9)), + new Person(3, "Julie Smith", new DateOnly(1958, 10, 10)), + }.AsQueryable(); + + public FluentDataGridColumSelectTests() + { + var dataGridModule = JSInterop.SetupModule("./_content/Microsoft.FluentUI.AspNetCore.Components/Components/DataGrid/FluentDataGrid.razor.js"); + dataGridModule.SetupModule("init", _ => true); + + // Register services + Services.AddFluentUIComponents(); + Services.AddScoped(factory => new KeyCodeService()); + } + [Fact] public void FluentDataGrid_ColumSelect_SingleSelect_Rendering() { - IList SelectedItems = new List { People.ElementAt(1) }; + IEnumerable SelectedItems = new List { People.ElementAt(1) }; // Arrange var cut = Render( @@ -24,7 +46,7 @@ [Fact] public async Task FluentDataGrid_ColumSelect_SingleSelect_SelectedItems() { - IList SelectedItems = new List(); + IEnumerable SelectedItems = new List(); // Arrange var cut = Render( @@ -51,12 +73,12 @@ Assert.Single(SelectedItems); } - + [Fact] public async Task FluentDataGrid_ColumSelect_Selectable_SingleSelect_SelectedItems() { - IList SelectedItems = new List(); + IEnumerable SelectedItems = new List(); // Arrange var cut = Render( @@ -152,7 +174,7 @@ [Fact] public void FluentDataGrid_ColumSelect_SingleStickySelect_Rendering() { - IList SelectedItems = new List { People.ElementAt(1) }; + IEnumerable SelectedItems = new List { People.ElementAt(1) }; // Arrange var cut = Render( @@ -170,7 +192,7 @@ [Fact] public async Task FluentDataGrid_ColumSelect_SingleStickySelect_SelectedItems() { - IList SelectedItems = new List(); + IEnumerable SelectedItems = new List(); // Arrange var cut = Render( @@ -200,7 +222,7 @@ [Fact] public async Task FluentDataGrid_ColumSelect_SingleStickySameItemSelect_SelectedItems() { - IList SelectedItems = new List(); + IEnumerable SelectedItems = new List(); // Arrange var cut = Render( @@ -230,7 +252,7 @@ [Fact] public async Task FluentDataGrid_ColumSelect_Selectable_SingleStickySelect_SelectedItems() { - IList SelectedItems = new List(); + IEnumerable SelectedItems = new List(); // Arrange var cut = Render( @@ -326,7 +348,7 @@ [Fact] public void FluentDataGrid_ColumSelect_MultiSelect_Rendering() { - IList SelectedItems = new List { People.ElementAt(1), People.ElementAt(2) }; + IEnumerable SelectedItems = new List { People.ElementAt(1), People.ElementAt(2) }; // Arrange var cut = Render( @@ -344,7 +366,7 @@ [Fact] public async Task FluentDataGrid_ColumSelect_MultiSelect_SelectedItems() { - IList SelectedItems = new List(); + IEnumerable SelectedItems = new List(); // Arrange var cut = Render( @@ -379,7 +401,7 @@ [Fact] public async Task FluentDataGrid_ColumSelect_Selectable_MultiSelect_SelectedItems() { - IList SelectedItems = new List(); + IEnumerable SelectedItems = new List(); // Arrange var cut = Render( @@ -456,15 +478,15 @@ // Arrange var cut = Render( - @ - - - - ); + @ + + + + ); // Pre-Assert Assert.Equal(2, cut.FindAll("svg").Count); @@ -490,7 +512,7 @@ [Fact] public async Task FluentDataGrid_ColumSelect_MultiSelect_SelectAll_SelectedItems() { - IList SelectedItems = new List(); + IEnumerable SelectedItems = new List(); // Arrange var cut = Render( @@ -553,7 +575,7 @@ // [Fact] // public void FluentDataGrid_ColumSelect_SwitchMultiToSingleSelect() // { - // IList selectedItems = new List { People.ElementAt(1), People.ElementAt(2) }; + // IEnumerable SelectedItems = new List { People.ElementAt(1), People.ElementAt(2) }; // // Arrange // var cut = Render( @@ -583,7 +605,7 @@ [Fact] public async Task FluentDataGrid_ColumSelect_SelectAll_Disabled() { - IList SelectedItems = new List(); + IEnumerable SelectedItems = new List(); // Arrange var cut = Render( @@ -609,7 +631,7 @@ [Fact] public void FluentDataGrid_ColumSelect_MultiSelect_Customized_Rendering() { - IList SelectedItems = new List() { People.ElementAt(1), People.ElementAt(2) }; + IEnumerable SelectedItems = new List() { People.ElementAt(1), People.ElementAt(2) }; // Arrange var cut = Render( @@ -634,7 +656,7 @@ [Fact] public async Task FluentDataGrid_ColumSelect_SingleSelect_NotSelectFromEntireRow() { - IList SelectedItems = new List(); + IEnumerable SelectedItems = new List(); // Arrange var cut = Render( @@ -665,7 +687,7 @@ [Fact] public async Task FluentDataGrid_ColumSelect_MultiSelect_NotSelectFromEntireRow() { - IList SelectedItems = new List(); + IEnumerable SelectedItems = new List(); // Arrange var cut = Render( diff --git a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.razor.cs b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.razor.cs deleted file mode 100644 index de8c8df1c1..0000000000 --- a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.razor.cs +++ /dev/null @@ -1,33 +0,0 @@ -// ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. -// ------------------------------------------------------------------------ - -using Bunit; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.FluentUI.AspNetCore.Components.Tests.Components.DataGrid; - -public partial class FluentDataGridColumSelectTests : TestContext -{ - public FluentDataGridColumSelectTests() - { - JSInterop.Mode = JSRuntimeMode.Loose; - Services.AddSingleton(LibraryConfiguration.Empty); - - // Register Service - var keycodeService = new KeyCodeService(); - Services.AddScoped(factory => keycodeService); - } - - private record Person(int PersonId, string Name, DateOnly BirthDate) - { - public bool Selected { get; set; } - }; - - private readonly IQueryable People = new[] - { - new Person(1, "Jean Martin", new DateOnly(1985, 3, 16)), - new Person(2, "Kenji Sato", new DateOnly(2004, 1, 9)), - new Person(3, "Julie Smith", new DateOnly(1958, 10, 10)), - }.AsQueryable(); -} diff --git a/tests/Core/Components/DataGrid/FluentDataGridIsFixedTests.razor b/tests/Core/Components/DataGrid/FluentDataGridIsFixedTests.razor index 4e41d84faf..5024cc9a81 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridIsFixedTests.razor +++ b/tests/Core/Components/DataGrid/FluentDataGridIsFixedTests.razor @@ -8,7 +8,7 @@ dataGridModule.SetupModule("init", _ => true); // Register services - Services.AddSingleton(LibraryConfiguration.Empty); + Services.AddFluentUIComponents(); Services.AddScoped(factory => new KeyCodeService()); } diff --git a/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Ascending.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Ascending.verified.razor.html index 246053f255..8fe0626eec 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Ascending.verified.razor.html +++ b/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Ascending.verified.razor.html @@ -1,44 +1,44 @@ - - - -
-
- - -
Item1
- + + + - - - - - + + + + - - - + + + - - - + + + - - - + + +
+
+ + +
Item1
+
-
-
-
Item2
+
+
+
+
Item2
AD
AD
BC
BC
CB
CB
DA
DA
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Descending.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Descending.verified.razor.html index 4877c81b50..090e3af1e1 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Descending.verified.razor.html +++ b/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Descending.verified.razor.html @@ -1,44 +1,44 @@ - - - -
-
- - -
Item1
- + + + - - - - - + + + + - - - + + + - - - + + + - - - + + +
+
+ + +
Item1
+
-
-
-
Item2
+
+
+
+
Item2
DA
DA
CB
CB
BC
BC
AD
AD
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Ascending.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Ascending.verified.razor.html index 246053f255..8fe0626eec 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Ascending.verified.razor.html +++ b/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Ascending.verified.razor.html @@ -1,44 +1,44 @@ - - - -
-
- - -
Item1
- + + + - - - - - + + + + - - - + + + - - - + + + - - - + + +
+
+ + +
Item1
+
-
-
-
Item2
+
+
+
+
Item2
AD
AD
BC
BC
CB
CB
DA
DA
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Descending.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Descending.verified.razor.html index 4877c81b50..090e3af1e1 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Descending.verified.razor.html +++ b/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Descending.verified.razor.html @@ -1,44 +1,44 @@ - - - -
-
- - -
Item1
- + + + - - - - - + + + + - - - + + + - - - + + + - - - + + +
+
+ + +
Item1
+
-
-
-
Item2
+
+
+
+
Item2
DA
DA
CB
CB
BC
BC
AD
AD
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridSortByTests.razor b/tests/Core/Components/DataGrid/FluentDataGridSortByTests.razor index 27e0f41148..a58f68fce2 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridSortByTests.razor +++ b/tests/Core/Components/DataGrid/FluentDataGridSortByTests.razor @@ -8,7 +8,7 @@ public FluentDataGridSortByTests() { JSInterop.Mode = JSRuntimeMode.Loose; - Services.AddSingleton(LibraryConfiguration.Empty); + Services.AddFluentUIComponents(); // Register Service var keycodeService = new KeyCodeService(); diff --git a/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_Default.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_Default.verified.razor.html index fd82a98758..c1509a4e8f 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_Default.verified.razor.html +++ b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_Default.verified.razor.html @@ -1,25 +1,25 @@ - - - -
-
-
-
Name
+ + + + - - - + + + - - + + - - + +
+
+
+
Name
Denis Voituron
Denis Voituron
Vincent Baaij
Vincent Baaij
Bill Gates
Bill Gates
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridTests.razor b/tests/Core/Components/DataGrid/FluentDataGridTests.razor index 82dcd9f0e9..73858c3808 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridTests.razor +++ b/tests/Core/Components/DataGrid/FluentDataGridTests.razor @@ -8,7 +8,7 @@ dataGridModule.SetupModule("init", _ => true); // Register services - Services.AddSingleton(LibraryConfiguration.Empty); + Services.AddFluentUIComponents(); Services.AddScoped(factory => new KeyCodeService()); } From b8261281d31c9807b512361f4a1bec51768e6deb Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Thu, 19 Jun 2025 15:47:28 +0200 Subject: [PATCH 21/44] Add Tests --- .../Examples/DataGridMultiSelect.razor | 10 +- .../Components/DataGrid/FluentDataGrid.md | 2 +- spelling.dic | 2 + .../DataGrid/Columns/SelectColumn.cs | 144 +++++++------ .../DataGrid/FluentDataGrid.razor.cs | 8 - .../DataGrid/FluentDataGridCell.razor.cs | 6 +- .../DataGrid/FluentDataGridCellTests.razor | 190 ++++++++++++++++++ ...t_Customized_Rendering.verified.razor.html | 22 +- ..._MultiSelect_Rendering.verified.razor.html | 32 +-- ...SingleSelect_Rendering.verified.razor.html | 24 +-- ...StickySelect_Rendering.verified.razor.html | 24 +-- .../FluentDataGridColumSelectTests.razor | 2 +- .../DataGrid/FluentDataGridTests.razor | 18 ++ .../Core/Components/DataGrid/GridSortTests.cs | 2 +- 14 files changed, 341 insertions(+), 145 deletions(-) create mode 100644 tests/Core/Components/DataGrid/FluentDataGridCellTests.razor diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridMultiSelect.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridMultiSelect.razor index 086e12d61d..494d5817dd 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridMultiSelect.razor +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridMultiSelect.razor @@ -1,6 +1,6 @@ - + + @bind-Value="@Mode" /> @@ -28,7 +28,7 @@ -
+
SelectedItems: @String.Join("; ", SelectedItems.Select(p => p.Name))
@@ -52,8 +52,8 @@ else -
- Peoples: +
+ Persons: @String.Join("; ", People.Where(p => p.Selected).Select(p => p.Name))
} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md index 88c8de7a0e..e49a87b926 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md @@ -25,6 +25,6 @@ Pressing enter finishes the filter action by the current input to filter on and The resize options UI is using a customized string for the label ('Width (+/- 10px)' instead of the normal 'Column width'). This is done through the custom localizer which is registered in the Server project's `Program.cs` file. -{{ DataGridTypical }} + {{ DataGridMultiSelect }} diff --git a/spelling.dic b/spelling.dic index f523fe7494..193240e27b 100644 --- a/spelling.dic +++ b/spelling.dic @@ -86,3 +86,5 @@ gridcell Voituron beforetoggle resx +yyyy +Langa diff --git a/src/Core/Components/DataGrid/Columns/SelectColumn.cs b/src/Core/Components/DataGrid/Columns/SelectColumn.cs index 0022aaa0fa..85b515bd0b 100644 --- a/src/Core/Components/DataGrid/Columns/SelectColumn.cs +++ b/src/Core/Components/DataGrid/Columns/SelectColumn.cs @@ -17,15 +17,15 @@ public class SelectColumn : ColumnBase /// /// List of keys to press, to select/unselect a row. /// -#pragma warning disable MA0018 // Do not declare static members on generic types (deprecated; use CA1000 instead) - public static readonly string[] KEYBOARD_SELECT_KEYS = ["Enter", "NumpadEnter"]; -#pragma warning restore MA0018 // Do not declare static members on generic types (deprecated; use CA1000 instead) - private readonly Icon IconUnselectedMultiple = new CoreIcons.Regular.Size20.CheckboxUnchecked().WithColor(Color.Lightweight); - private readonly Icon IconSelectedMultiple = new CoreIcons.Filled.Size20.CheckboxChecked(); - private readonly Icon IconUnselectedSingle = new CoreIcons.Regular.Size20.RadioButton().WithColor(Color.Lightweight); - private readonly Icon IconSelectedSingle = new CoreIcons.Filled.Size20.RadioButton(); + internal static readonly string[] KEYBOARD_SELECT_KEYS = ["Enter", "NumpadEnter"]; + private readonly Icon IconUnselectedMultiple = new CoreIcons.Regular.Size20.CheckboxUnchecked(); + private readonly Icon IconSelectedMultiple = new CoreIcons.Filled.Size20.CheckboxChecked().WithColor(Color.Primary); + private readonly Icon IconUnselectedSingle = new CoreIcons.Regular.Size20.RadioButton(); + private readonly Icon IconSelectedSingle = new CoreIcons.Filled.Size20.RadioButton().WithColor(Color.Primary); + + private DataGridSelectMode _selectMode = DataGridSelectMode.Single; private readonly List _selectedItems = []; /// @@ -66,7 +66,21 @@ public SelectColumn() /// Gets or sets the list of selected items. /// [Parameter] - public IEnumerable SelectedItems { get; set; } = []; +#pragma warning disable BL0007 // Component parameters should be auto properties + public IEnumerable SelectedItems +#pragma warning restore BL0007 // Component parameters should be auto properties + { + get => _selectedItems; + set + { + if (_selectedItems != value) + { + _selectedItems.Clear(); + _selectedItems.AddRange(value); + SelectAll = false; + } + } + } /// /// Gets or sets a callback when list of selected items changed. @@ -78,7 +92,23 @@ public SelectColumn() /// Gets or sets the selection mode (Single, SingleSticky or Multiple). /// [Parameter] - public DataGridSelectMode SelectMode { get; set; } = DataGridSelectMode.Single; +#pragma warning disable BL0007 // Component parameters should be auto properties + public DataGridSelectMode SelectMode +#pragma warning restore BL0007 // Component parameters should be auto properties + { + get => _selectMode; + set + { + _selectMode = value; + + if (value is DataGridSelectMode.Single or DataGridSelectMode.SingleSticky) + { + _ = KeepOnlyFirstSelectedItemAsync(); + } + + RefreshHeaderContent(); + } + } /// /// Gets or sets the Icon to be rendered when the row is non selected. @@ -111,7 +141,7 @@ public SelectColumn() /// Only when is Multiple. /// [Parameter] - public Icon? IconIndeterminate { get; set; } = new CoreIcons.Filled.Size20.CheckboxIndeterminate(); + public Icon? IconIndeterminate { get; set; } = new CoreIcons.Filled.Size20.CheckboxIndeterminate().WithColor(Color.Primary); /// /// Gets or sets the Icon title display as a tooltip and used with Accessibility. @@ -178,8 +208,6 @@ public SelectColumn() public void ClearSelection() { _selectedItems.Clear(); - SelectedItems = _selectedItems; - RefreshHeaderContent(); } @@ -258,30 +286,19 @@ protected internal override Task OnCellKeyDownAsync(FluentDataGridCell value) - //{ - // if (_selectedItems != value) - // { - // _selectedItems.Clear(); - // _selectedItems.AddRange(value); - // SelectAll = false; - - // SelectedItems = _selectedItems; - // } - //} - /// private async Task AddOrRemoveSelectedItemAsync(TGridItem? item) { if (item != null && (Selectable == null || Selectable.Invoke(item))) { - if (SelectMode is DataGridSelectMode.SingleSticky && SelectedItems.Contains(item)) + if (SelectMode is DataGridSelectMode.SingleSticky && _selectedItems.Contains(item)) { return; } - if (_selectedItems.Remove(item)) + if (SelectedItems.Contains(item)) { + _selectedItems.Remove(item); SelectAll = false; await CallOnSelectAsync(item, isSelected: false); } @@ -289,7 +306,7 @@ private async Task AddOrRemoveSelectedItemAsync(TGridItem? item) { if (SelectMode is DataGridSelectMode.Single or DataGridSelectMode.SingleSticky) { - foreach (var previous in SelectedItems) + foreach (var previous in _selectedItems) { await CallOnSelectAsync(previous, isSelected: false); } @@ -301,7 +318,6 @@ private async Task AddOrRemoveSelectedItemAsync(TGridItem? item) await CallOnSelectAsync(item, isSelected: true); } - SelectedItems = _selectedItems; if (SelectedItemsChanged.HasDelegate) { await SelectedItemsChanged.InvokeAsync(SelectedItems); @@ -338,34 +354,34 @@ private Icon GetIcon(bool? selected) }; } - //private async Task KeepOnlyFirstSelectedItemAsync() - //{ - // if (SelectedItems.Count <= 1) - // { - // return; - // } - - // // Unselect all except the first - // foreach (var item in SelectedItems.Skip(1)) - // { - // await OnSelect.InvokeAsync((item, false)); - // } - - // // Keep the first selected item - // SelectedItems.RemoveRange(1, SelectedItems.Count - 1); - - // if (SelectedItemsChanged.HasDelegate) - // { - // await SelectedItemsChanged.InvokeAsync(SelectedItems); - // } - - // // Indeterminate - // SelectAll = null; - // if (SelectAllChanged.HasDelegate) - // { - // await SelectAllChanged.InvokeAsync(SelectAll); - // } - //} + private async Task KeepOnlyFirstSelectedItemAsync() + { + if (_selectedItems.Count <= 1) + { + return; + } + + // Unselect all except the first + foreach (var item in _selectedItems.Skip(1)) + { + await OnSelect.InvokeAsync((item, false)); + } + + // Keep the first selected item + _selectedItems.RemoveRange(1, _selectedItems.Count - 1); + + if (SelectedItemsChanged.HasDelegate) + { + await SelectedItemsChanged.InvokeAsync(_selectedItems); + } + + // Indeterminate + SelectAll = null; + if (SelectAllChanged.HasDelegate) + { + await SelectAllChanged.InvokeAsync(SelectAll); + } + } /// private RenderFragment GetDefaultChildContent() @@ -377,7 +393,7 @@ private RenderFragment GetDefaultChildContent() return; } - var selected = SelectedItems.Contains(item) || Property.Invoke(item); + var selected = _selectedItems.Contains(item) || Property.Invoke(item); // Sync with SelectedItems list if (selected && !_selectedItems.Contains(item)) @@ -390,12 +406,6 @@ private RenderFragment GetDefaultChildContent() _selectedItems.Remove(item); } - SelectedItems = _selectedItems; - if (SelectedItemsChanged.HasDelegate) - { - _ = SelectedItemsChanged.InvokeAsync(SelectedItems); - } - builder.OpenComponent>(0); builder.AddAttribute(1, "Value", GetIcon(selected)); builder.AddAttribute(2, "Title", selected ? TitleChecked : TitleUnchecked); @@ -535,10 +545,9 @@ internal async Task OnClickAllAsync(MouseEventArgs e) ); } - SelectedItems = _selectedItems; if (SelectedItemsChanged.HasDelegate) { - await SelectedItemsChanged.InvokeAsync(_selectedItems); + await SelectedItemsChanged.InvokeAsync(SelectedItems); } RefreshHeaderContent(); @@ -555,9 +564,10 @@ internal async Task OnKeyAllAsync(KeyboardEventArgs e) } /// -/// Represents the arguments for the SelectAll template. +/// Represents the arguments for selecting all items in a template. /// -/// +/// A nullable boolean indicating whether all items are selected. if all items are selected; +/// if not; if the selection state is undefined. #pragma warning disable MA0048 // File name must match type name public record SelectAllTemplateArgs(bool? AllSelected) { } #pragma warning restore MA0048 // File name must match type name diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index ad1af1b97c..524a0a73e9 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -1048,14 +1048,6 @@ public async Task UpdateItemsPerPageAsync(int visibleRows) } await Pagination.SetItemsPerPageAsync(visibleRows - 1); // subtract 1 for the table header - - //if (Pagination.CurrentPageIndex > Pagination.LastPageIndex && Pagination.LastPageIndex.HasValue && Pagination.LastPageIndex.Value > 0) - //{ - // await Pagination.SetCurrentPageIndexAsync(Pagination.LastPageIndex.Value); - //} - - //await RefreshDataAsync(); - //StateHasChanged(); } //public void SetPageReference(Type page) diff --git a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs index 98bae4d7ae..2e9bc54e1e 100644 --- a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs @@ -81,9 +81,9 @@ public FluentDataGridCell(LibraryConfiguration configuration) : base(configurati .AddStyle("grid-column", GridColumn.ToString(CultureInfo.InvariantCulture), () => !Grid.EffectiveLoadingValue && (Grid.Items is not null || Grid.ItemsProvider is not null) && Grid.DisplayMode == DataGridDisplayMode.Grid) .AddStyle("text-align", "center", Column is SelectColumn) .AddStyle("align-content", "center", Column is SelectColumn) - .AddStyle("padding-inline-start", "calc(((var(--design-unit)* 3) + var(--focus-stroke-width) - var(--stroke-width))* 1px)", Column is SelectColumn && Owner.RowType == DataGridRowType.Default) - .AddStyle("padding-top", "calc(var(--design-unit) * 2.5px)", Column is SelectColumn && (Grid.RowSize == DataGridRowSize.Medium || Owner.RowType == DataGridRowType.Header)) - .AddStyle("padding-top", "calc(var(--design-unit) * 1.5px)", Column is SelectColumn && Grid.RowSize == DataGridRowSize.Small && Owner.RowType == DataGridRowType.Default) + //.AddStyle("padding-inline-start", "calc(((var(--design-unit)* 3) + var(--focus-stroke-width) - var(--stroke-width))* 1px)", Column is SelectColumn && Owner.RowType == DataGridRowType.Default) + .AddStyle("padding-top", "10px", Column is SelectColumn && (Grid.RowSize == DataGridRowSize.Medium || Owner.RowType == DataGridRowType.Header)) + .AddStyle("padding-top", "6px", Column is SelectColumn && Grid.RowSize == DataGridRowSize.Small && Owner.RowType == DataGridRowType.Default) .AddStyle("width", Column?.Width, !string.IsNullOrEmpty(Column?.Width) && Grid.DisplayMode == DataGridDisplayMode.Table) .AddStyle("height", $"{Grid.ItemSize.ToString(CultureInfo.InvariantCulture):0}px", () => !Grid.EffectiveLoadingValue && Grid.Virtualize) .AddStyle("height", $"{((int)Grid.RowSize).ToString(CultureInfo.InvariantCulture)}px", () => !Grid.EffectiveLoadingValue && !Grid.Virtualize && !Grid.MultiLine && (Grid.Items is not null || Grid.ItemsProvider is not null)) diff --git a/tests/Core/Components/DataGrid/FluentDataGridCellTests.razor b/tests/Core/Components/DataGrid/FluentDataGridCellTests.razor new file mode 100644 index 0000000000..9bc6518fe3 --- /dev/null +++ b/tests/Core/Components/DataGrid/FluentDataGridCellTests.razor @@ -0,0 +1,190 @@ +@using Xunit +@using static Microsoft.FluentUI.AspNetCore.Components.Tests.Components.DataGrid.FluentDataGridColumSelectTests +@inherits Bunit.TestContext + +@code { + + private readonly IQueryable People = new[] + { + new Person(1, "Jean Martin", new DateOnly(1985, 3, 16)), + new Person(2, "Kenji Sato", new DateOnly(2004, 1, 9)), + new Person(3, "Julie Smith", new DateOnly(1958, 10, 10)), + }.AsQueryable(); + + public FluentDataGridCellTests() + { + var dataGridModule = JSInterop.SetupModule("./_content/Microsoft.FluentUI.AspNetCore.Components/Components/DataGrid/FluentDataGrid.razor.js"); + dataGridModule.SetupModule("init", _ => true); + + // Register services + Services.AddFluentUIComponents(); + Services.AddScoped(factory => new KeyCodeService()); + } + + [Fact] + public void FluentDataGridCell_Default() + { + // Arrange + var grid = Render>( + @ + + + + ); + + // Act + var cell = grid.FindComponent>(); + + // Assert + Assert.NotNull(grid); + Assert.NotNull(cell); + Assert.Equal(DataGridCellType.Default, cell.Instance.CellType); + Assert.Equal(0, cell.Instance.GridColumn); + Assert.Null(cell.Instance.ChildContent); + } + + [Fact] + public async Task FluentDataGridCell_HandleOnCellClickAsync_InvokesCallbacks() + { + // Arrange + var cut = Render>( + @ + + + + ); + // Act + var cell = cut.FindComponent>(); + await cell.Instance.HandleOnCellClickAsync(); + + // Assert + Assert.True(OnCellClickInvoked); + } + + [Fact] + public async Task FluentDataGridCell_HandleOnCellFocusAsync_InvokesCallbacks() + { + // Arrange + var cut = Render>( + @ + + + + ); + + // Act + var cell = cut.FindComponent>(); + await cell.Instance.HandleOnCellFocusAsync(); + // Assert + Assert.True(OnCellFocusInvoked); + } + + [Fact] + public async Task FluentDataGridCell_HandleOnCellKeyDownAsync_HandlesKeyEnter() + { + // Arrange + var items = new List(People).AsQueryable(); + + var cut = Render>( + @ + + + ); + + // Act + var keyboardEvent = new KeyboardEventArgs { Code = "Enter" }; + var cell = cut.FindComponent>(); + await cell.Instance.HandleOnCellKeyDownAsync(keyboardEvent); + + // Assert + Assert.True(OnCellKeyDownInvoked); + } + + + [Fact] + public async Task FluentDataGridCell_HandleOnCellKeyDownAsync_HandlesKeyOther() + { + // Arrange + var items = new List(People).AsQueryable(); + + var cut = Render>( + @ + + + ); + + // Act + var keyboardEvent = new KeyboardEventArgs { Code = "A" }; + var cell = cut.FindComponent>(); + await cell.Instance.HandleOnCellKeyDownAsync(keyboardEvent); + + // Assert + Assert.False(OnCellKeyDownInvoked); + } + + [Fact] + public void FluentDataGridCell_Dispose() + { + // Arrange + var grid = Render>( + @ + + + + + ); + + // Act + var cell = grid.FindComponent>(); + cell.Instance.Dispose(); + cell.Dispose(); + + // Assert + Assert.True(cell.IsDisposed); + + } + + public bool OnCellClickInvoked { get; set; } + public bool OnCellFocusInvoked { get; set; } + public bool OnCellKeyDownInvoked { get; set; } + + + private void HandleCellClick() + { + // This method simulates the callback that would be invoked on cell click + // In a real scenario, this would be defined in the FluentDataGrid component + // and passed to the FluentDataGridCell. + OnCellClickInvoked = true; + } + + private void HandleCellFocus() + { + // This method simulates the callback that would be invoked on cell focus + // In a real scenario, this would be defined in the FluentDataGrid component + // and passed to the FluentDataGridCell. + OnCellFocusInvoked = true; + } + + private void HandleCellKeyDown((Person person, bool selected) e) + { + // This method simulates the callback that would be invoked on cell key down + // In a real scenario, this would be defined in the FluentDataGrid component + // and passed to the FluentDataGridCell. + if (e.selected ) + { + OnCellKeyDownInvoked = true; + } + else + { + OnCellKeyDownInvoked = false; + } + } +} diff --git a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Customized_Rendering.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Customized_Rendering.verified.razor.html index acb1cb4bd5..f37fc0a1dc 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Customized_Rendering.verified.razor.html +++ b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Customized_Rendering.verified.razor.html @@ -2,14 +2,10 @@ - - - - + - - + + - - + +
-
-
-
-
-
+
+
+
Name
@@ -20,17 +16,17 @@
+ Jean MartinJean Martin
Kenji SatoKenji Sato
Julie SmithJulie Smith
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Rendering.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Rendering.verified.razor.html index 8c52ea1a1f..561663a7e9 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Rendering.verified.razor.html +++ b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Rendering.verified.razor.html @@ -2,12 +2,12 @@ - - - - - + - + - +
-
-
-
-
-
+
+
@@ -20,8 +20,8 @@
-
-
- - + - - - - + - + - +
-
-
-
-
-
-
@@ -20,8 +14,8 @@
-
-
- - + - - - + - + - +
-
-
-
-
-
-
@@ -20,8 +14,8 @@
-
-
-
+
+ 20 + items
+ +
\ No newline at end of file diff --git a/tests/Core/Components/Paginator/FluentPaginatorTests.razor b/tests/Core/Components/Paginator/FluentPaginatorTests.razor index 82bf8b3878..2fb28e1b84 100644 --- a/tests/Core/Components/Paginator/FluentPaginatorTests.razor +++ b/tests/Core/Components/Paginator/FluentPaginatorTests.razor @@ -29,6 +29,44 @@ Assert.Contains("100", cut.Find(".summary").InnerHtml); } + [Fact] + public async Task FluentPaginator_SetItemsPerPage() + { + // Arrange + var state = new PaginationState(); + await state.SetTotalItemCountAsync(20); + await state.SetItemsPerPageAsync(5); + + // Act + var cut = Render(@); + + // Assert + cut.Verify(); + Assert.NotNull(cut.Find(".fluent-paginator")); + Assert.NotNull(cut.Find(".summary")); + Assert.NotNull(cut.Find(".paginator-nav")); + Assert.Contains("20", cut.Find(".summary").InnerHtml); + } + + [Fact] + public async Task FluentPaginator_CurrentPageBeyondLastPage() + { + // Arrange + var state = new PaginationState(); + await state.SetCurrentPageIndexAsync(12); + await state.SetTotalItemCountAsync(100); + + // Act + var cut = Render(@); + + // Assert + + Assert.NotNull(cut.Find(".fluent-paginator")); + Assert.NotNull(cut.Find(".summary")); + Assert.NotNull(cut.Find(".paginator-nav")); + Assert.Contains("100", cut.Find(".summary").InnerHtml); + } + [Fact] public void FluentPaginator_DoesNotRender_WhenTotalItemCountIsNull() { diff --git a/tests/Core/Components/Paginator/PaginationStateTest.cs b/tests/Core/Components/Paginator/PaginationStateTest.cs new file mode 100644 index 0000000000..73147687eb --- /dev/null +++ b/tests/Core/Components/Paginator/PaginationStateTest.cs @@ -0,0 +1,46 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +using Xunit; + +namespace Microsoft.FluentUI.AspNetCore.Components.Tests.Components.Paginator; +public class PaginationStateTest : Bunit.TestContext +{ + [Fact] + public async Task PaginationState_GetHashCode_ReturnsExpectedAsync() + { + // Arrange + var paginationState = new PaginationState(); + paginationState.ItemsPerPage = 20; + + await paginationState.SetTotalItemCountAsync(100); + await paginationState.SetItemsPerPageAsync(20); + await paginationState.SetCurrentPageIndexAsync(2); + + // Act + var hashCode = paginationState.GetHashCode(); + + // Assert + Assert.NotEqual(0, hashCode); + } + [Fact] + public async Task SetTotalItemCountAsync_UpdatesTotalItemCountAndKeepsCurrentPageIndexValid() + { + // Arrange + var paginationState = new PaginationState(); + paginationState.ItemsPerPage = 10; + await paginationState.SetItemsPerPageAsync(10); + await paginationState.SetCurrentPageIndexAsync(3); + + // Act + await paginationState.SetTotalItemCountAsync(25, force: true); + await paginationState.SetTotalItemCountAsync(25, force: false); + + // Assert + Assert.Equal(25, paginationState.TotalItemCount); + // With 10 items per page and 25 items, there are 3 pages (0,1,2) + // So current page index should be adjusted to 2 if it was set to 3 + Assert.Equal(2, paginationState.CurrentPageIndex); + } +} diff --git a/tests/Core/Components/Paginator/TotalItemCountChangedEventArgsTest.cs b/tests/Core/Components/Paginator/TotalItemCountChangedEventArgsTest.cs new file mode 100644 index 0000000000..e7cc96192c --- /dev/null +++ b/tests/Core/Components/Paginator/TotalItemCountChangedEventArgsTest.cs @@ -0,0 +1,33 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +using Xunit; + +namespace Microsoft.FluentUI.AspNetCore.Components.Tests.Components.Paginator; + +public class TotalItemCountChangedEventArgsTest +{ + [Fact] + public void Constructor_SetsTotalItemCount() + { + // Arrange + int? expectedCount = 42; + + // Act + var args = new TotalItemCountChangedEventArgs(expectedCount); + + // Assert + Assert.Equal(expectedCount, args.TotalItemCount); + } + + [Fact] + public void Constructor_AllowsNullTotalItemCount() + { + // Act + var args = new TotalItemCountChangedEventArgs(null); + + // Assert + Assert.Null(args.TotalItemCount); + } +} From af4f1e8330f56ec1b50b7c76c1dcc02778463cd2 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Thu, 19 Jun 2025 16:50:42 +0200 Subject: [PATCH 23/44] Add Row tests --- .../DataGrid/FluentDataGridRowTests.razor | 213 ++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 tests/Core/Components/DataGrid/FluentDataGridRowTests.razor diff --git a/tests/Core/Components/DataGrid/FluentDataGridRowTests.razor b/tests/Core/Components/DataGrid/FluentDataGridRowTests.razor new file mode 100644 index 0000000000..04c92c22d0 --- /dev/null +++ b/tests/Core/Components/DataGrid/FluentDataGridRowTests.razor @@ -0,0 +1,213 @@ +@using Xunit +@using static Microsoft.FluentUI.AspNetCore.Components.Tests.Components.DataGrid.FluentDataGridColumSelectTests +@inherits Bunit.TestContext + +@code { + + private readonly IQueryable People = new[] + { + new Person(1, "Jean Martin", new DateOnly(1985, 3, 16)), + new Person(2, "Kenji Sato", new DateOnly(2004, 1, 9)), + new Person(3, "Julie Smith", new DateOnly(1958, 10, 10)), + }.AsQueryable(); + + public FluentDataGridRowTests() + { + var dataGridModule = JSInterop.SetupModule("./_content/Microsoft.FluentUI.AspNetCore.Components/Components/DataGrid/FluentDataGrid.razor.js"); + dataGridModule.SetupModule("init", _ => true); + + // Register services + Services.AddFluentUIComponents(); + Services.AddScoped(factory => new KeyCodeService()); + } + + [Fact] + public void FluentDataGridRow_Default() + { + // Arrange + var grid = Render>( + @ + + + + ); + + // Act + var row = grid.FindComponent>(); + + // Assert + Assert.NotNull(grid); + Assert.NotNull(row); + Assert.Equal(DataGridRowType.Default, row.Instance.RowType); + Assert.NotNull(row.Instance.ChildContent); + } + + [Fact] + public async Task FluentDataGridRow_HandleOnRowClickAsync_InvokesCallbacks() + { + // Arrange + var cut = Render>( + @ + + + + + ); + // Act + var row = cut.FindComponent>(); + await row.Instance.HandleOnRowClickAsync(row.Instance.RowId); + + // Assert + Assert.True(OnRowClickInvoked); + } + + [Fact] + public async Task FluentDataGridRow_HandleOnRowClickAsync_EmptyRow() + { + // Arrange + var People = new List() { }.AsQueryable(); + + var cut = Render>( + @ + + + + No Data + ); + + // Act + var row = cut.FindComponent>(); + await row.Instance.HandleOnRowClickAsync(row.Instance.RowId); + + // Assert + Assert.False(OnRowClickInvoked); + } + + + [Fact] + public async Task FluentDataGridRow_HandleOnCellFocusAsync_InvokesCallbacks() + { + // Arrange + var cut = Render>( + @ + + + + ); + + // Act + var cell = cut.FindComponent>(); + await cell.Instance.HandleOnCellFocusAsync(); + // Assert + Assert.True(OnRowFocusInvoked); + } + + [Fact] + public async Task FluentDataGridRow_HandleOnCellKeyDownAsync_HandlesKeyEnter() + { + // Arrange + var items = new List(People).AsQueryable(); + + var cut = Render>( + @ + + + ); + + // Act + var keyboardEvent = new KeyboardEventArgs { Code = "Enter" }; + var cell = cut.FindComponent>(); + await cell.Instance.HandleOnCellKeyDownAsync(keyboardEvent); + + // Assert + Assert.True(OnRowKeyDownInvoked); + } + + + [Fact] + public async Task FluentDataGridRow_HandleOnCellKeyDownAsync_HandlesKeyOther() + { + // Arrange + var items = new List(People).AsQueryable(); + + var cut = Render>( + @ + + + ); + + // Act + var keyboardEvent = new KeyboardEventArgs { Code = "A" }; + var cell = cut.FindComponent>(); + await cell.Instance.HandleOnCellKeyDownAsync(keyboardEvent); + + // Assert + Assert.False(OnRowKeyDownInvoked); + } + + [Fact] + public void FluentDataGridRow_Dispose() + { + // Arrange + var grid = Render>( + @ + + + + + ); + + // Act + var cell = grid.FindComponent>(); + cell.Instance.Dispose(); + cell.Dispose(); + + // Assert + Assert.True(cell.IsDisposed); + + } + + public bool OnRowClickInvoked { get; set; } + public bool OnRowFocusInvoked { get; set; } + public bool OnRowKeyDownInvoked { get; set; } + + + private void HandleRowClick() + { + // This method simulates the callback that would be invoked on cell click + // In a real scenario, this would be defined in the FluentDataGrid component + // and passed to the FluentDataGridCell. + OnRowClickInvoked = true; + } + + private void HandleRowFocus() + { + // This method simulates the callback that would be invoked on cell focus + // In a real scenario, this would be defined in the FluentDataGrid component + // and passed to the FluentDataGridCell. + OnRowFocusInvoked = true; + } + + private void HandleRowKeyDown((Person person, bool selected) e) + { + // This method simulates the callback that would be invoked on cell key down + // In a real scenario, this would be defined in the FluentDataGrid component + // and passed to the FluentDataGridCell. + if (e.selected ) + { + OnRowKeyDownInvoked = true; + } + else + { + OnRowKeyDownInvoked = false; + } + } +} From 89038a7d0e23a5d6254ebffaa3a8216257ed8fbd Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Thu, 19 Jun 2025 16:54:01 +0200 Subject: [PATCH 24/44] process review comments --- src/Core/Microsoft.FluentUI.AspNetCore.Components.csproj | 3 --- tests/Core/Components/Paginator/PaginationStateTest.cs | 2 +- .../Components/Paginator/TotalItemCountChangedEventArgsTest.cs | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Core/Microsoft.FluentUI.AspNetCore.Components.csproj b/src/Core/Microsoft.FluentUI.AspNetCore.Components.csproj index 655289b460..15f6d4496c 100644 --- a/src/Core/Microsoft.FluentUI.AspNetCore.Components.csproj +++ b/src/Core/Microsoft.FluentUI.AspNetCore.Components.csproj @@ -117,7 +117,4 @@ CS1591 - - - diff --git a/tests/Core/Components/Paginator/PaginationStateTest.cs b/tests/Core/Components/Paginator/PaginationStateTest.cs index 73147687eb..181c58b4aa 100644 --- a/tests/Core/Components/Paginator/PaginationStateTest.cs +++ b/tests/Core/Components/Paginator/PaginationStateTest.cs @@ -5,7 +5,7 @@ using Xunit; namespace Microsoft.FluentUI.AspNetCore.Components.Tests.Components.Paginator; -public class PaginationStateTest : Bunit.TestContext +public class PaginationStateTests : Bunit.TestContext { [Fact] public async Task PaginationState_GetHashCode_ReturnsExpectedAsync() diff --git a/tests/Core/Components/Paginator/TotalItemCountChangedEventArgsTest.cs b/tests/Core/Components/Paginator/TotalItemCountChangedEventArgsTest.cs index e7cc96192c..258f82bfa2 100644 --- a/tests/Core/Components/Paginator/TotalItemCountChangedEventArgsTest.cs +++ b/tests/Core/Components/Paginator/TotalItemCountChangedEventArgsTest.cs @@ -6,7 +6,7 @@ namespace Microsoft.FluentUI.AspNetCore.Components.Tests.Components.Paginator; -public class TotalItemCountChangedEventArgsTest +public class TotalItemCountChangedEventArgsTests { [Fact] public void Constructor_SetsTotalItemCount() From ba9c0fbb33961ea6ebbf1345aca14bd8febce509 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Thu, 19 Jun 2025 21:01:20 +0200 Subject: [PATCH 25/44] More tests --- .../DataGrid/FluentDataGridRow.razor.cs | 44 ++-- .../DataGrid/FluentDataGridRowTests.razor | 213 ++++++++++++++++-- 2 files changed, 225 insertions(+), 32 deletions(-) diff --git a/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs b/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs index 53e600deac..254f5e16be 100644 --- a/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs @@ -65,12 +65,6 @@ public FluentDataGridRow(LibraryConfiguration configuration) : base(configuratio [Parameter] public RenderFragment? ChildContent { get; set; } - /// - /// Gets or sets a callback for when a cell is focused. - /// - [Parameter] - public EventCallback> OnCellFocus { get; set; } - /// /// Gets or sets the owning component /// @@ -108,6 +102,7 @@ public void Dispose() } /// + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage(Justification = "Tested in aspnetcore code")] Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, object? arg) => callback.InvokeAsync(arg); @@ -142,19 +137,24 @@ internal async Task HandleOnRowClickAsync(string rowId) { var row = GetRow(rowId); - if (row is not null && !string.IsNullOrWhiteSpace(row.Class) && + if (row is null) + { + return; + } + + if (!string.IsNullOrWhiteSpace(row.Class) && (row.Class.Contains(FluentDataGrid.EMPTY_CONTENT_ROW_CLASS, StringComparison.Ordinal) || row.Class.Contains(FluentDataGrid.LOADING_CONTENT_ROW_CLASS, StringComparison.Ordinal))) { return; } - if (row != null && Grid.OnRowClick.HasDelegate) + if (Grid.OnRowClick.HasDelegate) { await Grid.OnRowClick.InvokeAsync(row); } - if (row != null && row.RowType == DataGridRowType.Default) + if (row.RowType == DataGridRowType.Default) { foreach (var column in Grid._columns) { @@ -167,7 +167,12 @@ internal async Task HandleOnRowClickAsync(string rowId) internal async Task HandleOnRowDoubleClickAsync(string rowId) { var row = GetRow(rowId); - if (row != null && Grid.OnRowDoubleClick.HasDelegate) + if (row is null) + { + return; + } + + if (Grid.OnRowDoubleClick.HasDelegate) { await Grid.OnRowDoubleClick.InvokeAsync(row); } @@ -176,19 +181,24 @@ internal async Task HandleOnRowDoubleClickAsync(string rowId) /// internal async Task HandleOnRowKeyDownAsync(string rowId, KeyboardEventArgs e) { - if (!SelectColumn.KEYBOARD_SELECT_KEYS.Contains(e.Code, StringComparer.Ordinal)) + var row = GetRow(rowId); + + if (row is null) { return; } - var row = GetRow(rowId); + if (!SelectColumn.KEYBOARD_SELECT_KEYS.Contains(e.Code, StringComparer.Ordinal)) + { + return; + } - if (row != null && Grid.OnRowClick.HasDelegate) + if (Grid.OnRowClick.HasDelegate) { await Grid.OnRowClick.InvokeAsync(row); } - if (row != null && row.RowType == DataGridRowType.Default) + if (row.RowType == DataGridRowType.Default) { foreach (var column in Grid._columns) { @@ -197,13 +207,11 @@ internal async Task HandleOnRowKeyDownAsync(string rowId, KeyboardEventArgs e) } } - private FluentDataGridRow? GetRow(string rowId, Func, bool>? where = null) + private FluentDataGridRow? GetRow(string rowId) { if (!string.IsNullOrEmpty(rowId) && InternalGridContext.Rows.TryGetValue(rowId, out var row)) { - return where == null - ? row - : row is not null && where(row) ? row : null; + return row; } return null; diff --git a/tests/Core/Components/DataGrid/FluentDataGridRowTests.razor b/tests/Core/Components/DataGrid/FluentDataGridRowTests.razor index 04c92c22d0..0beda7cb64 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridRowTests.razor +++ b/tests/Core/Components/DataGrid/FluentDataGridRowTests.razor @@ -83,27 +83,124 @@ Assert.False(OnRowClickInvoked); } + [Fact] + public async Task FluentDataGridRow_HandleOnRowClickAsync_Class() + { + // Arrange + + var cut = Render>( + @ + + ); + + // Act + var row = cut.FindComponent>(); + await row.Instance.HandleOnRowClickAsync(row.Instance.RowId); + + // Assert + Assert.True(OnRowClickInvoked); + } + + [Fact] + public async Task FluentDataGridRow_HandleOnRowClickAsync_NoRowPassedIn() + { + // Arrange + var cut = Render>( + @ + + + + ); + // Act + var row = cut.FindComponent>(); + await row.Instance.HandleOnRowClickAsync(string.Empty); + + // Assert + Assert.False(OnRowClickInvoked); + } + + [Fact] + public async Task FluentDataGridRow_HandleOnRowDoubleClickAsync() + { + // Arrange + var People = new List() { }.AsQueryable(); + + var cut = Render>( + @ + + + + No Data + ); + + // Act + var row = cut.FindComponent>(); + await row.Instance.HandleOnRowDoubleClickAsync(row.Instance.RowId); + + // Assert + Assert.True(OnRowDoubleClickInvoked); + } + + [Fact] + public async Task FluentDataGridRow_HandleOnRowDoubleClickAsync_EmptyRow() + { + // Arrange + var People = new List() { }.AsQueryable(); + + var cut = Render>( + @ + + + + No Data + ); + + // Act + var row = cut.FindComponent>(); + await row.Instance.HandleOnRowClickAsync(row.Instance.RowId); + + // Assert + Assert.False(OnRowClickInvoked); + } [Fact] - public async Task FluentDataGridRow_HandleOnCellFocusAsync_InvokesCallbacks() + public async Task FluentDataGridRow_HandleOnRowDoubleClickAsync_NoRowPassedIn() { // Arrange var cut = Render>( - @ + @ + + + + ); + // Act + var row = cut.FindComponent>(); + await row.Instance.HandleOnRowDoubleClickAsync(string.Empty); + + // Assert + Assert.False(OnRowDoubleClickInvoked); + } + + [Fact] + public async Task FluentDataGridRow_HandleOnRowFocusAsync() + { + // Arrange + var cut = Render>( + @ ); // Act - var cell = cut.FindComponent>(); - await cell.Instance.HandleOnCellFocusAsync(); + var cell = cut.FindComponent>(); + await cell.Instance.HandleOnRowFocusAsync(); // Assert Assert.True(OnRowFocusInvoked); } [Fact] - public async Task FluentDataGridRow_HandleOnCellKeyDownAsync_HandlesKeyEnter() + public async Task FluentDataGridRow_HandleOnRowKeyDownAsync_HandlesKeyEnter() { // Arrange var items = new List(People).AsQueryable(); @@ -113,15 +210,15 @@ ); // Act var keyboardEvent = new KeyboardEventArgs { Code = "Enter" }; - var cell = cut.FindComponent>(); - await cell.Instance.HandleOnCellKeyDownAsync(keyboardEvent); + var row = cut.FindComponent>(); + await row.Instance.HandleOnRowKeyDownAsync(row.Instance.RowId, keyboardEvent); // Assert Assert.True(OnRowKeyDownInvoked); @@ -129,7 +226,7 @@ [Fact] - public async Task FluentDataGridRow_HandleOnCellKeyDownAsync_HandlesKeyOther() + public async Task FluentDataGridRow_HandleOnRowKeyDownAsync_HandlesKeyOther() { // Arrange var items = new List(People).AsQueryable(); @@ -139,15 +236,95 @@ ); // Act var keyboardEvent = new KeyboardEventArgs { Code = "A" }; - var cell = cut.FindComponent>(); - await cell.Instance.HandleOnCellKeyDownAsync(keyboardEvent); + var row = cut.FindComponent>(); + await row.Instance.HandleOnRowKeyDownAsync(row.Instance.RowId, keyboardEvent); + + // Assert + Assert.False(OnRowKeyDownInvoked); + } + + [Fact] + public async Task FluentDataGridRow_HandleOnRowKeyDownAsync_WithoutSelectColumn() + { + // Arrange + var items = new List(People).AsQueryable(); + + var cut = Render>( + @ + + ); + + // Act + var keyboardEvent = new KeyboardEventArgs { Code = "Enter" }; + var row = cut.FindComponent>(); + await row.Instance.HandleOnRowKeyDownAsync(row.Instance.RowId, keyboardEvent); + + // Assert + Assert.False(OnRowKeyDownInvoked); + } + + [Fact] + public async Task FluentDataGridRow_HandleOnRowKeyDownAsync_WithoutSelectColumn_NoDelegate() + { + // Arrange + var items = new List(People).AsQueryable(); + + var cut = Render>( + @ + + ); + + // Act + var keyboardEvent = new KeyboardEventArgs { Code = "Enter" }; + var row = cut.FindComponent>(); + await row.Instance.HandleOnRowKeyDownAsync(row.Instance.RowId, keyboardEvent); + + // Assert + Assert.False(OnRowKeyDownInvoked); + } + + [Fact] + public async Task FluentDataGridRow_HandleOnRowKeyDownAsync_WithoutSelectColumn_WithHeader() + { + // Arrange + var items = new List(People).AsQueryable(); + + var cut = Render>( + @ + + ); + + // Act + var keyboardEvent = new KeyboardEventArgs { Code = "Enter" }; + var row = cut.FindComponent>(); + await row.Instance.HandleOnRowKeyDownAsync(row.Instance.RowId, keyboardEvent); + + // Assert + Assert.False(OnRowKeyDownInvoked); + } + + [Fact] + public async Task FluentDataGridRow_HandleOnRowKeyDownAsync_NoRowPassedIn() + { + // Arrange + var items = new List(People).AsQueryable(); + + var cut = Render>( + @ + + ); + + // Act + var keyboardEvent = new KeyboardEventArgs { Code = "Enter" }; + var row = cut.FindComponent>(); + await row.Instance.HandleOnRowKeyDownAsync(string.Empty, keyboardEvent); // Assert Assert.False(OnRowKeyDownInvoked); @@ -166,16 +343,16 @@ ); // Act - var cell = grid.FindComponent>(); + var cell = grid.FindComponent>(); cell.Instance.Dispose(); cell.Dispose(); // Assert Assert.True(cell.IsDisposed); - } public bool OnRowClickInvoked { get; set; } + public bool OnRowDoubleClickInvoked { get; set; } public bool OnRowFocusInvoked { get; set; } public bool OnRowKeyDownInvoked { get; set; } @@ -188,6 +365,14 @@ OnRowClickInvoked = true; } + private void HandleRowDoubleClick() + { + // This method simulates the callback that would be invoked on cell click + // In a real scenario, this would be defined in the FluentDataGrid component + // and passed to the FluentDataGridCell. + OnRowDoubleClickInvoked = true; + } + private void HandleRowFocus() { // This method simulates the callback that would be invoked on cell focus From 924df75e6655f7f55d3be66016a854e0cc6d4913 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Thu, 19 Jun 2025 21:01:20 +0200 Subject: [PATCH 26/44] More tests --- spelling.dic | 7 + .../DataGrid/FluentDataGrid.razor.cs | 46 ++-- .../DataGrid/FluentDataGridRow.razor.cs | 44 ++-- .../DataGrid/FluentDataGridCellTests.razor | 5 +- .../FluentDataGridColumSelectTests.razor | 3 +- .../DataGrid/FluentDataGridIsFixedTests.razor | 3 +- .../DataGrid/FluentDataGridRowTests.razor | 243 ++++++++++++++++-- ...FluentDataGrid_Default.verified.razor.html | 2 +- .../DataGrid/FluentDataGridTests.razor | 9 +- 9 files changed, 283 insertions(+), 79 deletions(-) diff --git a/spelling.dic b/spelling.dic index 193240e27b..1697f4bd8c 100644 --- a/spelling.dic +++ b/spelling.dic @@ -88,3 +88,10 @@ beforetoggle resx yyyy Langa +Overscan +gipr +rowindex +rowcount +displaymode +Voituron +inputfile diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index 524a0a73e9..f31d2c676c 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -2,10 +2,10 @@ // MIT License - Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------------------ +using System.Diagnostics.CodeAnalysis; using System.Globalization; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web.Virtualization; -using Microsoft.Extensions.DependencyInjection; using Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; using Microsoft.FluentUI.AspNetCore.Components.Infrastructure; using Microsoft.FluentUI.AspNetCore.Components.Utilities; @@ -29,7 +29,6 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve private ElementReference? _gridReference; private Virtualize<(int, TGridItem)>? _virtualizeComponent; private IAsyncQueryExecutor? _asyncQueryExecutor; - private AsyncServiceScope? _scope; private readonly InternalGridContext _internalGridContext; internal readonly List> _columns; private bool _collectingColumns; @@ -79,7 +78,7 @@ public FluentDataGrid(LibraryConfiguration configuration) : base(configuration) private NavigationManager NavigationManager { get; set; } = default!; [Inject] - private IServiceScopeFactory ScopeFactory { get; set; } = default!; + private IServiceProvider Services { get; set; } = default!; [Inject] private IKeyCodeService KeyCodeService { get; set; } = default!; @@ -408,7 +407,7 @@ protected override void OnInitialized() } /// - protected override async Task OnParametersSetAsync() + protected override Task OnParametersSetAsync() { // The associated pagination state may have been added/removed/replaced _currentPageItemsChanged.SubscribeOrMove(Pagination?.CurrentPageItemsChanged); @@ -427,15 +426,9 @@ protected override async Task OnParametersSetAsync() var dataSourceHasChanged = !Equals(ItemsProvider, _lastAssignedItemsProvider) || !ReferenceEquals(Items, _lastAssignedItems); if (dataSourceHasChanged) { - if (_scope.HasValue) - { - await _scope.Value.DisposeAsync(); - } - - _scope = ScopeFactory.CreateAsyncScope(); _lastAssignedItemsProvider = ItemsProvider; _lastAssignedItems = Items; - _asyncQueryExecutor = AsyncQueryExecutorSupplier.GetAsyncQueryExecutor(_scope.Value.ServiceProvider, Items); + _asyncQueryExecutor = AsyncQueryExecutorSupplier.GetAsyncQueryExecutor(Services, Items); } var paginationStateHasChanged = @@ -447,7 +440,7 @@ protected override async Task OnParametersSetAsync() // We don't want to trigger the first data load until we've collected the initial set of columns, // because they might perform some action like setting the default sort order, so it would be wasteful // to have to re-query immediately - _ = (_columns.Count > 0 && mustRefreshData) ? RefreshDataCoreAsync() : Task.CompletedTask; + return (_columns.Count > 0 && mustRefreshData) ? RefreshDataCoreAsync() : Task.CompletedTask; } /// @@ -935,29 +928,20 @@ private string AriaSortValue(ColumnBase column) .Build(); } - /// - public override async ValueTask DisposeAsync() + /// + /// Unregister the grid events + /// + /// + /// + [ExcludeFromCodeCoverage] + protected override async ValueTask DisposeAsync(IJSObjectReference jsModule) { - GC.SuppressFinalize(this); - _currentPageItemsChanged.Dispose(); -#pragma warning disable MA0042 // Do not use blocking calls in an async method - _scope?.Dispose(); -#pragma warning restore MA0042 // Do not use blocking calls in an async method - try - { - if (_jsEventDisposable is not null) - { - await _jsEventDisposable.InvokeVoidAsync("stop"); - await _jsEventDisposable.DisposeAsync().ConfigureAwait(false); - } - } - catch (Exception ex) when (ex is JSDisconnectedException || - ex is OperationCanceledException) + if (_jsEventDisposable is not null) { - // The JSRuntime side may routinely be gone already if the reason we're disposing is that - // the client disconnected. This is not an error. + await _jsEventDisposable.InvokeVoidAsync("dispose"); + await _jsEventDisposable.DisposeAsync().ConfigureAwait(false); } } diff --git a/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs b/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs index 53e600deac..254f5e16be 100644 --- a/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs @@ -65,12 +65,6 @@ public FluentDataGridRow(LibraryConfiguration configuration) : base(configuratio [Parameter] public RenderFragment? ChildContent { get; set; } - /// - /// Gets or sets a callback for when a cell is focused. - /// - [Parameter] - public EventCallback> OnCellFocus { get; set; } - /// /// Gets or sets the owning component /// @@ -108,6 +102,7 @@ public void Dispose() } /// + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage(Justification = "Tested in aspnetcore code")] Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, object? arg) => callback.InvokeAsync(arg); @@ -142,19 +137,24 @@ internal async Task HandleOnRowClickAsync(string rowId) { var row = GetRow(rowId); - if (row is not null && !string.IsNullOrWhiteSpace(row.Class) && + if (row is null) + { + return; + } + + if (!string.IsNullOrWhiteSpace(row.Class) && (row.Class.Contains(FluentDataGrid.EMPTY_CONTENT_ROW_CLASS, StringComparison.Ordinal) || row.Class.Contains(FluentDataGrid.LOADING_CONTENT_ROW_CLASS, StringComparison.Ordinal))) { return; } - if (row != null && Grid.OnRowClick.HasDelegate) + if (Grid.OnRowClick.HasDelegate) { await Grid.OnRowClick.InvokeAsync(row); } - if (row != null && row.RowType == DataGridRowType.Default) + if (row.RowType == DataGridRowType.Default) { foreach (var column in Grid._columns) { @@ -167,7 +167,12 @@ internal async Task HandleOnRowClickAsync(string rowId) internal async Task HandleOnRowDoubleClickAsync(string rowId) { var row = GetRow(rowId); - if (row != null && Grid.OnRowDoubleClick.HasDelegate) + if (row is null) + { + return; + } + + if (Grid.OnRowDoubleClick.HasDelegate) { await Grid.OnRowDoubleClick.InvokeAsync(row); } @@ -176,19 +181,24 @@ internal async Task HandleOnRowDoubleClickAsync(string rowId) /// internal async Task HandleOnRowKeyDownAsync(string rowId, KeyboardEventArgs e) { - if (!SelectColumn.KEYBOARD_SELECT_KEYS.Contains(e.Code, StringComparer.Ordinal)) + var row = GetRow(rowId); + + if (row is null) { return; } - var row = GetRow(rowId); + if (!SelectColumn.KEYBOARD_SELECT_KEYS.Contains(e.Code, StringComparer.Ordinal)) + { + return; + } - if (row != null && Grid.OnRowClick.HasDelegate) + if (Grid.OnRowClick.HasDelegate) { await Grid.OnRowClick.InvokeAsync(row); } - if (row != null && row.RowType == DataGridRowType.Default) + if (row.RowType == DataGridRowType.Default) { foreach (var column in Grid._columns) { @@ -197,13 +207,11 @@ internal async Task HandleOnRowKeyDownAsync(string rowId, KeyboardEventArgs e) } } - private FluentDataGridRow? GetRow(string rowId, Func, bool>? where = null) + private FluentDataGridRow? GetRow(string rowId) { if (!string.IsNullOrEmpty(rowId) && InternalGridContext.Rows.TryGetValue(rowId, out var row)) { - return where == null - ? row - : row is not null && where(row) ? row : null; + return row; } return null; diff --git a/tests/Core/Components/DataGrid/FluentDataGridCellTests.razor b/tests/Core/Components/DataGrid/FluentDataGridCellTests.razor index 9bc6518fe3..de80f1e281 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridCellTests.razor +++ b/tests/Core/Components/DataGrid/FluentDataGridCellTests.razor @@ -4,7 +4,7 @@ @code { - private readonly IQueryable People = new[] + private readonly IQueryable People = new[] { new Person(1, "Jean Martin", new DateOnly(1985, 3, 16)), new Person(2, "Kenji Sato", new DateOnly(2004, 1, 9)), @@ -13,8 +13,7 @@ public FluentDataGridCellTests() { - var dataGridModule = JSInterop.SetupModule("./_content/Microsoft.FluentUI.AspNetCore.Components/Components/DataGrid/FluentDataGrid.razor.js"); - dataGridModule.SetupModule("init", _ => true); + JSInterop.Mode = JSRuntimeMode.Loose; // Register services Services.AddFluentUIComponents(); diff --git a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.razor b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.razor index 79a983f010..a4bacdeb53 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.razor +++ b/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.razor @@ -17,8 +17,7 @@ public FluentDataGridColumSelectTests() { - var dataGridModule = JSInterop.SetupModule("./_content/Microsoft.FluentUI.AspNetCore.Components/Components/DataGrid/FluentDataGrid.razor.js"); - dataGridModule.SetupModule("init", _ => true); + JSInterop.Mode = JSRuntimeMode.Loose; // Register services Services.AddFluentUIComponents(); diff --git a/tests/Core/Components/DataGrid/FluentDataGridIsFixedTests.razor b/tests/Core/Components/DataGrid/FluentDataGridIsFixedTests.razor index 5024cc9a81..39ffd280b6 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridIsFixedTests.razor +++ b/tests/Core/Components/DataGrid/FluentDataGridIsFixedTests.razor @@ -4,8 +4,7 @@ @code { public FluentDataGridIsFixedTests() { - var dataGridModule = JSInterop.SetupModule("./_content/Microsoft.FluentUI.AspNetCore.Components/Components/DataGrid/FluentDataGrid.razor.js"); - dataGridModule.SetupModule("init", _ => true); + JSInterop.Mode = JSRuntimeMode.Loose; // Register services Services.AddFluentUIComponents(); diff --git a/tests/Core/Components/DataGrid/FluentDataGridRowTests.razor b/tests/Core/Components/DataGrid/FluentDataGridRowTests.razor index 04c92c22d0..6cddecf7da 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridRowTests.razor +++ b/tests/Core/Components/DataGrid/FluentDataGridRowTests.razor @@ -4,7 +4,7 @@ @code { - private readonly IQueryable People = new[] + private readonly IQueryable People = new[] { new Person(1, "Jean Martin", new DateOnly(1985, 3, 16)), new Person(2, "Kenji Sato", new DateOnly(2004, 1, 9)), @@ -13,8 +13,7 @@ public FluentDataGridRowTests() { - var dataGridModule = JSInterop.SetupModule("./_content/Microsoft.FluentUI.AspNetCore.Components/Components/DataGrid/FluentDataGrid.razor.js"); - dataGridModule.SetupModule("init", _ => true); + JSInterop.Mode = JSRuntimeMode.Loose; // Register services Services.AddFluentUIComponents(); @@ -42,6 +41,31 @@ Assert.NotNull(row.Instance.ChildContent); } + + [Fact] + public void FluentDataGridRow_TestNativeHandlers() + { + // Arrange + var grid = Render>( + @ + + + + ); + + // Act + var row = grid.FindComponent>(); + + // Assert + Assert.NotNull(grid); + Assert.NotNull(row); + Assert.Equal(DataGridRowType.Default, row.Instance.RowType); + row.Find("tr").Click(); + row.Find("tr").DoubleClick(); + row.Find("tr").KeyDown(new KeyboardEventArgs { Code = "Enter" }); + Assert.NotNull(row.Instance.ChildContent); + } + [Fact] public async Task FluentDataGridRow_HandleOnRowClickAsync_InvokesCallbacks() { @@ -83,27 +107,124 @@ Assert.False(OnRowClickInvoked); } + [Fact] + public async Task FluentDataGridRow_HandleOnRowClickAsync_Class() + { + // Arrange + + var cut = Render>( + @ + + ); + + // Act + var row = cut.FindComponent>(); + await row.Instance.HandleOnRowClickAsync(row.Instance.RowId); + + // Assert + Assert.True(OnRowClickInvoked); + } + + [Fact] + public async Task FluentDataGridRow_HandleOnRowClickAsync_NoRowPassedIn() + { + // Arrange + var cut = Render>( + @ + + + + ); + // Act + var row = cut.FindComponent>(); + await row.Instance.HandleOnRowClickAsync(string.Empty); + + // Assert + Assert.False(OnRowClickInvoked); + } + + [Fact] + public async Task FluentDataGridRow_HandleOnRowDoubleClickAsync() + { + // Arrange + var People = new List() { }.AsQueryable(); + + var cut = Render>( + @ + + + + No Data + ); + + // Act + var row = cut.FindComponent>(); + await row.Instance.HandleOnRowDoubleClickAsync(row.Instance.RowId); + + // Assert + Assert.True(OnRowDoubleClickInvoked); + } + + [Fact] + public async Task FluentDataGridRow_HandleOnRowDoubleClickAsync_EmptyRow() + { + // Arrange + var People = new List() { }.AsQueryable(); + + var cut = Render>( + @ + + + + No Data + ); + + // Act + var row = cut.FindComponent>(); + await row.Instance.HandleOnRowClickAsync(row.Instance.RowId); + + // Assert + Assert.False(OnRowClickInvoked); + } + + [Fact] + public async Task FluentDataGridRow_HandleOnRowDoubleClickAsync_NoRowPassedIn() + { + // Arrange + var cut = Render>( + @ + + + + ); + // Act + var row = cut.FindComponent>(); + await row.Instance.HandleOnRowDoubleClickAsync(string.Empty); + + // Assert + Assert.False(OnRowDoubleClickInvoked); + } [Fact] - public async Task FluentDataGridRow_HandleOnCellFocusAsync_InvokesCallbacks() + public async Task FluentDataGridRow_HandleOnRowFocusAsync() { // Arrange var cut = Render>( - @ + @ ); // Act - var cell = cut.FindComponent>(); - await cell.Instance.HandleOnCellFocusAsync(); + var cell = cut.FindComponent>(); + await cell.Instance.HandleOnRowFocusAsync(); // Assert Assert.True(OnRowFocusInvoked); } [Fact] - public async Task FluentDataGridRow_HandleOnCellKeyDownAsync_HandlesKeyEnter() + public async Task FluentDataGridRow_HandleOnRowKeyDownAsync_HandlesKeyEnter() { // Arrange var items = new List(People).AsQueryable(); @@ -113,15 +234,15 @@ ); // Act var keyboardEvent = new KeyboardEventArgs { Code = "Enter" }; - var cell = cut.FindComponent>(); - await cell.Instance.HandleOnCellKeyDownAsync(keyboardEvent); + var row = cut.FindComponent>(); + await row.Instance.HandleOnRowKeyDownAsync(row.Instance.RowId, keyboardEvent); // Assert Assert.True(OnRowKeyDownInvoked); @@ -129,7 +250,7 @@ [Fact] - public async Task FluentDataGridRow_HandleOnCellKeyDownAsync_HandlesKeyOther() + public async Task FluentDataGridRow_HandleOnRowKeyDownAsync_HandlesKeyOther() { // Arrange var items = new List(People).AsQueryable(); @@ -139,15 +260,95 @@ ); // Act var keyboardEvent = new KeyboardEventArgs { Code = "A" }; - var cell = cut.FindComponent>(); - await cell.Instance.HandleOnCellKeyDownAsync(keyboardEvent); + var row = cut.FindComponent>(); + await row.Instance.HandleOnRowKeyDownAsync(row.Instance.RowId, keyboardEvent); + + // Assert + Assert.False(OnRowKeyDownInvoked); + } + + [Fact] + public async Task FluentDataGridRow_HandleOnRowKeyDownAsync_WithoutSelectColumn() + { + // Arrange + var items = new List(People).AsQueryable(); + + var cut = Render>( + @ + + ); + + // Act + var keyboardEvent = new KeyboardEventArgs { Code = "Enter" }; + var row = cut.FindComponent>(); + await row.Instance.HandleOnRowKeyDownAsync(row.Instance.RowId, keyboardEvent); + + // Assert + Assert.False(OnRowKeyDownInvoked); + } + + [Fact] + public async Task FluentDataGridRow_HandleOnRowKeyDownAsync_WithoutSelectColumn_NoDelegate() + { + // Arrange + var items = new List(People).AsQueryable(); + + var cut = Render>( + @ + + ); + + // Act + var keyboardEvent = new KeyboardEventArgs { Code = "Enter" }; + var row = cut.FindComponent>(); + await row.Instance.HandleOnRowKeyDownAsync(row.Instance.RowId, keyboardEvent); + + // Assert + Assert.False(OnRowKeyDownInvoked); + } + + [Fact] + public async Task FluentDataGridRow_HandleOnRowKeyDownAsync_WithoutSelectColumn_WithHeader() + { + // Arrange + var items = new List(People).AsQueryable(); + + var cut = Render>( + @ + + ); + + // Act + var keyboardEvent = new KeyboardEventArgs { Code = "Enter" }; + var row = cut.FindComponent>(); + await row.Instance.HandleOnRowKeyDownAsync(row.Instance.RowId, keyboardEvent); + + // Assert + Assert.False(OnRowKeyDownInvoked); + } + + [Fact] + public async Task FluentDataGridRow_HandleOnRowKeyDownAsync_NoRowPassedIn() + { + // Arrange + var items = new List(People).AsQueryable(); + + var cut = Render>( + @ + + ); + + // Act + var keyboardEvent = new KeyboardEventArgs { Code = "Enter" }; + var row = cut.FindComponent>(); + await row.Instance.HandleOnRowKeyDownAsync(string.Empty, keyboardEvent); // Assert Assert.False(OnRowKeyDownInvoked); @@ -166,16 +367,16 @@ ); // Act - var cell = grid.FindComponent>(); + var cell = grid.FindComponent>(); cell.Instance.Dispose(); cell.Dispose(); // Assert Assert.True(cell.IsDisposed); - } public bool OnRowClickInvoked { get; set; } + public bool OnRowDoubleClickInvoked { get; set; } public bool OnRowFocusInvoked { get; set; } public bool OnRowKeyDownInvoked { get; set; } @@ -188,6 +389,14 @@ OnRowClickInvoked = true; } + private void HandleRowDoubleClick() + { + // This method simulates the callback that would be invoked on cell click + // In a real scenario, this would be defined in the FluentDataGrid component + // and passed to the FluentDataGridCell. + OnRowDoubleClickInvoked = true; + } + private void HandleRowFocus() { // This method simulates the callback that would be invoked on cell focus diff --git a/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_Default.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_Default.verified.razor.html index c1509a4e8f..2f54afb00b 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_Default.verified.razor.html +++ b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_Default.verified.razor.html @@ -2,7 +2,7 @@ - - - + - + - +
+
Name
diff --git a/tests/Core/Components/DataGrid/FluentDataGridTests.razor b/tests/Core/Components/DataGrid/FluentDataGridTests.razor index 566fb53cbc..1f2b170157 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridTests.razor +++ b/tests/Core/Components/DataGrid/FluentDataGridTests.razor @@ -4,8 +4,7 @@ @code { public FluentDataGridTests() { - var dataGridModule = JSInterop.SetupModule("./_content/Microsoft.FluentUI.AspNetCore.Components/Components/DataGrid/FluentDataGrid.razor.js"); - dataGridModule.SetupModule("init", _ => true); + JSInterop.Mode = JSRuntimeMode.Loose; // Register services Services.AddFluentUIComponents(); @@ -19,7 +18,7 @@ var cut = Render>( @ - +

empty content

); @@ -49,8 +48,8 @@ { // Arrange && Act var cut = Render>( - @ From 8fe02c02c0da369cf17b82f6bffb2203d4acad33 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Mon, 23 Jun 2025 11:37:02 +0200 Subject: [PATCH 27/44] - Add typical demo - Fix error with flags - Fix css colors - Add tests --- .../Components/DataGrid/FluentDataGrid.md | 2 +- .../FluentUI.Demo.SampleData/Olympics2024.cs | 2 +- .../Components/DataGrid/FluentDataGrid.razor | 4 +- .../DataGrid/FluentDataGrid.razor.cs | 54 +- .../DataGrid/FluentDataGrid.razor.css | 2 +- ...luentDataGrid_NoHeader.verified.razor.html | 14 + ...tDataGrid_StickyHeader.verified.razor.html | 25 + .../DataGrid/FluentDataGridTests.razor | 648 +++++++++++++++++- 8 files changed, 713 insertions(+), 38 deletions(-) create mode 100644 tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_NoHeader.verified.razor.html create mode 100644 tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_StickyHeader.verified.razor.html diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md index e49a87b926..88c8de7a0e 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md @@ -25,6 +25,6 @@ Pressing enter finishes the filter action by the current input to filter on and The resize options UI is using a customized string for the label ('Width (+/- 10px)' instead of the normal 'Column width'). This is done through the custom localizer which is registered in the Server project's `Program.cs` file. - +{{ DataGridTypical }} {{ DataGridMultiSelect }} diff --git a/examples/Tools/FluentUI.Demo.SampleData/Olympics2024.cs b/examples/Tools/FluentUI.Demo.SampleData/Olympics2024.cs index 1cb9583b05..10036f8f22 100644 --- a/examples/Tools/FluentUI.Demo.SampleData/Olympics2024.cs +++ b/examples/Tools/FluentUI.Demo.SampleData/Olympics2024.cs @@ -105,7 +105,7 @@ public static IEnumerable Countries yield return new Country("ch", "Switzerland", new Medals(Gold: 1, Silver: 2, Bronze: 5)); yield return new Country("th", "Thailand", new Medals(Gold: 1, Silver: 3, Bronze: 2)); yield return new Country("tj", "Tajikistan", new Medals(Gold: 0, Silver: 0, Bronze: 3)); - yield return new Country("tpe", "Chinese Taipei", new Medals(Gold: 2, Silver: 0, Bronze: 5)); + yield return new Country("tw", "Chinese Taipei", new Medals(Gold: 2, Silver: 0, Bronze: 5)); yield return new Country("tn", "Tunisia", new Medals(Gold: 1, Silver: 1, Bronze: 1)); yield return new Country("tr", "Turkey", new Medals(Gold: 0, Silver: 3, Bronze: 5)); yield return new Country("ug", "Uganda", new Medals(Gold: 1, Silver: 1, Bronze: 0)); diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor b/src/Core/Components/DataGrid/FluentDataGrid.razor index edffbe0d82..f8631b38ce 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor @@ -23,8 +23,8 @@ class="@GridClass()" style="@StyleValue" aria-rowcount="@(_internalGridContext.TotalItemCount + 1)" - @onclosecolumnoptions="CloseColumnOptions" - @onclosecolumnresize="CloseColumnResize" + @onclosecolumnoptions="CloseColumnOptionsAsync" + @onclosecolumnresize="CloseColumnResizeAsync" @attributes="AdditionalAttributes"> @if (GenerateHeader != DataGridGeneratedHeaderType.None) { diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index f31d2c676c..fea828bd21 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -33,7 +33,7 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve internal readonly List> _columns; private bool _collectingColumns; private ColumnBase? _displayOptionsForColumn; - private ColumnBase? _displayResizeForColumn; + internal ColumnBase? _displayResizeForColumn; private ColumnBase? _sortByColumn; private bool _sortByAscending; private bool _checkColumnOptionsPosition; @@ -452,18 +452,11 @@ protected override async Task OnAfterRenderAsync(bool firstRender) // Import the JavaScript module await JSModule.ImportJavaScriptModuleAsync(JAVASCRIPT_FILE); - try + _jsEventDisposable = await JSModule.ObjectReference.InvokeAsync("init", _gridReference, AutoFocus); + if (AutoItemsPerPage) { - _jsEventDisposable = await JSModule.ObjectReference.InvokeAsync("init", _gridReference, AutoFocus); - if (AutoItemsPerPage) - { - await JSModule.ObjectReference.InvokeVoidAsync("dynamicItemsPerPage", _gridReference, DotNetObjectReference.Create(this), (int)RowSize); - } - } - catch (JSException ex) - { - Console.WriteLine("[FluentDataGrid] " + ex.Message); + await JSModule.ObjectReference.InvokeVoidAsync("dynamicItemsPerPage", _gridReference, DotNetObjectReference.Create(this), (int)RowSize); } } @@ -553,12 +546,12 @@ public Task SortByColumnAsync(ColumnBase column, DataGridSortDirectio DataGridSortDirection.Ascending => true, DataGridSortDirection.Descending => false, DataGridSortDirection.Auto => _sortByColumn != column || !_sortByAscending, - _ => throw new NotSupportedException($"Unknown sort direction {direction}"), + _ => true, }; _sortByColumn = column; - StateHasChanged(); // We want to see the updated sort order in the header, even before the data query is completed + _ = InvokeAsync(StateHasChanged); // We want to see the updated sort order in the header, even before the data query is completed return RefreshDataAsync(); } @@ -598,7 +591,7 @@ public Task RemoveSortByColumnAsync(ColumnBase column) _sortByColumn = _internalGridContext.DefaultSortColumn.Column ?? null; _sortByAscending = _internalGridContext.DefaultSortColumn.Direction != DataGridSortDirection.Descending; - StateHasChanged(); // We want to see the updated sort order in the header, even before the data query is completed + _ = InvokeAsync(StateHasChanged); // We want to see the updated sort order in the header, even before the data query is completed return RefreshDataCoreAsync(); } @@ -621,7 +614,7 @@ public Task ShowColumnOptionsAsync(ColumnBase column) { _displayOptionsForColumn = column; _checkColumnOptionsPosition = true; // Triggers a call to JSRuntime to position the options element, apply autofocus, and any other setup - StateHasChanged(); + _ = InvokeAsync(StateHasChanged); return Task.CompletedTask; } @@ -654,10 +647,21 @@ public Task ShowColumnOptionsAsync(int index) public Task CloseColumnOptionsAsync() { _displayOptionsForColumn = null; - StateHasChanged(); + _ = InvokeAsync(StateHasChanged); return Task.CompletedTask; } + /// + /// Closes the column resize UI that was previously displayed. + /// + public Task CloseColumnResizeAsync() + { + _displayResizeForColumn = null; + _ = InvokeAsync(StateHasChanged); + return Task.CompletedTask; + + } + /// /// Displays the column resize UI for the specified column, closing any other column /// resize UI that was previously displayed. @@ -668,7 +672,7 @@ public Task ShowColumnResizeAsync(ColumnBase column) { _displayResizeForColumn = column; _checkColumnResizePosition = true; // Triggers a call to JSRuntime to position the options element, apply autofocus, and any other setup - StateHasChanged(); + _ = InvokeAsync(StateHasChanged); return Task.CompletedTask; } @@ -787,7 +791,7 @@ private async Task RefreshDataCoreAsync() // Debounce the requests. This eliminates a lot of redundant queries at the cost of slight lag after interactions. // TODO: Consider making this configurable, or smarter (e.g., doesn't delay on first call in a batch, then the amount // of delay increases if you rapidly issue repeated requests, such as when scrolling a long way) - await Task.Delay(100, request.CancellationToken); + await Task.Delay(100); if (request.CancellationToken.IsCancellationRequested) { @@ -945,19 +949,7 @@ protected override async ValueTask DisposeAsync(IJSObjectReference jsModule) } } - private void CloseColumnOptions() - { - _displayOptionsForColumn = null; - StateHasChanged(); - } - - private void CloseColumnResize() - { - _displayResizeForColumn = null; - StateHasChanged(); - } - - private void LoadStateFromQueryString(string queryString) + internal void LoadStateFromQueryString(string queryString) { if (!SaveStateInUrl) { diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.css b/src/Core/Components/DataGrid/FluentDataGrid.razor.css index d71f7f0449..37e7df7bca 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.css +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.css @@ -31,7 +31,7 @@ position: absolute; min-width: 250px; top: 2.7rem; - background: var(--colorNeutralBackground1Hover); + background: var(--colorNeutralBackground1); border: 1px solid var(--colorNeutralBackground1Pressed); border-radius: 0.3rem; box-shadow: var(--shadow8); diff --git a/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_NoHeader.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_NoHeader.verified.razor.html new file mode 100644 index 0000000000..c3d66f4c83 --- /dev/null +++ b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_NoHeader.verified.razor.html @@ -0,0 +1,14 @@ + + + + + + + + + + + + + +
Denis Voituron
Vincent Baaij
Bill Gates
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_StickyHeader.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_StickyHeader.verified.razor.html new file mode 100644 index 0000000000..42bce80e07 --- /dev/null +++ b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_StickyHeader.verified.razor.html @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + +
+
+
+
Name
+
+
+
Denis Voituron
Vincent Baaij
Bill Gates
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridTests.razor b/tests/Core/Components/DataGrid/FluentDataGridTests.razor index 1f2b170157..815b8b582b 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridTests.razor +++ b/tests/Core/Components/DataGrid/FluentDataGridTests.razor @@ -1,4 +1,5 @@ -@using Xunit +@using Bunit.TestDoubles +@using Xunit @inherits Bunit.TestContext @code { @@ -27,6 +28,40 @@ cut.Verify(); } + [Fact] + public void FluentDataGrid_NoHeader() + { + // Arrange && Act + var cut = Render>( + @ + + + +

empty content

+
); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentDataGrid_StickyHeader() + { + // Arrange && Act + var cut = Render>( + @ + + + +

empty content

+
); + + // Assert + cut.Verify(); + } + + + [Fact] public void FluentDataGrid_ResizeColumnOnAllRows_Default() { @@ -85,6 +120,253 @@ Assert.NotNull(cut.Find("#empty-content")); } + [Fact] + public void FluentDataGrid_EmptyContent_TableMode() + { + // Arrange && Act + var cut = Render>( + @ +

empty content

+ + + +
); + + // Assert + Assert.NotNull(cut.Find("#empty-content")); + } + + [Fact] + public void FluentDataGrid_With_Empty_Items_Stays_Loading_Until_Changed_TableMode() + { + // Arrange && Act + var cut = Render>( + @ +

empty content

+

loading content

+ + + +
); + + // Assert + Assert.NotNull(cut.Find("#loading-content")); + Assert.Throws(() => cut.Find("#empty-content")); + + cut.SetParametersAndRender(parameters => parameters + .Add(p => p.Loading, false)); + + Assert.Throws(() => cut.Find("#loading-content")); + Assert.NotNull(cut.Find("#empty-content")); + } + + [Fact] + public void FluentDataGrid_RowStyle() + { + // Arrange && Act + Func rowStyle = x => x.Name.StartsWith("Vincent") ? "background-color: var(--highlight-bg)" : null; + var grid = Render>( + @ + + ); + + // Assert + var rows = grid.FindComponents>(); + + Assert.NotEmpty(rows); // Asserting that there are rows present + + var row1 = rows.FirstOrDefault(r => r.Instance.Item?.Name == "Denis Voituron"); + Assert.NotNull(row1); + Assert.Null(row1.Instance.Style); // Should not have a style + + + var row2 = rows.FirstOrDefault(r => r.Instance.Item?.Name == "Vincent Baaij"); + + Assert.NotNull(row2); + Assert.Equal("background-color: var(--highlight-bg)", row2.Instance.Style); // Should have a style + } + + [Fact] + public void FluentDataGrid_Multiline_Virtualize() + { + // Arrange && Act + // Assert + Assert.Throws(() => + { + var grid = Render>( + @ + + + ); + }); + } + + [Fact] + public void FluentDataGrid_GridTemplateColumns_And_Width() + { + // Arrange && Act + // Assert + Assert.Throws(() => + { + var grid = Render>( + @ + + + ); + }); + } + + [Fact] + public void FluentDataGrid_Items_And_ItemsProvider() + { + // Arrange && Act + ValueTask> GetItems(GridItemsProviderRequest request) + { + return ValueTask.FromResult(GridItemsProviderResult.From( + Array.Empty(), + 0)); + } + // Assert + Assert.Throws(() => + { + var grid = Render>( + @ + + + ); + }); + } + + [Fact] + public void FluentDataGrid_Virtualize() + { + + // Arrange && Act + + var items = GetRandomCustomers(); // Added method call to assign items + var grid = Render>( + @
+ + + +
+ ); + + // Assert + var rows = grid.FindComponents>(); + + Assert.NotEmpty(rows); // Asserting that there are rows present + Assert.Single(rows); //In bUnit the actual height of the grid can't be determined, so we just check that at least one row is rendered. + + } + + [Fact] + public void FluentDataGrid_AutoItems() + { + // Arrange && Act + var grid = Render>( + @ + + + ); + + // Assert + var rows = grid.FindComponents>(); + + Assert.NotEmpty(rows); // Asserting that there are rows present + + } + + [Fact] + public void FluentDataGrid_AutoFit() + { + // Arrange && Act + + // Assert + var grid = Render>( + @ + + + ); + + // Assert + var rows = grid.FindComponents>(); + + Assert.NotEmpty(rows); // Asserting that there are rows present + + } + + [Fact] + public void FluentDataGrid_LoadState_Sort() + { + // Arrange && Act + FluentDataGrid? grid = default!; + //var navMan = Services.GetRequiredService(); + + //navMan.NavigateTo("?gr1_orderby=Name%20asc"); + + var cut = Render>( + @ + + + ); + grid.LoadStateFromQueryString("?gr1_orderby=Name%20asc"); + + // Assert + var row = cut.FindComponent>(); + Assert.Equal("Bill Gates", row.Instance.Item?.Name); + } + + [Fact] + public void FluentDataGrid_LoadState_Pagination() + { + // Arrange && Act + var navMan = Services.GetRequiredService(); + PaginationState state = new(); + + state.ItemsPerPage = 1; + + navMan.NavigateTo("?gr1_orderby=asc&gr1_page=2&gr1_top=1"); + + var grid = Render>( + @ + + + ); + + // Assert + var row = grid.FindComponent>(); + Assert.Equal("Denis Voituron", row.Instance.Item?.Name); + } + + [Fact] + public async Task FluentDataGrid_Pagination_ChangeItemsPerPage() + { + // Arrange && Act + PaginationState state = new(); + + state.ItemsPerPage = 1; + + + var grid = Render>( + @ + + + ); + + + // Assert + var rows = grid.FindComponents>(); + + Assert.Single(rows); // Should only have one row displayed due to ItemsPerPage = 1 + + state.ItemsPerPage = 2; + await grid.InvokeAsync(() => state.SetCurrentPageIndexAsync(0)); // Reset to first page + + rows = grid.FindComponents>(); + Assert.Equal(2, rows.Count); + } + [Fact] public async Task FluentDataGrid_With_ItemProvider_Stays_Loading_Until_ChangedAsync() { @@ -143,7 +425,8 @@

@context.Name

- ); + + ); // Assert var dataGrid = cut.Instance; @@ -176,6 +459,355 @@ Assert.Throws(() => cut.Find("#loading-content")); } + [Fact] + public void FluentDataGrid_SetLoadingState() + { + // Arrange && Act + FluentDataGrid? grid = default!; + + var cut = Render>( + @ + + + + + + ); + + // Assert + grid.SetLoadingState(true); + Assert.Equal(true, grid.Loading); + + grid.SetLoadingState(false); + Assert.Equal(false, grid.Loading); + + + } + + [Fact] + public async Task FluentDataGrid_ShowColumnOptionAsync() + { + // Arrange && Act + FluentDataGrid? grid = default!; + + var cut = Render>( + @ + + + +
Hello!
+
+
+
+
+ ); + + await grid.ShowColumnOptionsAsync(0); + + var columnOptions = cut.Find(".options").TextContent; + + // Assert + Assert.Equal("Hello!", columnOptions); + + } + + [Fact] + public async Task FluentDataGrid_ShowColumnOptionAsync_ByName() + { + // Arrange && Act + FluentDataGrid? grid = default!; + + var cut = Render>( + @ + + + +
Hello!
+
+
+
+
+ ); + + await grid.ShowColumnOptionsAsync("Name"); + + var columnOptions = cut.Find(".options").TextContent; + + // Assert + Assert.Equal("Hello!", columnOptions); + + } + + [Fact] + public async Task FluentDataGrid_CloseColumnOptionAsync() + { + // Arrange && Act + FluentDataGrid? grid = default!; + + var cut = Render>( + @ + + + +
Hello!
+
+
+
+
+ ); + + await grid.ShowColumnOptionsAsync(0); + + var columnOptions = cut.Find(".options").TextContent; + + // Assert + Assert.Equal("Hello!", columnOptions); + + await grid.CloseColumnOptionsAsync(); + + Assert.Throws(() => cut.Find(".options")); + + + } + + [Fact] + public async Task FluentDataGrid_ShowColumnResizeAsync() + { + // Arrange && Act + FluentDataGrid? grid = default!; + + var cut = Render>( + @ + + + ); + + var row = cut.FindComponent>(); + + row.Find(".col-options-button").Click(); + + await grid.ShowColumnResizeAsync("Name"); + + var resizeOptions = cut.Find("fluent-label").TextContent; + + // Assert + Assert.Equal("Column width", resizeOptions); + + } + + [Fact] + public async Task FluentDataGrid_ShowColumnResizeAsync_InvalidColumName() + { + // Arrange && Act + FluentDataGrid? grid = default!; + + var cut = Render>( + @ + + + ); + + await grid.ShowColumnResizeAsync("InvalidColumnName"); + + // Assert that the display resize for column is null as the column name is invalid + Assert.Null(grid._displayResizeForColumn); + + } + + [Fact] + public async Task FluentDataGrid_ShowColumnResizeAsync_ByIndex() + { + // Arrange && Act + FluentDataGrid? grid = default!; + + var cut = Render>( + @ + + + ); + + var row = cut.FindComponent>(); + + row.Find(".col-options-button").Click(); + + await grid.ShowColumnResizeAsync(0); + + var resizeOptions = cut.Find("fluent-label").TextContent; + + // Assert + Assert.Equal("Column width", resizeOptions); + + } + + [Fact] + public async Task FluentDataGrid_ShowColumnResizeAsync_ByIndexOutOfBounds() + { + // Arrange && Act + FluentDataGrid? grid = default!; + + var cut = Render>( + @ + + + ); + + //var row = cut.FindComponent>(); + //row.Find(".col-options-button").Click(); + + await grid.ShowColumnResizeAsync(-1); + + // Assert + Assert.Null(grid._displayResizeForColumn); + + await grid.ShowColumnResizeAsync(3); + + // Assert + Assert.Null(grid._displayResizeForColumn); + + } + + [Fact] + public async Task FluentDataGrid_CloseColumnResizeAsync() + { + // Arrange && Act + FluentDataGrid? grid = default!; + + var cut = Render>( + @ + + + ); + + var row = cut.FindComponent>(); + await grid.ShowColumnResizeAsync("Name"); + + // Assert + Assert.NotNull(grid._displayResizeForColumn); + + await grid.CloseColumnResizeAsync(); + + Assert.Null(grid._displayResizeForColumn); + + } + + + [Theory] + [InlineData(DataGridSortDirection.Ascending, "Bill Gates")] + [InlineData(DataGridSortDirection.Descending, "Vincent Baaij")] + [InlineData(DataGridSortDirection.Auto, "Vincent Baaij")] + [InlineData((DataGridSortDirection)3, "Bill Gates")] //Defaults to Ascending + public void FluentDataGrid_SortByColumn_ByColumnIndex(DataGridSortDirection sortDirection, string expectedValue) + { + // Arrange && Act + FluentDataGrid? grid = default!; + + var cut = Render>( + @ + + + ); + + grid.SortByColumnAsync(0, sortDirection); + + var row = cut.FindComponent>(); + + // Assert + Assert.Equal(expectedValue, row.Instance.InternalGridContext.Items.First().Name); + + } + + [Theory] + [InlineData(DataGridSortDirection.Ascending, "Bill Gates")] + [InlineData(DataGridSortDirection.Descending, "Vincent Baaij")] + [InlineData(DataGridSortDirection.Auto, "Vincent Baaij")] + [InlineData((DataGridSortDirection)3, "Bill Gates")] //Defaults to Ascending + public void FluentDataGrid_SortByColumn_ByColumnName(DataGridSortDirection sortDirection, string expectedValue) + { + // Arrange && Act + FluentDataGrid? grid = default!; + + var cut = Render>( + @ + + + ); + + grid.SortByColumnAsync("Name", sortDirection); + + var row = cut.FindComponent>(); + + // Assert + Assert.Equal(expectedValue, row.Instance.InternalGridContext.Items.First().Name); + + } + + [Fact] + public void FluentDataGrid_RemoveSortByColumn() + { + // Arrange && Act + FluentDataGrid? grid = default!; + + var cut = Render>( + @ + + + + ); + + grid.SortByColumnAsync("Name", DataGridSortDirection.Descending); + var row = cut.FindComponent>(); + + // Assert + Assert.Equal(2, row.Instance.InternalGridContext.Items.First().Id); + + grid.RemoveSortByColumnAsync(); + row = cut.FindComponent>(); + + Assert.Equal(1, row.Instance.InternalGridContext.Items.First().Id); + + } + + + [Fact] + public void FluentDataGrid_RemoveSortByColumn_DefaultSort() + { + // Arrange && Act + FluentDataGrid? grid = default!; + + var cut = Render>( + @ + + + + ); + + grid.SortByColumnAsync("Id", DataGridSortDirection.Descending); + var row = cut.FindComponent>(); + + // Assert + Assert.Equal(3, row.Instance.InternalGridContext.Items.First().Id); + + grid.RemoveSortByColumnAsync(); + row = cut.FindComponent>(); + + Assert.Equal(3, row.Instance.InternalGridContext.Items.First().Id); + + } // // Add a test to call DisposeAsync on the component // [Fact] // public async Task FluentDataGrid_DisposeAsync_Calls_Dispose() @@ -203,4 +835,16 @@ } private record Customer(int Id, string Name); + + private IQueryable GetRandomCustomers(int size = 500) + { + Customer[] data = new Customer[size]; + + for (int i = 0; i < size; i++) + { + + data[i] = new Customer(i, $"Customer {i} - {Guid.NewGuid().ToString("N").Substring(0, 8)}" ); + } + return data.AsQueryable(); + } } From abb696c49f829181236e927637465d242aa31084 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Sun, 29 Jun 2025 15:58:08 +0200 Subject: [PATCH 28/44] More tests --- .../Examples/DataGridVirtualization.razor | 29 ++ .../Components/DataGrid/FluentDataGrid.md | 2 + spelling.dic | 4 + .../Components/DataGrid/Columns/GridSort.cs | 3 + .../DataGrid/FluentDataGrid.razor.cs | 9 +- .../DataGrid/GridItemsProviderRequest.cs | 4 + .../AsyncQueryExecutorSupplier.cs | 1 + .../ColumnsCollectedNotifier.cs | 2 + .../Infrastructure/InternalGridContext.cs | 3 + .../Pagination/FluentPaginator.razor.cs | 2 +- .../EventCallbackSubscribable.cs | 9 +- .../DataGrid/ColumnResizeOptionsTests.razor | 23 + ...olumnOptionsUISettings.verified.razor.html | 25 + ...ColumnResizeUISettings.verified.razor.html | 25 + ...d_ColumnSortUISettings.verified.razor.html | 25 + .../DataGrid/FluentDataGridTests.razor | 466 +++++++++++++++++- .../Core/Components/DataGrid/GridSortTests.cs | 148 +++++- 17 files changed, 754 insertions(+), 26 deletions(-) create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridVirtualization.razor create mode 100644 tests/Core/Components/DataGrid/ColumnResizeOptionsTests.razor create mode 100644 tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnOptionsUISettings.verified.razor.html create mode 100644 tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnResizeUISettings.verified.razor.html create mode 100644 tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnSortUISettings.verified.razor.html diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridVirtualization.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridVirtualization.razor new file mode 100644 index 0000000000..e3ff893185 --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridVirtualization.razor @@ -0,0 +1,29 @@ + +
+ + + +
+ +@code { + private IQueryable? items; + + protected override void OnInitialized() + { + items = GetRandomCustomers(); // Added method call to assign items + } + + private record Customer(int Id, string Name); + + private IQueryable GetRandomCustomers(int size = 500) + { + Customer[] data = new Customer[size]; + + for (int i = 0; i < size; i++) + { + + data[i] = new Customer(i, $"Customer {i} - {Guid.NewGuid().ToString("N").Substring(0, 8)}"); + } + return data.AsQueryable(); + } +} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md index 88c8de7a0e..e631693acd 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md @@ -25,6 +25,8 @@ Pressing enter finishes the filter action by the current input to filter on and The resize options UI is using a customized string for the label ('Width (+/- 10px)' instead of the normal 'Column width'). This is done through the custom localizer which is registered in the Server project's `Program.cs` file. +{{ DataGridVirtualization }} + {{ DataGridTypical }} {{ DataGridMultiSelect }} diff --git a/spelling.dic b/spelling.dic index 1697f4bd8c..5d77530561 100644 --- a/spelling.dic +++ b/spelling.dic @@ -95,3 +95,7 @@ rowcount displaymode Voituron inputfile +eventargs +Subscribable +colspan +Voituron diff --git a/src/Core/Components/DataGrid/Columns/GridSort.cs b/src/Core/Components/DataGrid/Columns/GridSort.cs index f89ce8af13..256255cdda 100644 --- a/src/Core/Components/DataGrid/Columns/GridSort.cs +++ b/src/Core/Components/DataGrid/Columns/GridSort.cs @@ -2,6 +2,7 @@ // MIT License - Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------------------ +using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; namespace Microsoft.FluentUI.AspNetCore.Components; @@ -259,6 +260,8 @@ private List BuildPropertyList(bool ascending) } // Not sure we really want this level of complexity, but it converts expressions like @(c => c.Medals.Gold) to "Medals.Gold" + // Makes it too complex to test, so we exclude from coverage + [ExcludeFromCodeCoverage(Justification = "Too complex to write test for.")] #pragma warning disable MA0015 // Specify the parameter name in ArgumentException private static string ToPropertyName(LambdaExpression expression) { diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index fea828bd21..f13dd71eb7 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -34,7 +34,7 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve private bool _collectingColumns; private ColumnBase? _displayOptionsForColumn; internal ColumnBase? _displayResizeForColumn; - private ColumnBase? _sortByColumn; + internal ColumnBase? _sortByColumn; private bool _sortByAscending; private bool _checkColumnOptionsPosition; private bool _checkColumnResizePosition; @@ -780,7 +780,7 @@ private async Task RefreshDataCoreAsync() } _internalGridContext.ResetRowIndexes(startIndex); - StateHasChanged(); + _ = InvokeAsync(StateHasChanged); } // Gets called both by RefreshDataCoreAsync and directly by the Virtualize child component during scrolling @@ -791,8 +791,7 @@ private async Task RefreshDataCoreAsync() // Debounce the requests. This eliminates a lot of redundant queries at the cost of slight lag after interactions. // TODO: Consider making this configurable, or smarter (e.g., doesn't delay on first call in a batch, then the amount // of delay increases if you rapidly issue repeated requests, such as when scrolling a long way) - await Task.Delay(100); - + await Task.Delay(20); if (request.CancellationToken.IsCancellationRequested) { return default; @@ -829,7 +828,7 @@ private async Task RefreshDataCoreAsync() if (_internalGridContext.TotalItemCount > 0 && Loading is null) { Loading = false; - StateHasChanged(); + _ = InvokeAsync(StateHasChanged); } // We're supplying the row _index along with each row's data because we need it for aria-rowindex, and we have to account for diff --git a/src/Core/Components/DataGrid/GridItemsProviderRequest.cs b/src/Core/Components/DataGrid/GridItemsProviderRequest.cs index e1f6e74db1..f6c28cdd12 100644 --- a/src/Core/Components/DataGrid/GridItemsProviderRequest.cs +++ b/src/Core/Components/DataGrid/GridItemsProviderRequest.cs @@ -2,6 +2,8 @@ // MIT License - Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------------------ +using System.Diagnostics.CodeAnalysis; + namespace Microsoft.FluentUI.AspNetCore.Components; /// @@ -64,6 +66,7 @@ public IQueryable ApplySorting(IQueryable source) => /// Produces a collection of (property name, direction) pairs representing the sorting rules. /// /// A collection of (property name, direction) pairs representing the sorting rules + [ExcludeFromCodeCoverage(Justification = "This is a not reachable in a unit test scenario.")] public IReadOnlyCollection GetSortByProperties() => SortByColumn?.SortBy?.ToPropertyList(SortByAscending) ?? []; @@ -73,6 +76,7 @@ public IReadOnlyCollection GetSortByProperties() => /// The to compare with the current request. /// if the specified request has the same start index, count, sort column, and sort order as /// the current request; otherwise, . + [ExcludeFromCodeCoverage(Justification = "This is a not reachable in a unit test scenario.")] public bool IsSameRequest(GridItemsProviderRequest req) { if (StartIndex != req.StartIndex) diff --git a/src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs b/src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs index ad2844277b..c049b15821 100644 --- a/src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs +++ b/src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs @@ -8,6 +8,7 @@ namespace Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; +[ExcludeFromCodeCoverage(Justification = "This is an *internal* utility class that is too complicated to test. It provides a way to get an async query executor for EF Core queries.")] internal static class AsyncQueryExecutorSupplier { // The primary goal with this is to ensure that: diff --git a/src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs b/src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs index b35f29ecea..cf437e573b 100644 --- a/src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs +++ b/src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs @@ -3,6 +3,7 @@ // ------------------------------------------------------------------------ using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Components; namespace Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; @@ -34,6 +35,7 @@ namespace Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; /// For internal use only. Do not use. ///
/// For internal use only. Do not use. +[ExcludeFromCodeCoverage(Justification = "The return in SetParametersAsync can't be invoked because of the way this component gets rendered by the DataGrid. ")] [EditorBrowsable(EditorBrowsableState.Never)] public sealed class ColumnsCollectedNotifier : Microsoft.AspNetCore.Components.IComponent { diff --git a/src/Core/Components/DataGrid/Infrastructure/InternalGridContext.cs b/src/Core/Components/DataGrid/Infrastructure/InternalGridContext.cs index 24e2806ef7..bfade94198 100644 --- a/src/Core/Components/DataGrid/Infrastructure/InternalGridContext.cs +++ b/src/Core/Components/DataGrid/Infrastructure/InternalGridContext.cs @@ -2,6 +2,7 @@ // MIT License - Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------------------ +using System.Diagnostics.CodeAnalysis; using Microsoft.FluentUI.AspNetCore.Components.Infrastructure; namespace Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; @@ -21,6 +22,8 @@ internal sealed class InternalGridContext public ICollection Items { get; set; } = []; public int TotalItemCount { get; set; } + + [ExcludeFromCodeCoverage(Justification = "This can only be set when a Virtualized grid is scrolled which can't be done by bUnit")] public int TotalViewItemCount { get; set; } public FluentDataGrid Grid { get; } diff --git a/src/Core/Components/Pagination/FluentPaginator.razor.cs b/src/Core/Components/Pagination/FluentPaginator.razor.cs index d68a7cad58..996d5652b3 100644 --- a/src/Core/Components/Pagination/FluentPaginator.razor.cs +++ b/src/Core/Components/Pagination/FluentPaginator.razor.cs @@ -102,7 +102,7 @@ private async Task GoToPageAsync(int pageIndex) await State.SetCurrentPageIndexAsync(pageIndex); if (CurrentPageIndexChanged.HasDelegate) { - await CurrentPageIndexChanged.InvokeAsync(State.CurrentPageIndex); + await InvokeAsync(() => CurrentPageIndexChanged.InvokeAsync(State.CurrentPageIndex)); } } } diff --git a/src/Core/Infrastructure/EventCallbackSubscribable.cs b/src/Core/Infrastructure/EventCallbackSubscribable.cs index a9788445d0..8183eebc40 100644 --- a/src/Core/Infrastructure/EventCallbackSubscribable.cs +++ b/src/Core/Infrastructure/EventCallbackSubscribable.cs @@ -24,7 +24,14 @@ public async Task InvokeCallbacksAsync(T eventArg) { foreach (var callback in _callbacks.Values) { - await callback.InvokeAsync(eventArg); + try + { + await callback.InvokeAsync(eventArg); + } + catch (InvalidOperationException) + { + // Continue invoking the rest of the callbacks even if one fails. + } } } diff --git a/tests/Core/Components/DataGrid/ColumnResizeOptionsTests.razor b/tests/Core/Components/DataGrid/ColumnResizeOptionsTests.razor new file mode 100644 index 0000000000..83ccae7f77 --- /dev/null +++ b/tests/Core/Components/DataGrid/ColumnResizeOptionsTests.razor @@ -0,0 +1,23 @@ +@using Bunit.TestDoubles +@using Xunit +@inherits Bunit.TestContext + +@code { + public ColumnResizeOptionsTests() + { + JSInterop.Mode = JSRuntimeMode.Loose; + + // Register services + Services.AddFluentUIComponents(); + Services.AddScoped(factory => new KeyCodeService()); + } + + [Fact] + public void ColumnResizeOptions_InvalidColumn() + { + // Arrange && Act && Assert + Assert.Throws(() => Render>( + @ + )); + } +} diff --git a/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnOptionsUISettings.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnOptionsUISettings.verified.razor.html new file mode 100644 index 0000000000..2f54afb00b --- /dev/null +++ b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnOptionsUISettings.verified.razor.html @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + +
+
+
+
Name
+
+
+
Denis Voituron
Vincent Baaij
Bill Gates
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnResizeUISettings.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnResizeUISettings.verified.razor.html new file mode 100644 index 0000000000..2f54afb00b --- /dev/null +++ b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnResizeUISettings.verified.razor.html @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + +
+
+
+
Name
+
+
+
Denis Voituron
Vincent Baaij
Bill Gates
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnSortUISettings.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnSortUISettings.verified.razor.html new file mode 100644 index 0000000000..2f54afb00b --- /dev/null +++ b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnSortUISettings.verified.razor.html @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + +
+
+
+
Name
+
+
+
Denis Voituron
Vincent Baaij
Bill Gates
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridTests.razor b/tests/Core/Components/DataGrid/FluentDataGridTests.razor index 815b8b582b..d920889326 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridTests.razor +++ b/tests/Core/Components/DataGrid/FluentDataGridTests.razor @@ -233,33 +233,83 @@ @ - ); + ); }); } [Fact] - public void FluentDataGrid_Virtualize() + public async Task FluentDataGrid_Virtualize() { // Arrange && Act + FluentDataGrid? grid = default!; + PaginationState state = new(); + + state.ItemsPerPage = 20; var items = GetRandomCustomers(); // Added method call to assign items - var grid = Render>( + var cut = Render>( @
- +
- ); + ); // Assert - var rows = grid.FindComponents>(); + await grid.Pagination!.SetCurrentPageIndexAsync(5); + + var rows = cut.FindComponents>(); Assert.NotEmpty(rows); // Asserting that there are rows present + //Assert.Equal(101, rows.Count); //In bUnit the actual height of the grid can't be determined, so we just check that at least one row is rendered. Assert.Single(rows); //In bUnit the actual height of the grid can't be determined, so we just check that at least one row is rendered. } + [Fact] + public async Task FluentDataGrid_ItemsProvider() + { + // Arrange && Act + FluentDataGrid? grid = default!; + GridItemsProvider itemsProvider = default!; + + await Task.Delay(100, Xunit.TestContext.Current.CancellationToken); + itemsProvider = async req => + { + await Task.Delay(100); // Simulate async delay + return GridItemsProviderResult.From( + items: GetRandomCustomers().ToArray(), + totalItemCount: 500); + }; + + + + var cut = Render>( + @
+ + + +
+ ); + + // Assert + var rows = cut.FindComponents>(); + + Assert.NotEmpty(rows); // Asserting that there are rows present + Assert.Equal(2, rows.Count); //In bUnit the actual height of the grid can't be determined, so we just check that at least one row is rendered. + } + + private async Task> RefreshItemsAsync(GridItemsProviderRequest req) + { + await Task.Delay(100); // Simulate async delay + var b = req.IsSameRequest(new GridItemsProviderRequest()); + + return GridItemsProviderResult.From( + items: GetRandomCustomers().ToArray(), + totalItemCount: 500); + } + [Fact] public void FluentDataGrid_AutoItems() { @@ -297,19 +347,32 @@ } [Fact] - public void FluentDataGrid_LoadState_Sort() + public void FluentDataGrid_LoadState_NoSaveState() { // Arrange && Act FluentDataGrid? grid = default!; - //var navMan = Services.GetRequiredService(); + var cut = Render>( + @ + + + ); + grid.LoadStateFromQueryString("?orderby=Name%20asc"); - //navMan.NavigateTo("?gr1_orderby=Name%20asc"); + // Assert + var row = cut.FindComponent>(); + Assert.Equal("Bill Gates", row.Instance.Item?.Name); + } + [Fact] + public void FluentDataGrid_LoadState_Sort() + { + // Arrange && Act + FluentDataGrid? grid = default!; var cut = Render>( @ - ); + ); grid.LoadStateFromQueryString("?gr1_orderby=Name%20asc"); // Assert @@ -598,6 +661,140 @@ } + [Fact] + public async Task FluentDataGrid_ShowColumnResizeAsync_Discrete_ClickButtons() + { + // Arrange && Act + FluentDataGrid? grid = default!; + + var cut = Render>( + @ + + + ); + + var row = cut.FindComponent>(); + + + row.Find(".col-options-button").Click(); + + var resizeOptions = cut.Find("fluent-label").TextContent; + await grid.ShowColumnResizeAsync("Name"); + + var x = cut.FindComponent>(); + + var buttons = x.FindAll("fluent-button"); + buttons[0].Click(); // Click the shrink button + buttons[1].Click(); // Click the grow button + buttons[2].Click(); // Click the reset button + + // Assert + Assert.Equal("Column width", resizeOptions); + + } + + [Fact] + public async Task FluentDataGrid_ShowColumnResizeAsync_Exact() + { + // Arrange && Act + FluentDataGrid? grid = default!; + + var cut = Render>( + @ + + + ); + + var row = cut.FindComponent>(); + + row.Find(".col-options-button").Click(); + + await grid.ShowColumnResizeAsync("Name"); + + var resizeOptions = cut.Find("label").TextContent; + + // Assert + Assert.Equal("Column", resizeOptions.Split(' ')[0]); + + } + + [Fact] + public async Task FluentDataGrid_ShowColumnResizeAsync_Exact_ClickButtons() + { + // Arrange && Act + FluentDataGrid? grid = default!; + + var cut = Render>( + @ + + + ); + + var row = cut.FindComponent>(); + + + row.Find(".col-options-button").Click(); + + await grid.ShowColumnResizeAsync("Name"); + var resizeOptions = cut.Find("label").TextContent; + + var x = cut.FindComponent>(); + + x.Find("fluent-text-input").Change("200"); // Set the width to 200 pixels + + var buttons = x.FindAll("fluent-button"); + buttons[0].Click(); // Click the apply button + buttons[1].Click(); // Click the reset button + + + // Assert + Assert.Equal("Column", resizeOptions.Split(' ')[0]); + + } + + [Fact] + public async Task FluentDataGrid_ShowColumnResizeAsync_Exact_KeyDown() + { + // Arrange && Act + FluentDataGrid? grid = default!; + + var cut = Render>( + @ + + + ); + + var row = cut.FindComponent>(); + + + row.Find(".col-options-button").Click(); + + await grid.ShowColumnResizeAsync("Name"); + var resizeOptions = cut.Find("label").TextContent; + + var x = cut.FindComponent>(); + + x.Find("fluent-text-input").KeyDown(new KeyboardEventArgs {Key = "Enter"}); + + + // Assert + Assert.Equal("Column", resizeOptions.Split(' ')[0]); + + } + + [Fact] public async Task FluentDataGrid_ShowColumnResizeAsync_InvalidColumName() { @@ -711,7 +908,7 @@ [InlineData(DataGridSortDirection.Descending, "Vincent Baaij")] [InlineData(DataGridSortDirection.Auto, "Vincent Baaij")] [InlineData((DataGridSortDirection)3, "Bill Gates")] //Defaults to Ascending - public void FluentDataGrid_SortByColumn_ByColumnIndex(DataGridSortDirection sortDirection, string expectedValue) + public async Task FluentDataGrid_SortByColumn_ByColumnIndex(DataGridSortDirection sortDirection, string expectedValue) { // Arrange && Act FluentDataGrid? grid = default!; @@ -722,7 +919,7 @@
); - grid.SortByColumnAsync(0, sortDirection); + await grid.SortByColumnAsync(0, sortDirection); var row = cut.FindComponent>(); @@ -731,12 +928,32 @@ } + [Fact] + public async Task FluentDataGrid_SortByColumn_ByColumnIndex_OutOfBounds() + { + // Arrange && Act + FluentDataGrid? grid = default!; + + var cut = Render>( + @ + + + ); + + // Assert + await grid.SortByColumnAsync(-1); + Assert.Null(grid._sortByColumn); + + await grid.SortByColumnAsync(3); + Assert.Null(grid._sortByColumn); + } + [Theory] [InlineData(DataGridSortDirection.Ascending, "Bill Gates")] [InlineData(DataGridSortDirection.Descending, "Vincent Baaij")] [InlineData(DataGridSortDirection.Auto, "Vincent Baaij")] [InlineData((DataGridSortDirection)3, "Bill Gates")] //Defaults to Ascending - public void FluentDataGrid_SortByColumn_ByColumnName(DataGridSortDirection sortDirection, string expectedValue) + public async Task FluentDataGrid_SortByColumn_ByColumnName(DataGridSortDirection sortDirection, string expectedValue) { // Arrange && Act FluentDataGrid? grid = default!; @@ -747,7 +964,7 @@ ); - grid.SortByColumnAsync("Name", sortDirection); + await grid.SortByColumnAsync("Name", sortDirection); var row = cut.FindComponent>(); @@ -756,6 +973,24 @@ } + [Fact] + public async Task FluentDataGrid_SortByColumn_ByColumnName_InvalidName() + { + // Arrange && Act + FluentDataGrid? grid = default!; + + var cut = Render>( + @ + + + ); + + // Assert + await grid.SortByColumnAsync("InvalidColumnName"); + Assert.Null(grid._sortByColumn); + } + + [Fact] public void FluentDataGrid_RemoveSortByColumn() { @@ -808,6 +1043,121 @@ Assert.Equal(3, row.Instance.InternalGridContext.Items.First().Id); } + + + [Fact] + public async Task FluentDataGrid_UpdateItemsPerPageAsync() + { + // Arrange && Act + FluentDataGrid? grid = default!; + PaginationState state = new(); + + state.ItemsPerPage = 3; + + var cut = Render>( + @ + + + + ); + + // Assert + var rows = cut.FindAll("tr"); + Assert.Equal(4, rows.Count()); + + await grid.UpdateItemsPerPageAsync(3); + rows = cut.FindAll("tr"); + Assert.Equal(3, rows.Count()); + + await grid.UpdateItemsPerPageAsync(1); + rows = cut.FindAll("tr"); + Assert.Equal(2, rows.Count()); + } + + [Fact] + public async Task FluentDataGrid_UpdateItemsPerPageAsync_NoPagination() + { + // Arrange && Act + FluentDataGrid? grid = default!; + + var cut = Render>( + @ + + + + ); + + // Assert + var rows = cut.FindAll("tr"); + Assert.Equal(4, rows.Count()); + + await grid.UpdateItemsPerPageAsync(3); + rows = cut.FindAll("tr"); + Assert.Equal(4, rows.Count()); + } + + [Fact] + public async Task FluentDataGrid_KeyDown() + { + // Arrange && Act + FluentKeyCodeEventArgs pressed = new(); + FluentKeyCodeEventArgs myMinusKey = new() { Key = KeyCode.Minus , KeyCode = 45, Value = "-" }; + FluentKeyCodeEventArgs myPlusKey = new() { Key = KeyCode.NumpadAdd, KeyCode = 43, Value = "+" }; + FluentKeyCodeEventArgs myResetKey = new() { Key = KeyCode.KeyR, KeyCode = 114, Value = "r", ShiftKey = true }; + + + // Register Service + var keycodeService = new KeyCodeService(); + Services.AddScoped(factory => keycodeService); + + // Create a listener + var listener = new MyKeyCodeListener(e => pressed = e); + + + + var cut = Render>( + @
+ + + + + +
+ ); + + // Register the listener + keycodeService.RegisterListener(listener); + + await keycodeService.Listeners.First().OnKeyDownAsync(myMinusKey); + await keycodeService.Listeners.First().OnKeyDownAsync(myPlusKey); + await keycodeService.Listeners.First().OnKeyDownAsync(myResetKey); + + // Assert + // Nothing to do here. We want these called for making sure lines are covered + Assert.True(true); + } + + + [Fact] + public async Task FluentDataGrid_SetColumnWidthExact() + { + // Arrange && Act + FluentDataGrid? grid = default!; + + var cut = Render>( + @ + + + + ); + + await grid.SetColumnWidthExactAsync(0, 100); + + // Assert + // Nothing to do here. We want these called for making sure lines are covered + Assert.True(true); + } + // // Add a test to call DisposeAsync on the component // [Fact] // public async Task FluentDataGrid_DisposeAsync_Calls_Dispose() @@ -847,4 +1197,92 @@ } return data.AsQueryable(); } + + private class MyKeyCodeListener : IKeyCodeListener + { + private Action _actionKeyDown; + private Action? _actionKeyUp; + + public MyKeyCodeListener(Action actionKeyDown, Action? actionKeyUp = null) + { + _actionKeyDown = actionKeyDown; + _actionKeyUp = actionKeyUp; + } + + public Task OnKeyDownAsync(FluentKeyCodeEventArgs args) + { + _actionKeyDown.Invoke(args); + return Task.CompletedTask; + } + + public Task OnKeyUpAsync(FluentKeyCodeEventArgs args) + { + _actionKeyUp?.Invoke(args); + return Task.CompletedTask; + } + } + + [Fact] + public void FluentDataGrid_ColumnResizeUISettings() + { + // Arrange && Act + var uISettings = ColumnResizeUISettings.Default with + { + IconPositionStart = false, + }; + + var cut = Render>( + @ + + + +

empty content

+
); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentDataGrid_ColumnSortUISettings() + { + // Arrange && Act + var uISettings = ColumnSortUISettings.Default with + { + IconPositionStart = false, + }; + + var cut = Render>( + @ + + + +

empty content

+
); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentDataGrid_ColumnOptionsUISettings() + { + // Arrange && Act + var uISettings = ColumnOptionsUISettings.Default with + { + IconPositionStart = false, + }; + + var cut = Render>( + @ + + + +

empty content

+
); + + // Assert + cut.Verify(); + } + } diff --git a/tests/Core/Components/DataGrid/GridSortTests.cs b/tests/Core/Components/DataGrid/GridSortTests.cs index 2730460f55..dccaf9408d 100644 --- a/tests/Core/Components/DataGrid/GridSortTests.cs +++ b/tests/Core/Components/DataGrid/GridSortTests.cs @@ -1,9 +1,10 @@ // ------------------------------------------------------------------------ // MIT License - Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------------------ + using Xunit; -namespace Microsoft.FluentUI.AspNetCore.Components.Tests.DataGrid; +namespace Microsoft.FluentUI.AspNetCore.Components.Tests.Components.DataGrid; public class GridSortTests : Bunit.TestContext { @@ -27,6 +28,29 @@ public void GridSortTests_SortBy_Number(bool ascending, IList expected) Assert.True(ordered.Select(x => x.Number).SequenceEqual(expected)); } + [Theory] + [InlineData(true, new int[] { 1, 2, 3, 4 })] + [InlineData(false, new int[] { 4, 3, 2, 1 })] + public void GridSortTests_SortBy_Number_Descending(bool ascending, IList expected) + { + var sort = GridSort.ByDescending(x => x.Number); + var ordered = sort.Apply(_gridData.AsQueryable(), ascending); + + Assert.False(ordered.Select(x => x.Number).SequenceEqual(expected)); + } + + [Theory] + [InlineData(true, new int[] { 1, 2, 3, 4 })] + [InlineData(false, new int[] { 4, 3, 2, 1 })] + public void GridSortTests_SortBy_Number_Descending_WithComparer(bool ascending, IList expected) + { + var groupComparer = Comparer.Create((x, y) => y.CompareTo(x)); + var sort = GridSort.ByDescending(x => x.Number, groupComparer); + var ordered = sort.Apply(_gridData.AsQueryable(), ascending); + + Assert.True(ordered.Select(x => x.Number).SequenceEqual(expected)); + } + [Theory] [InlineData(true, new int[] { 1, 3, 2, 4 })] [InlineData(false, new int[] { 4, 2, 3, 1 })] @@ -41,6 +65,36 @@ public void GridSortTests_SortBy_GroupThenNumberAscending(bool ascending, IList< Assert.True(ordered.Select(x => x.Number).SequenceEqual(expected)); } + [Theory] + [InlineData(true, new int[] { 1, 3, 2, 4 })] + [InlineData(false, new int[] { 4, 2, 3, 1 })] + public void GridSortTests_SortBy_GroupThenNumberAscending_WithComparer(bool ascending, IList expected) + { + var groupComparer = StringComparer.OrdinalIgnoreCase; + var sort = GridSort + .ByAscending(x => x.Group, groupComparer) + .ThenAscending(x => x.Number); + + var ordered = sort.Apply(_gridData.AsQueryable(), ascending); + + Assert.True(ordered.Select(x => x.Number).SequenceEqual(expected)); + } + + [Theory] + [InlineData(true, new int[] { 1, 3, 2, 4 })] + [InlineData(false, new int[] { 4, 2, 3, 1 })] + public void GridSortTests_SortBy_GroupThenNumberAscending_WithComparer2(bool ascending, IList expected) + { + var groupComparer = Comparer.Create((x, y) => y.CompareTo(x)); + var sort = GridSort + .ByAscending(x => x.Group) + .ThenAscending(x => x.Number, groupComparer); + + var ordered = sort.Apply(_gridData.AsQueryable(), ascending); + + Assert.False(ordered.Select(x => x.Number).SequenceEqual(expected)); + } + [Theory] [InlineData(true, new int[] { 3, 1, 4, 2 })] [InlineData(false, new int[] { 2, 4, 1, 3 })] @@ -55,6 +109,23 @@ public void GridSortTests_SortBy_GroupThenNumberDescending(bool ascending, IList Assert.True(ordered.Select(x => x.Number).SequenceEqual(expected)); } + [Theory] + [InlineData(true, new int[] { 3, 1, 4, 2 })] + [InlineData(false, new int[] { 2, 4, 1, 3 })] + public void GridSortTests_SortBy_GroupThenNumberDescending_WithComparer(bool ascending, IList expected) + { + // Create an int comparer + // that compares numbers in descending order + var groupComparer = Comparer.Create((x, y) => y.CompareTo(x)); + var sort = GridSort + .ByAscending(x => x.Group) + .ThenDescending(x => x.Number, groupComparer); + + var ordered = sort.Apply(_gridData.AsQueryable(), ascending); + + Assert.False(ordered.Select(x => x.Number).SequenceEqual(expected)); + } + [Theory] [InlineData(true, new int[] { 1, 3, 2, 4 })] [InlineData(false, new int[] { 2, 4, 1, 3 })] @@ -69,6 +140,21 @@ public void GridSortTests_SortBy_GroupThenNumberAlwaysAscending(bool ascending, Assert.True(ordered.Select(x => x.Number).SequenceEqual(expected)); } + [Theory] + [InlineData(true, new int[] { 1, 3, 2, 4 })] + [InlineData(false, new int[] { 2, 4, 1, 3 })] + public void GridSortTests_SortBy_GroupThenNumberAlwaysAscending_WithComparer(bool ascending, IList expected) + { + var groupComparer = Comparer.Create((x, y) => y.CompareTo(x)); + var sort = GridSort + .ByAscending(x => x.Group) + .ThenAlwaysAscending(x => x.Number, groupComparer); + + var ordered = sort.Apply(_gridData.AsQueryable(), ascending); + + Assert.False(ordered.Select(x => x.Number).SequenceEqual(expected)); + } + [Theory] [InlineData(true, new int[] { 3, 1, 4, 2 })] [InlineData(false, new int[] { 4, 2, 3, 1 })] @@ -83,11 +169,63 @@ public void GridSortTests_SortBy_GroupThenNumberAlwaysDescending(bool ascending, Assert.True(ordered.Select(x => x.Number).SequenceEqual(expected)); } -#pragma warning restore CA1861 // Avoid constant arrays as arguments + [Theory] + [InlineData(true, new int[] { 3, 1, 4, 2 })] + [InlineData(false, new int[] { 4, 2, 3, 1 })] + public void GridSortTests_SortBy_GroupThenNumberAlwaysDescending_WithComparer(bool ascending, IList expected) + { + var groupComparer = Comparer.Create((x, y) => y.CompareTo(x)); + var sort = GridSort + .ByAscending(x => x.Group) + .ThenAlwaysDescending(x => x.Number, groupComparer); - public class GridRow(int number, string group) + var ordered = sort.Apply(_gridData.AsQueryable(), ascending); + + Assert.False(ordered.Select(x => x.Number).SequenceEqual(expected)); + } + + [Fact] + public void ToPropertyList_ReturnsCorrectPropertyAndDirection() + { + // Arrange + var sort = GridSort + .ByAscending(x => x.Group) + .ThenDescending(x => x.Number); + + // Act + var resultAsc = sort.ToPropertyList(true).ToList(); + var resultDesc = sort.ToPropertyList(false).ToList(); + + // Assert + Assert.Equal(2, resultAsc.Count); + Assert.Equal("Group", resultAsc[0].PropertyName); + Assert.Equal("Number", resultAsc[1].PropertyName); + + Assert.Equal(2, resultDesc.Count); + Assert.Equal("Group", resultDesc[0].PropertyName); + Assert.Equal("Number", resultDesc[1].PropertyName); + } + + public class NestedRow + { + public InnerRow Inner { get; set; } = new(); + } + + public class InnerRow { - public int Number { get; } = number; - public string Group { get; } = group; + public int Value { get; set; } } } + +public class GridRow(int number, string group) +{ + public int Number { get; } = number; + public string Group { get; } = group; +} + +public class TripleRow(int number, string group, string name) +{ + public int Number { get; } = number; + public string Group { get; } = group; + public string Name { get; } = name; +} From 4aae12f6455e61fa445dd3e3f3052f48f5033d0c Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Sun, 29 Jun 2025 16:08:42 +0200 Subject: [PATCH 29/44] - More tests - Fix ToDo items - Misc code changes --- spelling.dic | 2 + .../DataGrid/Columns/ColumnBase.razor | 150 +-- .../DataGrid/Columns/ColumnBase.razor.cs | 20 +- .../DataGrid/Columns/ColumnKeyGridSort.cs | 15 +- .../Components/DataGrid/Columns/GridSort.cs | 31 +- .../DataGrid/Columns/SelectColumn.cs | 8 +- .../Components/DataGrid/FluentDataGrid.razor | 1 + .../Components/Pagination/PaginationState.cs | 3 - src/Core/Enums/DataGridCellType.cs | 5 - src/Core/Enums/DataGridDisplayMode.cs | 3 - src/Core/Enums/DataGridGeneratedHeaderType.cs | 3 - src/Core/Enums/DataGridResizeType.cs | 3 - src/Core/Enums/DataGridRowSize.cs | 3 - src/Core/Enums/DataGridRowType.cs | 3 - src/Core/Enums/DataGridSelectMode.cs | 3 - src/Core/Enums/DataGridSortDirection.cs | 3 - src/Core/Infrastructure/DefaultValues.cs | 1 + .../DefaultValuesComponentBuilder.cs | 1 + tests/Core/Components.Tests.csproj | 15 - .../DataGrid/ColumnKeyGridSortTests.cs | 39 + .../DataGrid/FluentDataGridCellTests.razor | 28 +- .../DataGrid/FluentDataGridRowTests.razor | 2 +- ...ColumnIndex_Descending.verified.razor.html | 44 - ...ColumnTitle_Descending.verified.razor.html | 44 - .../DataGrid/FluentDataGridSortByTests.razor | 57 +- ...rid_ColumnKeyGridSort.verified.razor.html} | 42 +- ...idSort_NoSortFunction.verified.razor.html} | 42 +- .../DataGrid/FluentDataGridTests.razor | 857 ++++++++++++++++-- ..._Customized_Rendering.verified.razor.html} | 0 ...MultiSelect_Rendering.verified.razor.html} | 0 ...SingleSelect_Rendering.verified.razor.html | 44 + ...tickySelect_Rendering.verified.razor.html} | 0 ...t_Customized_Rendering.verified.razor.html | 32 + ..._MultiSelect_Rendering.verified.razor.html | 50 + ...SingleSelect_Rendering.verified.razor.html | 44 + ...tickySelect_Rendering.verified.razor.html} | 0 ...ectTests.razor => SelectColumnTests.razor} | 514 +++++++++-- .../DisplayAttributeExtensionsTests.cs | 95 ++ 38 files changed, 1759 insertions(+), 448 deletions(-) create mode 100644 tests/Core/Components/DataGrid/ColumnKeyGridSortTests.cs delete mode 100644 tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Descending.verified.razor.html delete mode 100644 tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Descending.verified.razor.html rename tests/Core/Components/DataGrid/{FluentDataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Ascending.verified.razor.html => FluentDataGridTests.FluentDataGrid_ColumnKeyGridSort.verified.razor.html} (63%) rename tests/Core/Components/DataGrid/{FluentDataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Ascending.verified.razor.html => FluentDataGridTests.FluentDataGrid_ColumnKeyGridSort_NoSortFunction.verified.razor.html} (63%) rename tests/Core/Components/DataGrid/{FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Customized_Rendering.verified.razor.html => SelectColumnTests.FluentDataGrid_ColumSelect_MultiSelect_Customized_Rendering.verified.razor.html} (100%) rename tests/Core/Components/DataGrid/{FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Rendering.verified.razor.html => SelectColumnTests.FluentDataGrid_ColumSelect_MultiSelect_Rendering.verified.razor.html} (100%) create mode 100644 tests/Core/Components/DataGrid/SelectColumnTests.FluentDataGrid_ColumSelect_SingleSelect_Rendering.verified.razor.html rename tests/Core/Components/DataGrid/{FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_SingleSelect_Rendering.verified.razor.html => SelectColumnTests.FluentDataGrid_ColumSelect_SingleStickySelect_Rendering.verified.razor.html} (100%) create mode 100644 tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_MultiSelect_Customized_Rendering.verified.razor.html create mode 100644 tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_MultiSelect_Rendering.verified.razor.html create mode 100644 tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_SingleSelect_Rendering.verified.razor.html rename tests/Core/Components/DataGrid/{FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_SingleStickySelect_Rendering.verified.razor.html => SelectColumnTests.SelectColumnTests_SingleStickySelect_Rendering.verified.razor.html} (100%) rename tests/Core/Components/DataGrid/{FluentDataGridColumSelectTests.razor => SelectColumnTests.razor} (55%) create mode 100644 tests/Core/Extensions/DisplayAttributeExtensionsTests.cs diff --git a/spelling.dic b/spelling.dic index 5d77530561..46bd281ab0 100644 --- a/spelling.dic +++ b/spelling.dic @@ -99,3 +99,5 @@ eventargs Subscribable colspan Voituron +firstname +lastname diff --git a/src/Core/Components/DataGrid/Columns/ColumnBase.razor b/src/Core/Components/DataGrid/Columns/ColumnBase.razor index 42933b280f..6efa477baa 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnBase.razor +++ b/src/Core/Components/DataGrid/Columns/ColumnBase.razor @@ -29,70 +29,68 @@ { string? tooltip = Tooltip ? (HeaderTooltip ?? Title) : null; - - - @if (AnyColumnActionEnabled) - { - -
- @HeaderTitleContent -
+ @if (AnyColumnActionEnabled) + { + +
+ @HeaderTitleContent +
- @if (Grid.SortByAscending.HasValue && IsActiveSortColumn) + @if (Grid.SortByAscending.HasValue && IsActiveSortColumn) + { + if (Grid.SortByAscending == true) { - if (Grid.SortByAscending == true) - { - - } - else - { - - } + } - @if (ColumnOptions is not null && Filtered.GetValueOrDefault()) + else { - + } -
- } - else - { -
-
- @HeaderTitleContent -
+ } + @if (ColumnOptions is not null && Filtered.GetValueOrDefault()) + { + + } + + } + else + { +
+
+ @HeaderTitleContent
- } - - - @if (Sortable.HasValue ? Sortable.Value : IsSortableByDefault()) - { - - @GetSortOptionText() +
+ } + + + @if (Sortable.HasValue ? Sortable.Value : IsSortableByDefault()) + { + + @GetSortOptionText() + + + } + @if (Grid.ResizeType is not null && Grid.ResizableColumns) + { + + @Localizer[Localization.LanguageResource.DataGrid_ResizeMenu] + + } + @if (ColumnOptions is not null) + { + + @Localizer[Localization.LanguageResource.DataGrid_OptionsMenu] + + } + + - - } - @if (Grid.ResizeType is not null && Grid.ResizableColumns) - { - - @Localizer[Localization.LanguageResource.DataGrid_ResizeMenu] - - } - @if (ColumnOptions is not null) - { - - @Localizer[Localization.LanguageResource.DataGrid_OptionsMenu] - - } - - - } else { @@ -127,29 +125,29 @@ @if (Sortable.HasValue ? Sortable.Value : IsSortableByDefault()) { - - -
- @HeaderTitleContent -
- @if (Grid.SortByAscending.HasValue && IsActiveSortColumn) + +
+ @HeaderTitleContent +
+ + @if (Grid.SortByAscending.HasValue && IsActiveSortColumn) + { + if (Grid.SortByAscending == true) { - if (Grid.SortByAscending == true) - { - - } - else - { - - } + } - @if (ColumnOptions is not null && Filtered.GetValueOrDefault()) + else { - + } -
-
+ } + @if (ColumnOptions is not null && Filtered.GetValueOrDefault()) + { + + } + + } else { @@ -185,6 +183,8 @@ } } + [ExcludeFromCodeCoverage(Justification = "This method can't be tested because of Virtualization (which does not render completely under bUnit).")] + internal void RenderPlaceholderContent(RenderTreeBuilder __builder, PlaceholderContext placeholderContext) { // Blank if no placeholder template was supplied, as it's enough to style with CSS by default diff --git a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs index 9b1f3fa0d5..b0b4abba2a 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs @@ -2,6 +2,7 @@ // MIT License - Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------------------ +using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.Web; @@ -247,6 +248,7 @@ protected internal virtual Task OnCellClickAsync(FluentDataGridCell c /// /// /// + [ExcludeFromCodeCoverage(Justification = "This method is virtual. It is not called directly on this type.")] protected internal virtual Task OnCellKeyDownAsync(FluentDataGridCell cell, KeyboardEventArgs args) { return Task.CompletedTask; @@ -257,12 +259,14 @@ protected internal virtual Task OnCellKeyDownAsync(FluentDataGridCell /// /// The current . /// The data for the row being rendered. + protected internal abstract void CellContent(RenderTreeBuilder builder, TGridItem item); /// /// Overridden by derived components to provide the raw content for the column's cells. /// /// The data for the row being rendered. + [ExcludeFromCodeCoverage(Justification = "This method is virtual. It is not called directly on this type.")] protected internal virtual string? RawCellContent(TGridItem item) => null; /// @@ -273,23 +277,9 @@ protected internal virtual Task OnCellKeyDownAsync(FluentDataGridCell /// Derived components may override this to implement alternative default sortabillity rules. /// /// True if the column should be sortable by default, otherwise false. + [ExcludeFromCodeCoverage(Justification = "This method is virtual. It is not called directly on this type.")] protected virtual bool IsSortableByDefault() => false; - /// - /// Handles the key down event and performs actions based on the key combination pressed. - /// - /// If the is pressed and the key is , this method triggers the removal of sorting for the associated grid column - /// asynchronously. - /// The event arguments containing details about the key press, including the key code and modifier keys. - protected async Task HandleKeyDownAsync(FluentKeyCodeEventArgs e) - { - if (e.ShiftKey && e.Key == KeyCode.KeyR) - { - await Grid.RemoveSortByColumnAsync(this); - } - } - private async Task HandleColumnHeaderClickedAsync() { var hasSorting = Sortable is true || IsDefaultSortColumn; diff --git a/src/Core/Components/DataGrid/Columns/ColumnKeyGridSort.cs b/src/Core/Components/DataGrid/Columns/ColumnKeyGridSort.cs index 64a25a1af3..8143f88765 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnKeyGridSort.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnKeyGridSort.cs @@ -1,6 +1,7 @@ // ------------------------------------------------------------------------ // MIT License - Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------------------ + namespace Microsoft.FluentUI.AspNetCore.Components; /// @@ -10,11 +11,11 @@ namespace Microsoft.FluentUI.AspNetCore.Components; public sealed class ColumnKeyGridSort : IGridSort { private readonly string _columnKey; - private readonly Func, bool, IOrderedQueryable>? _sortFunction; + private readonly Func, bool, IOrderedQueryable> _sortFunction; internal ColumnKeyGridSort( string columnKey, - Func, bool, IOrderedQueryable>? sortFunction = null) + Func, bool, IOrderedQueryable> sortFunction) { _columnKey = columnKey; _sortFunction = sortFunction; @@ -28,13 +29,13 @@ internal ColumnKeyGridSort( /// /// The ordered collection public IOrderedQueryable Apply(IQueryable queryable, bool ascending) { - if (_sortFunction != null) - { - return _sortFunction(queryable, ascending); - } + //if (_sortFunction != null) + //{ + return _sortFunction(queryable, ascending); + //} // If no sort is provided, apply a sort that has no affect in order to be able to return an IOrderedQueryable - return queryable.OrderBy(x => 0); + //return queryable.OrderBy(x => 0); } /// diff --git a/src/Core/Components/DataGrid/Columns/GridSort.cs b/src/Core/Components/DataGrid/Columns/GridSort.cs index 256255cdda..2ea81362c9 100644 --- a/src/Core/Components/DataGrid/Columns/GridSort.cs +++ b/src/Core/Components/DataGrid/Columns/GridSort.cs @@ -261,7 +261,7 @@ private List BuildPropertyList(bool ascending) // Not sure we really want this level of complexity, but it converts expressions like @(c => c.Medals.Gold) to "Medals.Gold" // Makes it too complex to test, so we exclude from coverage - [ExcludeFromCodeCoverage(Justification = "Too complex to write test for.")] + [ExcludeFromCodeCoverage(Justification = "Find a way to test this at a later date.")] #pragma warning disable MA0015 // Specify the parameter name in ArgumentException private static string ToPropertyName(LambdaExpression expression) { @@ -297,22 +297,25 @@ private static string ToPropertyName(LambdaExpression expression) } } - // Now construct the string - return string.Create(length, body, (chars, body) => + return string.Create(length, body, action); + } + + [ExcludeFromCodeCoverage(Justification = "Find a way to test this at a later date.")] + private static void action(Span chars, MemberExpression body) + { + + var nextPos = chars.Length; + while (body is not null) { - var nextPos = chars.Length; - while (body is not null) + nextPos -= body.Member.Name.Length; + body.Member.Name.CopyTo(chars[nextPos..]); + if (nextPos > 0) { - nextPos -= body.Member.Name.Length; - body.Member.Name.CopyTo(chars[nextPos..]); - if (nextPos > 0) - { - chars[--nextPos] = '.'; - } - - body = (body.Expression as MemberExpression)!; + chars[--nextPos] = '.'; } - }); + + body = (body.Expression as MemberExpression)!; + } } } #pragma warning restore MA0015 // Specify the parameter name in ArgumentException diff --git a/src/Core/Components/DataGrid/Columns/SelectColumn.cs b/src/Core/Components/DataGrid/Columns/SelectColumn.cs index 85b515bd0b..7f96de7e6c 100644 --- a/src/Core/Components/DataGrid/Columns/SelectColumn.cs +++ b/src/Core/Components/DataGrid/Columns/SelectColumn.cs @@ -200,7 +200,7 @@ public DataGridSelectMode SelectMode /// [Parameter] - public override IGridSort? SortBy { get; set; } + public override IGridSort? SortBy { get; set; } = null; /// /// Allows to clear the selection. @@ -516,8 +516,10 @@ protected internal override void CellContent(RenderTreeBuilder builder, TGridIte return TooltipText?.Invoke(item); } - /// - protected override bool IsSortableByDefault() => SortBy is not null; + /// + /// A cannot be sorted on a . + /// + protected override bool IsSortableByDefault() => false; /// internal async Task OnClickAllAsync(MouseEventArgs e) diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor b/src/Core/Components/DataGrid/FluentDataGrid.razor index f8631b38ce..70739aaa3a 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor @@ -115,6 +115,7 @@ } + [ExcludeFromCodeCoverage(Justification = "This method is used virtualized mode and cannot be tested with bUnit currently.")] private void RenderPlaceholderRow(RenderTreeBuilder __builder, PlaceholderContext placeholderContext) { string? _rowsDataSize = $"height: {ItemSize}px"; diff --git a/src/Core/Components/Pagination/PaginationState.cs b/src/Core/Components/Pagination/PaginationState.cs index 4ad3b195f1..29eb88d873 100644 --- a/src/Core/Components/Pagination/PaginationState.cs +++ b/src/Core/Components/Pagination/PaginationState.cs @@ -6,8 +6,6 @@ namespace Microsoft.FluentUI.AspNetCore.Components; -// ToDo: remove pragma after next PR -#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved /// /// Holds state to represent pagination in a . /// @@ -105,4 +103,3 @@ public async Task SetTotalItemCountAsync(int totalItemCount, bool force = false) await TotalItemCountChangedSubscribable.InvokeCallbacksAsync(this); } } -#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved diff --git a/src/Core/Enums/DataGridCellType.cs b/src/Core/Enums/DataGridCellType.cs index 725c0a49c7..72c20e6884 100644 --- a/src/Core/Enums/DataGridCellType.cs +++ b/src/Core/Enums/DataGridCellType.cs @@ -6,9 +6,6 @@ namespace Microsoft.FluentUI.AspNetCore.Components; -// ToDo: remove pragma after next PR -#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved - /// /// The type of in a . /// @@ -31,5 +28,3 @@ public enum DataGridCellType [Description("rowheader")] RowHeader, } -#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved - diff --git a/src/Core/Enums/DataGridDisplayMode.cs b/src/Core/Enums/DataGridDisplayMode.cs index 911c0d2015..bd4c36dc1e 100644 --- a/src/Core/Enums/DataGridDisplayMode.cs +++ b/src/Core/Enums/DataGridDisplayMode.cs @@ -4,8 +4,6 @@ namespace Microsoft.FluentUI.AspNetCore.Components; -// ToDo: remove pragma after next PR -#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved /// /// The type of rendering to use for the /// @@ -23,5 +21,4 @@ public enum DataGridDisplayMode /// Table, } -#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved diff --git a/src/Core/Enums/DataGridGeneratedHeaderType.cs b/src/Core/Enums/DataGridGeneratedHeaderType.cs index 436cb773ca..f061ac142f 100644 --- a/src/Core/Enums/DataGridGeneratedHeaderType.cs +++ b/src/Core/Enums/DataGridGeneratedHeaderType.cs @@ -4,8 +4,6 @@ namespace Microsoft.FluentUI.AspNetCore.Components; -// ToDo: remove pragma after next PR -#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved /// /// The option for generating a header for the . /// @@ -26,4 +24,3 @@ public enum DataGridGeneratedHeaderType /// Sticky, } -#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved diff --git a/src/Core/Enums/DataGridResizeType.cs b/src/Core/Enums/DataGridResizeType.cs index 45a1863dfc..55e806bb31 100644 --- a/src/Core/Enums/DataGridResizeType.cs +++ b/src/Core/Enums/DataGridResizeType.cs @@ -4,8 +4,6 @@ namespace Microsoft.FluentUI.AspNetCore.Components; -// ToDo: remove pragma after next PR -#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved /// /// The type of in a . /// @@ -21,4 +19,3 @@ public enum DataGridResizeType /// Exact, } -#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved diff --git a/src/Core/Enums/DataGridRowSize.cs b/src/Core/Enums/DataGridRowSize.cs index 25b82f460e..6c9296a4ec 100644 --- a/src/Core/Enums/DataGridRowSize.cs +++ b/src/Core/Enums/DataGridRowSize.cs @@ -4,8 +4,6 @@ namespace Microsoft.FluentUI.AspNetCore.Components; -// ToDo: remove pragma after next PR -#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved /// /// The height of each in a . /// Values are in pixels. @@ -32,4 +30,3 @@ public enum DataGridRowSize /// Large = 58, } -#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved diff --git a/src/Core/Enums/DataGridRowType.cs b/src/Core/Enums/DataGridRowType.cs index 4f66e11841..84d838e678 100644 --- a/src/Core/Enums/DataGridRowType.cs +++ b/src/Core/Enums/DataGridRowType.cs @@ -6,8 +6,6 @@ namespace Microsoft.FluentUI.AspNetCore.Components; -// ToDo: remove pragma after next PR -#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved /// /// The type of in a . /// @@ -29,4 +27,3 @@ public enum DataGridRowType [Description("sticky-header")] StickyHeader, } -#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved diff --git a/src/Core/Enums/DataGridSelectMode.cs b/src/Core/Enums/DataGridSelectMode.cs index b0bb360960..e9a9c1204b 100644 --- a/src/Core/Enums/DataGridSelectMode.cs +++ b/src/Core/Enums/DataGridSelectMode.cs @@ -4,8 +4,6 @@ namespace Microsoft.FluentUI.AspNetCore.Components; -// ToDo: remove pragma after next PR -#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved /// /// How rows can be selected in a when using a . /// @@ -26,4 +24,3 @@ public enum DataGridSelectMode /// Multiple, } -#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved diff --git a/src/Core/Enums/DataGridSortDirection.cs b/src/Core/Enums/DataGridSortDirection.cs index 173546140a..7f3d9fd615 100644 --- a/src/Core/Enums/DataGridSortDirection.cs +++ b/src/Core/Enums/DataGridSortDirection.cs @@ -4,8 +4,6 @@ namespace Microsoft.FluentUI.AspNetCore.Components; -// ToDo: remove pragma after next PR -#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved /// /// Describes the direction in which a column is sorted. /// @@ -28,4 +26,3 @@ public enum DataGridSortDirection /// Descending, } -#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved diff --git a/src/Core/Infrastructure/DefaultValues.cs b/src/Core/Infrastructure/DefaultValues.cs index 911e4ee185..3baf32edde 100644 --- a/src/Core/Infrastructure/DefaultValues.cs +++ b/src/Core/Infrastructure/DefaultValues.cs @@ -9,6 +9,7 @@ namespace Microsoft.FluentUI.AspNetCore.Components; /// +[ExcludeFromCodeCoverage(Justification = "Test will be added later")] public class DefaultValues { // List of components and their Property/Default values. diff --git a/src/Core/Infrastructure/DefaultValuesComponentBuilder.cs b/src/Core/Infrastructure/DefaultValuesComponentBuilder.cs index 855e095cb9..7cd8ab2c9c 100644 --- a/src/Core/Infrastructure/DefaultValuesComponentBuilder.cs +++ b/src/Core/Infrastructure/DefaultValuesComponentBuilder.cs @@ -12,6 +12,7 @@ namespace Microsoft.FluentUI.AspNetCore.Components; /// /// Provides functionality to configure default values for component parameters of type . /// +[ExcludeFromCodeCoverage(Justification = "Test will be added later")] public class DefaultValuesComponentBuilder<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TComponent> { [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] diff --git a/tests/Core/Components.Tests.csproj b/tests/Core/Components.Tests.csproj index 57b5f62edb..0ffa3aaa3e 100644 --- a/tests/Core/Components.Tests.csproj +++ b/tests/Core/Components.Tests.csproj @@ -73,19 +73,4 @@ FluentLocalizer.resx - - - - FluentDataGridSortByTests.razor - - - FluentDataGridSortByTests.razor - - - FluentDataGridSortByTests.razor - - - FluentDataGridSortByTests.razor - - diff --git a/tests/Core/Components/DataGrid/ColumnKeyGridSortTests.cs b/tests/Core/Components/DataGrid/ColumnKeyGridSortTests.cs new file mode 100644 index 0000000000..abea2e4706 --- /dev/null +++ b/tests/Core/Components/DataGrid/ColumnKeyGridSortTests.cs @@ -0,0 +1,39 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +using Xunit; + +namespace Microsoft.FluentUI.AspNetCore.Components.Tests.Components.DataGrid; + +public class ColumnKeyGridSortTests : Bunit.TestContext +{ + [Fact] + public void ToPropertyList_ReturnsCorrectPropertyAndDirection() + { + // Arrange + var sort = new ColumnKeyGridSort( + "Group", (queryable, sortAscending) => + { + if (sortAscending) + { + return queryable.OrderBy(x => x.Group); + } + else + { + return queryable.OrderByDescending(x => x.Group); + } + }); + + // Act + var resultAsc = sort.ToPropertyList(true).ToList(); + var resultDesc = sort.ToPropertyList(false).ToList(); + + // Assert + Assert.Single(resultAsc); + Assert.Equal("Group", resultAsc[0].PropertyName); + + Assert.Single(resultDesc); + Assert.Equal("Group", resultDesc[0].PropertyName); + } +} diff --git a/tests/Core/Components/DataGrid/FluentDataGridCellTests.razor b/tests/Core/Components/DataGrid/FluentDataGridCellTests.razor index de80f1e281..244592a956 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridCellTests.razor +++ b/tests/Core/Components/DataGrid/FluentDataGridCellTests.razor @@ -1,5 +1,5 @@ @using Xunit -@using static Microsoft.FluentUI.AspNetCore.Components.Tests.Components.DataGrid.FluentDataGridColumSelectTests +@using static Microsoft.FluentUI.AspNetCore.Components.Tests.Components.DataGrid.SelectColumnTests @inherits Bunit.TestContext @code { @@ -103,6 +103,30 @@ Assert.True(OnCellKeyDownInvoked); } + [Fact] + public async Task FluentDataGridCell_HandleOnCellKeyDownAsync_HandlesKeyEnter_EntireRow() + { + // Arrange + var items = new List(People).AsQueryable(); + + var cut = Render>( + @ + + + ); + + // Act + var keyboardEvent = new KeyboardEventArgs { Code = "Enter" }; + var cell = cut.FindComponent>(); + await cell.Instance.HandleOnCellKeyDownAsync(keyboardEvent); + + // Assert + Assert.False(OnCellKeyDownInvoked); + } [Fact] public async Task FluentDataGridCell_HandleOnCellKeyDownAsync_HandlesKeyOther() @@ -148,7 +172,7 @@ // Assert Assert.True(cell.IsDisposed); - + } public bool OnCellClickInvoked { get; set; } diff --git a/tests/Core/Components/DataGrid/FluentDataGridRowTests.razor b/tests/Core/Components/DataGrid/FluentDataGridRowTests.razor index 6cddecf7da..b49b5a2761 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridRowTests.razor +++ b/tests/Core/Components/DataGrid/FluentDataGridRowTests.razor @@ -1,5 +1,5 @@ @using Xunit -@using static Microsoft.FluentUI.AspNetCore.Components.Tests.Components.DataGrid.FluentDataGridColumSelectTests +@using static Microsoft.FluentUI.AspNetCore.Components.Tests.Components.DataGrid.SelectColumnTests @inherits Bunit.TestContext @code { diff --git a/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Descending.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Descending.verified.razor.html deleted file mode 100644 index 090e3af1e1..0000000000 --- a/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Descending.verified.razor.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - -
Item1
- -
-
-
-
-
-
-
Item2
-
-
-
DA
CB
BC
AD
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Descending.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Descending.verified.razor.html deleted file mode 100644 index 090e3af1e1..0000000000 --- a/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Descending.verified.razor.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - -
Item1
- -
-
-
-
-
-
-
Item2
-
-
-
DA
CB
BC
AD
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridSortByTests.razor b/tests/Core/Components/DataGrid/FluentDataGridSortByTests.razor index a58f68fce2..f53211f4c0 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridSortByTests.razor +++ b/tests/Core/Components/DataGrid/FluentDataGridSortByTests.razor @@ -20,68 +20,77 @@ [Fact] public async Task DataGridSortByTests_SortByColumnTitle_Ascending() { - string[] expected = ["A", "B", "C", "D"]; FluentDataGrid<(string, string)> _dataGrid = null!; var cut = Render( - @ - - - ); + @ + + + ); await cut.InvokeAsync(() => _dataGrid.SortByColumnAsync("Item1", DataGridSortDirection.Ascending)); - cut.Verify(); + var row = cut.FindComponent>(); + + // Assert + Assert.Equal("A", row.Instance.InternalGridContext.Items.First().Item1); + } [Fact] public async Task DataGridSortByTests_SortByColumnTitle_Descending() { - string[] expected = ["D", "C", "B", "A"]; FluentDataGrid<(string, string)> _dataGrid = null!; var cut = Render( - @ - - - ); + @ + + + ); await cut.InvokeAsync(() => _dataGrid.SortByColumnAsync("Item1", DataGridSortDirection.Descending)); - cut.Verify(); + var row = cut.FindComponent>(); + + // Assert + Assert.Equal("D", row.Instance.InternalGridContext.Items.First().Item1); } [Fact] public async Task DataGridSortByTests_SortByColumnIndex_Ascending() { - string[] expected = ["A", "B", "C", "D"]; FluentDataGrid<(string, string)> _dataGrid = null!; var cut = Render( - @ - - - ); + @ + + + ); await cut.InvokeAsync(() => _dataGrid.SortByColumnAsync(0, DataGridSortDirection.Ascending)); - cut.Verify(); + var row = cut.FindComponent>(); + + // Assert + Assert.Equal("A", row.Instance.InternalGridContext.Items.First().Item1); } [Fact] public async Task DataGridSortByTests_SortByColumnIndex_Descending() { - string[] expected = ["D", "C", "B", "A"]; FluentDataGrid<(string, string)> _dataGrid = null!; var cut = Render( - @ - - - ); + @ + + + ); await cut.InvokeAsync(() => _dataGrid.SortByColumnAsync(0, DataGridSortDirection.Descending)); - cut.Verify(); + var row = cut.FindComponent>(); + + // Assert + Assert.Equal("D", row.Instance.InternalGridContext.Items.First().Item1); } } diff --git a/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Ascending.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnKeyGridSort.verified.razor.html similarity index 63% rename from tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Ascending.verified.razor.html rename to tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnKeyGridSort.verified.razor.html index 8fe0626eec..d67660990f 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnIndex_Ascending.verified.razor.html +++ b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnKeyGridSort.verified.razor.html @@ -1,44 +1,48 @@ - +
- - - - - + + + - - - + + + - - - + + + - - - + + +
+
-
Item1
+
First Name
+
+
-
-
Item2
-
+ + +
Last Name
+
+
+
AD
DollyParton
BC
JamesBond
CB
NicoleKidman
DA
TomCruise
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Ascending.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnKeyGridSort_NoSortFunction.verified.razor.html similarity index 63% rename from tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Ascending.verified.razor.html rename to tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnKeyGridSort_NoSortFunction.verified.razor.html index 8fe0626eec..db6e62aad7 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridSortByTests.DataGridSortByTests_SortByColumnTitle_Ascending.verified.razor.html +++ b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnKeyGridSort_NoSortFunction.verified.razor.html @@ -1,44 +1,48 @@ - +
- - - - - + + + - - - + + + - - - + + + - - - + + +
+
-
Item1
+
First Name
+
+
-
-
Item2
-
+ + +
Last Name
+
+
+
AD
TomCruise
BC
DollyParton
CB
NicoleKidman
DA
JamesBond
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridTests.razor b/tests/Core/Components/DataGrid/FluentDataGridTests.razor index d920889326..11ba88e761 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridTests.razor +++ b/tests/Core/Components/DataGrid/FluentDataGridTests.razor @@ -1,4 +1,5 @@ @using Bunit.TestDoubles +@using System.ComponentModel.DataAnnotations @using Xunit @inherits Bunit.TestContext @@ -237,6 +238,11 @@ }); } + public static RenderFragment StringToRenderFragment(string text) => builder => + { + builder.AddContent(0, text); + }; + [Fact] public async Task FluentDataGrid_Virtualize() { @@ -251,7 +257,7 @@ var cut = Render>( @
- +
); @@ -353,7 +359,7 @@ FluentDataGrid? grid = default!; var cut = Render>( @ - + ); grid.LoadStateFromQueryString("?orderby=Name%20asc"); @@ -445,7 +451,7 @@

empty content

loading content

- +

@context.Name

@@ -574,6 +580,141 @@ } + [Fact] + public async Task FluentDataGrid_ShowColumnOptionAsync_AlignEnd() + { + // Arrange && Act + FluentDataGrid? grid = default!; + + var cut = Render>( + @ + + + +
Hello!
+
+
+
+
+ ); + + await grid.ShowColumnOptionsAsync(0); + + var columnOptions = cut.Find(".options").TextContent; + + // Assert + Assert.Equal("Hello!", columnOptions); + + } + + [Fact] + public async Task FluentDataGrid_ShowColumnOptionAsync_WithResizeTypeNull() + { + // Arrange && Act + FluentDataGrid? grid = default!; + + var cut = Render>( + @ + + + +
Hello!
+
+
+
+
+ ); + + await grid.ShowColumnOptionsAsync(0); + + var columnOptions = cut.Find(".options").TextContent; + + // Assert + Assert.Equal("Hello!", columnOptions); + + } + + [Fact] + public async Task FluentDataGrid_ShowColumnOptionAsync_AlignEnd_WithResizeTypeNotNull() + { + // Arrange && Act + FluentDataGrid? grid = default!; + + var cut = Render>( + @ + + + +
Hello!
+
+
+
+
+ ); + + await grid.ShowColumnOptionsAsync(0); + + var columnOptions = cut.Find(".options").TextContent; + + // Assert + Assert.Equal("Hello!", columnOptions); + + } + + [Fact] + public async Task FluentDataGrid_FilterButtonAsync() + { + // Arrange && Act + FluentDataGrid? grid = default!; + + var cut = Render>( + @ + + + +
Hello!
+
+
+
+
+ ); + + await grid.ShowColumnOptionsAsync(0); + + var columnOptions = cut.Find(".options").TextContent; + + // Assert + Assert.Equal("Hello!", columnOptions); + + } + + [Fact] + public async Task FluentDataGrid_AnyColumnAction() + { + // Arrange && Act + FluentDataGrid? grid = default!; + + var cut = Render>( + @ + + + +
Hello!
+
+
+
+
+ ); + + await grid.ShowColumnOptionsAsync(0); + + var columnOptions = cut.Find(".options").TextContent; + + // Assert + Assert.Equal("Hello!", columnOptions); + + } + [Fact] public async Task FluentDataGrid_ShowColumnOptionAsync_ByName() { @@ -786,7 +927,7 @@ var x = cut.FindComponent>(); - x.Find("fluent-text-input").KeyDown(new KeyboardEventArgs {Key = "Enter"}); + x.Find("fluent-text-input").KeyDown(new KeyboardEventArgs {Key = "Enter"}); // Assert @@ -860,9 +1001,6 @@ ); - //var row = cut.FindComponent>(); - //row.Find(".col-options-button").Click(); - await grid.ShowColumnResizeAsync(-1); // Assert @@ -981,7 +1119,7 @@ var cut = Render>( @ - + ); @@ -1003,6 +1141,8 @@ ); + grid._sortByColumn = null; + grid.RemoveSortByColumnAsync(); grid.SortByColumnAsync("Name", DataGridSortDirection.Descending); var row = cut.FindComponent>(); @@ -1044,7 +1184,6 @@ } - [Fact] public async Task FluentDataGrid_UpdateItemsPerPageAsync() { @@ -1137,7 +1276,6 @@ Assert.True(true); } - [Fact] public async Task FluentDataGrid_SetColumnWidthExact() { @@ -1158,70 +1296,6 @@ Assert.True(true); } - // // Add a test to call DisposeAsync on the component - // [Fact] - // public async Task FluentDataGrid_DisposeAsync_Calls_Dispose() - // { - // // Arrange - // var cut = Render>( - // @ - // - // - // - // ); - // // Act - // await cut.Instance.DisposeAsync(); - // // Assert - // // No exceptions should be thrown, and the component should be disposed correctly - // Assert.True(cut.IsDisposed); - // } - - // Sample data... - private IEnumerable GetCustomers() - { - yield return new Customer(1, "Denis Voituron"); - yield return new Customer(2, "Vincent Baaij"); - yield return new Customer(3, "Bill Gates"); - } - - private record Customer(int Id, string Name); - - private IQueryable GetRandomCustomers(int size = 500) - { - Customer[] data = new Customer[size]; - - for (int i = 0; i < size; i++) - { - - data[i] = new Customer(i, $"Customer {i} - {Guid.NewGuid().ToString("N").Substring(0, 8)}" ); - } - return data.AsQueryable(); - } - - private class MyKeyCodeListener : IKeyCodeListener - { - private Action _actionKeyDown; - private Action? _actionKeyUp; - - public MyKeyCodeListener(Action actionKeyDown, Action? actionKeyUp = null) - { - _actionKeyDown = actionKeyDown; - _actionKeyUp = actionKeyUp; - } - - public Task OnKeyDownAsync(FluentKeyCodeEventArgs args) - { - _actionKeyDown.Invoke(args); - return Task.CompletedTask; - } - - public Task OnKeyUpAsync(FluentKeyCodeEventArgs args) - { - _actionKeyUp?.Invoke(args); - return Task.CompletedTask; - } - } - [Fact] public void FluentDataGrid_ColumnResizeUISettings() { @@ -1279,10 +1353,643 @@

empty content

- ); + + ); // Assert cut.Verify(); } + + [Fact] + public void FluentDataGrid_ColumnKeyGridSort() + { + // Arrange && Act + FluentDataGrid? grid = default!; + + var _firstNameSort = new ColumnKeyGridSort( + "firstname", + (queryable, sortAscending) => + { + if (sortAscending) + { + return queryable.OrderBy(x => x.Properties["firstname"]); + } + else + { + return queryable.OrderByDescending(x => x.Properties["firstname"]); + } + }); + + + var _lastNameSort = new ColumnKeyGridSort( + "lastname", + (queryable, sortAscending) => + { + if (sortAscending) + { + return queryable.OrderBy(x => x.Properties["lastname"]); + } + else + { + return queryable.OrderByDescending(x => x.Properties["lastname"]); + } + } + ); + + var _gridData = new GridRow[] { + new(new Dictionary{ { "firstname", "Tom" }, { "lastname", "Cruise" } }), + new(new Dictionary{ { "firstname", "Dolly" }, { "lastname", "Parton" } }), + new(new Dictionary{ { "firstname", "Nicole" }, { "lastname", "Kidman" } }), + new(new Dictionary{ { "firstname", "James" }, { "lastname", "Bond" } }), + }.AsQueryable(); + + + + var cut = Render>( + @ + + @context.Properties["firstname"] + + + + @context.Properties["lastname"] + + + ); + + var row = cut.FindComponent>(); + // Assert + + Assert.Equal("Dolly", row.Instance.InternalGridContext.Items.First().Properties["firstname"]); + + } + + [Fact] + public void FluentDataGrid_ColumnKeyGridSort_NoSortFunction() + { + // Arrange && Act + FluentDataGrid? grid = default!; + ColumnKeyGridSort? _firstNameSort = null; + + + var _lastNameSort = new ColumnKeyGridSort( + "lastname", + (queryable, sortAscending) => + { + if (sortAscending) + { + return queryable.OrderBy(x => x.Properties["lastname"]); + } + else + { + return queryable.OrderByDescending(x => x.Properties["lastname"]); + } + } + ); + + var _gridData = new GridRow[] { + new(new Dictionary{ { "firstname", "Tom" }, { "lastname", "Cruise" } }), + new(new Dictionary{ { "firstname", "Dolly" }, { "lastname", "Parton" } }), + new(new Dictionary{ { "firstname", "Nicole" }, { "lastname", "Kidman" } }), + new(new Dictionary{ { "firstname", "James" }, { "lastname", "Bond" } }), + }.AsQueryable(); + + var cut = Render>( + @ + + @context.Properties["firstname"] + + + + @context.Properties["lastname"] + + ); + + var row = cut.FindComponent>(); + // Assert + + Assert.Equal("Tom", row.Instance.InternalGridContext.Items.First().Properties["firstname"]); + } + + + [Fact] + public void FluentDataGrid_HeaderCellAsButtonWithMenu() + { + // Arrange && Act + FluentDataGrid? grid = default!; + var cut = Render>( + @ + + + +

empty content

+
); + + var rows = cut.FindComponents>(); + + // Assert + Assert.NotEmpty(rows); // Asserting that there are rows present + Assert.Equal(4, rows.Count); //3 DataRows + 1 header row + } + + [Fact] + public void FluentDataGrid_HeaderCellAsButtonWithMenu_MenuKeyDown() + { + // Arrange && Act + FluentDataGrid? grid = default!; + var cut = Render>( + @ + + +
Hello!
+
+
+
+ ); + + var row = cut.FindComponent>(); + + // Simulate a click on the header cell button + row.Find(".col-sort-button").Click(); + + var items = cut.FindAll("fluent-menu-item",true); + + items[0].KeyDown(new KeyboardEventArgs() { Key = "Enter" }); // Click the first item in the menu - Sort + items[1].KeyDown(new KeyboardEventArgs() { Key = "Enter" }); // Click the second item in the menu - Resize + items[2].KeyDown(new KeyboardEventArgs() { Key = "Enter" }); // Click the third item in the menu - Options + + // Assert + Assert.NotEmpty(items); // Asserting that there are menu items present + Assert.Equal(3, items.Count); // Only one item in the menu + } + + [Fact] + public void FluentDataGrid_HeaderCellAsButtonWithMenu_OnlySort() + { + // Arrange && Act + FluentDataGrid? grid = default!; + var cut = Render>( + @ + + + ); + grid.SortByColumnAsync("Name", DataGridSortDirection.Descending); + + var row = cut.FindComponent>(); + + // Simulate a click on the header cell button + row.Find(".col-sort-button").Click(); + + var items = cut.FindAll("fluent-menu-item",true); + + // Assert + Assert.NotEmpty(items); // Asserting that there are menu items present + Assert.Single(items); // Only one item in the menu + } + + [Fact] + public void FluentDataGrid_HeaderCellAsButtonWithMenu_Filtered_NoOptions() + { + // Arrange && Act + FluentDataGrid? grid = default!; + var cut = Render>( + @ + + + ); + + var row = cut.FindComponent>(); + + // Simulate a click on the header cell button + row.Find(".col-sort-button").Click(); + + var items = cut.FindAll("fluent-menu-item",true); + + // Assert + Assert.NotEmpty(items); // Asserting that there are menu items present + Assert.Single(items); // Only one item in the menu + } + + [Fact] + public void FluentDataGrid_HeaderCellAsButtonWithMenu_Filtered_WithOptions() + { + // Arrange && Act + FluentDataGrid? grid = default!; + var cut = Render>( + @ + + +
Hello!
+
+
+
+ ); + + var row = cut.FindComponent>(); + + // Simulate a click on the header cell button + row.Find(".col-sort-button").Click(); + + var items = cut.FindAll("fluent-menu-item",true); + + // Assert + Assert.NotEmpty(items); // Asserting that there are menu items present + Assert.Equal(2, items.Count); // Only one item in the menu + } + + [Fact] + public void FluentDataGrid_HeaderCellAsButtonWithMenu_OnlyResize() + { + // Arrange && Act + FluentDataGrid? grid = default!; + var cut = Render>( + @ + + + + ); + + var row = cut.FindComponent>(); + + // Simulate a click on the header cell button + row.Find(".col-sort-button").Click(); + + var items = cut.FindAll("fluent-menu-item",true); + + // Assert + Assert.NotEmpty(items); // Asserting that there are menu items present + Assert.Single(items); // Only one item in the menu + } + + [Fact] + public void FluentDataGrid_HeaderCellAsButtonWithMenu_OnlyOptions() + { + // Arrange && Act + FluentDataGrid? grid = default!; + var cut = Render>( + @ + + +
Hello!
+
+
+
+ ); + + var row = cut.FindComponent>(); + + // Simulate a click on the header cell button + row.Find(".col-sort-button").Click(); + + var items = cut.FindAll("fluent-menu-item",true); + + // Assert + Assert.NotEmpty(items); // Asserting that there are menu items present + Assert.Single(items); // Only one item in the menu + } + + [Fact] + public void FluentDataGrid_HeaderCellAsButtonWithMenu_NoColumnActions() + { + // Arrange && Act + FluentDataGrid? grid = default!; + var cut = Render>( + @ + + + + ); + + var row = cut.FindComponent>(); + + // Assert + Assert.Equal("Name", row.Find(".col-title-text").TextContent); // Only one item in the menu + } + + [Fact] + public void FluentDataGrid_HeaderCellAsButtonWithMenu_ButtonClick() + { + // Arrange && Act + FluentDataGrid? grid = default!; + var cut = Render>( + @ + + + + ); + + var row = cut.FindComponent>(); + + row.Find(".col-sort-button").Click(); // Sort ascending by default + row.Find(".col-sort-button").Click(); // Sort descending + + var button = cut.FindComponent(); + cut.InvokeAsync(button.Instance.OnClick.InvokeAsync); + + + // Assert + Assert.Equal("Name", row.Find(".col-title-text").TextContent); // Only one item in the menu + } + + [Fact] + public void FluentDataGrid_HeaderCellItemTemplate() + { + // Arrange && Act + FluentDataGrid? grid = default!; + var cut = Render>( + @ + + Hello! + + + ); + + var row = cut.FindComponent>(); + + // Simulate a click on the header cell button + var cell = row.Find("th"); + + // Assert + Assert.NotNull(cell); // Asserting that there are menu items present + Assert.Equal("Hello!", cell.InnerHtml); // Only one item in the menu + } + + + [Fact] + public void FluentDataGrid_HeaderCellTitleTemplate() + { + // Arrange && Act + FluentDataGrid? grid = default!; + var cut = Render>( + @ + + Hello! + + + ); + + var row = cut.FindComponent>(); + + // Simulate a click on the header cell button + var cell = row.Find(".col-title-text"); + + // Assert + Assert.NotNull(cell); // Asserting that there are menu items present + Assert.Equal("Hello!", cell.InnerHtml); // Only one item in the menu + } + + [Fact] + public void FluentDataGrid_TemplateColumn_WithTooltip() + { + var cut = Render>( + @ + +

@context.Name

+
+
); + + // Assert + Assert.NotNull(cut.Find("tr")); + } + + [Fact] + public void FluentDataGrid_PropertyColumn_WithFormat() + { + // Arrange && Act + var customers = GetCustomers(); + var cut = Render>( + @ + + + + + + ); + + // Assert + var rows = cut.FindComponents>(); + + // Verify header row exists + Assert.NotEmpty(rows); + + // Find data rows (excluding header) + var dataRows = rows.Where(r => r.Instance.RowType == DataGridRowType.Default).ToList(); + Assert.NotEmpty(dataRows); + + // Verify formatted content is rendered + var gridContent = cut.Markup; + + // Check formatted ID (should be padded with zeros) + Assert.Contains("00001", gridContent); // ID 1 formatted as D5 + Assert.Contains("00002", gridContent); // ID 2 formatted as D5 + + // Check formatted date (should be yyyy-MM-dd format) + Assert.Contains("2023-01-15", gridContent); // Date formatted as yyyy-MM-dd + Assert.Contains("2023-02-20", gridContent); // Date formatted as yyyy-MM-dd + } + + [Fact] + public void FluentDataGrid_PropertyColumn_WithCustomFormat() + { + // Arrange && Act + var customers = GetCustomers(); + var cut = Render>( + @ + + ); + + // Assert + var rows = cut.FindComponents>(); + + + var cell = rows[0].Find("td"); + Assert.Contains("01-15-2023", cell.InnerHtml); // Check custom date format + + cell = rows[1].Find("td"); + Assert.Contains("02-20-2023", cell.InnerHtml); // Check custom date format + } + + [Fact] + public void FluentDataGrid_PropertyColumn_WithEnumData() + { + // Arrange && Act + var customersWithStatus = GetCustomers(); + var cut = Render>( + @ + + + + + + ); + + // Assert + var rows = cut.FindComponents>(); + + // Verify header row exists + Assert.NotEmpty(rows); + + // Find data rows (excluding header) + var dataRows = rows.Where(r => r.Instance.RowType == DataGridRowType.Default).ToList(); + Assert.NotEmpty(dataRows); + + // Verify enum content is rendered as string + var gridContent = cut.Markup; + + // Check enum values are displayed as strings + Assert.Contains("active", gridContent); + Assert.Contains("inactive", gridContent); + Assert.Contains("pending", gridContent); + } + + [Fact] + public void FluentDataGrid_PropertyColumn_WithFormat_InvalidType() + { + // Arrange & Act & Assert + Assert.Throws(() => + { + var customers = GetCustomers(); + var cut = Render>( + @ + + + + ); + }); + } + + [Fact] + public void FluentDataGrid_PropertyColumn_UsesDisplayAttribute() + { + // Arrange && Act + var customers = GetCustomersWithDisplayAttributes(); + var cut = Render>( + @ + + + ); + + // Assert + var headerRow = cut.FindComponents>() + .FirstOrDefault(r => r.Instance.RowType == DataGridRowType.Header); + + Assert.NotNull(headerRow); + + // Verify that Display attribute values are used as column titles + var headerContent = headerRow.Markup; + Assert.Contains("Customer ID", headerContent); // From Display attribute + Assert.Contains("Customer Name", headerContent); // From Display attribute + } + + [Fact] + public void FluentDataGrid_PropertyColumn_ExplicitTitle_OverridesDisplayAttribute() + { + // Arrange && Act + var customers = GetCustomersWithDisplayAttributes(); + var cut = Render>( + @ + + + + ); + + // Assert + var headerRow = cut.FindComponents>() + .FirstOrDefault(r => r.Instance.RowType == DataGridRowType.Header); + + Assert.NotNull(headerRow); + + // Verify that explicit Title parameter overrides Display attribute + var headerContent = headerRow.Markup; + Assert.Contains("Override Title", headerContent); + Assert.DoesNotContain("Customer Name", headerContent); // Display attribute should be ignored + } + + // Sample data with Display attributes + + + // Sample data... + private IEnumerable GetCustomers() + { + yield return new Customer(1, "Denis Voituron", new DateTime(2023, 1, 15), CustomerStatus.Active ); + yield return new Customer(2, "Vincent Baaij", new DateTime(2023, 2, 20), CustomerStatus.Inactive); + yield return new Customer(3, "Bill Gates", new DateTime(2023, 3, 31), CustomerStatus.Pending); + } + + private record GridRow(Dictionary Properties); + private record Customer(int Id, string Name, DateTime CreatedDate, CustomerStatus Status); + + + private IQueryable GetRandomCustomers(int size = 500) + { + Customer[] data = new Customer[size]; + + for (int i = 0; i < size; i++) + { + + data[i] = new Customer(i, $"Customer {i} - {Guid.NewGuid().ToString("N").Substring(0, 8)}", new DateTime(2025, (i % 12) + 1, 2), CustomerStatus.Suspended ); + } + return data.AsQueryable(); + } + + private class MyKeyCodeListener : IKeyCodeListener + { + private Action _actionKeyDown; + private Action? _actionKeyUp; + + public MyKeyCodeListener(Action actionKeyDown, Action? actionKeyUp = null) + { + _actionKeyDown = actionKeyDown; + _actionKeyUp = actionKeyUp; + } + + public Task OnKeyDownAsync(FluentKeyCodeEventArgs args) + { + _actionKeyDown.Invoke(args); + return Task.CompletedTask; + } + + public Task OnKeyUpAsync(FluentKeyCodeEventArgs args) + { + _actionKeyUp?.Invoke(args); + return Task.CompletedTask; + } + } + + // Sample enum types for testing + private enum CustomerStatus + { + Active, + Inactive, + Pending, + Suspended + } + + private IEnumerable GetCustomersWithDisplayAttributes() + { + yield return new CustomerWithDisplayAttributes(1, "Denis Voituron"); + yield return new CustomerWithDisplayAttributes(2, "Vincent Baaij"); + yield return new CustomerWithDisplayAttributes(3, "Bill Gates"); + } + + private class CustomerWithDisplayAttributes + { + public CustomerWithDisplayAttributes(int id, string name) // Changed string id back to int + { + Id = id; + FullName = name; + } + + [Display(Name = "Customer ID")] + public int Id { get; set; } + + [Display(Name = "Customer Name")] + public string FullName { get; set; } + + } } diff --git a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Customized_Rendering.verified.razor.html b/tests/Core/Components/DataGrid/SelectColumnTests.FluentDataGrid_ColumSelect_MultiSelect_Customized_Rendering.verified.razor.html similarity index 100% rename from tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Customized_Rendering.verified.razor.html rename to tests/Core/Components/DataGrid/SelectColumnTests.FluentDataGrid_ColumSelect_MultiSelect_Customized_Rendering.verified.razor.html diff --git a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Rendering.verified.razor.html b/tests/Core/Components/DataGrid/SelectColumnTests.FluentDataGrid_ColumSelect_MultiSelect_Rendering.verified.razor.html similarity index 100% rename from tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_MultiSelect_Rendering.verified.razor.html rename to tests/Core/Components/DataGrid/SelectColumnTests.FluentDataGrid_ColumSelect_MultiSelect_Rendering.verified.razor.html diff --git a/tests/Core/Components/DataGrid/SelectColumnTests.FluentDataGrid_ColumSelect_SingleSelect_Rendering.verified.razor.html b/tests/Core/Components/DataGrid/SelectColumnTests.FluentDataGrid_ColumSelect_SingleSelect_Rendering.verified.razor.html new file mode 100644 index 0000000000..fcb0991b1d --- /dev/null +++ b/tests/Core/Components/DataGrid/SelectColumnTests.FluentDataGrid_ColumSelect_SingleSelect_Rendering.verified.razor.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
Name
+
+
+
+ + Jean Martin
+ + Kenji Sato
+ + Julie Smith
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_SingleSelect_Rendering.verified.razor.html b/tests/Core/Components/DataGrid/SelectColumnTests.FluentDataGrid_ColumSelect_SingleStickySelect_Rendering.verified.razor.html similarity index 100% rename from tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_SingleSelect_Rendering.verified.razor.html rename to tests/Core/Components/DataGrid/SelectColumnTests.FluentDataGrid_ColumSelect_SingleStickySelect_Rendering.verified.razor.html diff --git a/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_MultiSelect_Customized_Rendering.verified.razor.html b/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_MultiSelect_Customized_Rendering.verified.razor.html new file mode 100644 index 0000000000..f37fc0a1dc --- /dev/null +++ b/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_MultiSelect_Customized_Rendering.verified.razor.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
Name
+
+
+
+ Jean Martin
Kenji Sato
Julie Smith
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_MultiSelect_Rendering.verified.razor.html b/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_MultiSelect_Rendering.verified.razor.html new file mode 100644 index 0000000000..561663a7e9 --- /dev/null +++ b/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_MultiSelect_Rendering.verified.razor.html @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
Name
+
+
+
+ + Jean Martin
+ + Kenji Sato
+ + Julie Smith
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_SingleSelect_Rendering.verified.razor.html b/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_SingleSelect_Rendering.verified.razor.html new file mode 100644 index 0000000000..fcb0991b1d --- /dev/null +++ b/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_SingleSelect_Rendering.verified.razor.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
Name
+
+
+
+ + Jean Martin
+ + Kenji Sato
+ + Julie Smith
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_SingleStickySelect_Rendering.verified.razor.html b/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_SingleStickySelect_Rendering.verified.razor.html similarity index 100% rename from tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.FluentDataGrid_ColumSelect_SingleStickySelect_Rendering.verified.razor.html rename to tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_SingleStickySelect_Rendering.verified.razor.html diff --git a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.razor b/tests/Core/Components/DataGrid/SelectColumnTests.razor similarity index 55% rename from tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.razor rename to tests/Core/Components/DataGrid/SelectColumnTests.razor index a4bacdeb53..57cb6ad5ea 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridColumSelectTests.razor +++ b/tests/Core/Components/DataGrid/SelectColumnTests.razor @@ -1,4 +1,6 @@ @using Xunit; +@using Microsoft.AspNetCore.Components; + @inherits Bunit.TestContext @code @@ -15,7 +17,7 @@ new Person(3, "Julie Smith", new DateOnly(1958, 10, 10)), }.AsQueryable(); - public FluentDataGridColumSelectTests() + public SelectColumnTests() { JSInterop.Mode = JSRuntimeMode.Loose; @@ -25,25 +27,25 @@ } [Fact] - public void FluentDataGrid_ColumSelect_SingleSelect_Rendering() + public void SelectColumnTests_SingleSelect_Rendering() { IEnumerable SelectedItems = new List { People.ElementAt(1) }; // Arrange var cut = Render( - @ - - - - ); + @ + + + + ); cut.Verify(); } [Fact] - public async Task FluentDataGrid_ColumSelect_SingleSelect_SelectedItems() + public async Task SelectColumnTests_SingleSelect_SelectedItems() { IEnumerable SelectedItems = new List(); @@ -52,7 +54,8 @@ @ + @bind-SelectedItems="@SelectedItems" + Tooltip="true"/> ); @@ -75,7 +78,7 @@ [Fact] - public async Task FluentDataGrid_ColumSelect_Selectable_SingleSelect_SelectedItems() + public async Task SelectColumnTests_Selectable_SingleSelect_SelectedItems() { IEnumerable SelectedItems = new List(); @@ -107,7 +110,7 @@ } [Fact] - public async Task FluentDataGrid_ColumSelect_SingleSelect_Property() + public async Task SelectColumnTests_SingleSelect_Property() { var items = new List(People).AsQueryable(); @@ -138,7 +141,7 @@ } [Fact] - public async Task FluentDataGrid_ColumSelect_Selectable_SingleSelect_Property() + public async Task SelectColumnTests_Selectable_SingleSelect_Property() { var items = new List(People).AsQueryable(); @@ -171,7 +174,7 @@ } [Fact] - public void FluentDataGrid_ColumSelect_SingleStickySelect_Rendering() + public void SelectColumnTests_SingleStickySelect_Rendering() { IEnumerable SelectedItems = new List { People.ElementAt(1) }; @@ -189,7 +192,7 @@ } [Fact] - public async Task FluentDataGrid_ColumSelect_SingleStickySelect_SelectedItems() + public async Task SelectColumnTests_SingleStickySelect_SelectedItems() { IEnumerable SelectedItems = new List(); @@ -219,7 +222,7 @@ } [Fact] - public async Task FluentDataGrid_ColumSelect_SingleStickySameItemSelect_SelectedItems() + public async Task SelectColumnTests_SingleStickySameItemSelect_SelectedItems() { IEnumerable SelectedItems = new List(); @@ -249,7 +252,7 @@ } [Fact] - public async Task FluentDataGrid_ColumSelect_Selectable_SingleStickySelect_SelectedItems() + public async Task SelectColumnTests_Selectable_SingleStickySelect_SelectedItems() { IEnumerable SelectedItems = new List(); @@ -281,7 +284,7 @@ } [Fact] - public async Task FluentDataGrid_ColumSelect_SingleStickySelect_Property() + public async Task SelectColumnTests_SingleStickySelect_Property() { var items = new List(People).AsQueryable(); @@ -312,7 +315,7 @@ } [Fact] - public async Task FluentDataGrid_ColumSelect_Selectable_SingleStickySelect_Property() + public async Task SelectColumnTests_Selectable_SingleStickySelect_Property() { var items = new List(People).AsQueryable(); @@ -345,7 +348,7 @@ } [Fact] - public void FluentDataGrid_ColumSelect_MultiSelect_Rendering() + public void SelectColumnTests_MultiSelect_Rendering() { IEnumerable SelectedItems = new List { People.ElementAt(1), People.ElementAt(2) }; @@ -363,7 +366,7 @@ } [Fact] - public async Task FluentDataGrid_ColumSelect_MultiSelect_SelectedItems() + public async Task SelectColumnTests_MultiSelect_SelectedItems() { IEnumerable SelectedItems = new List(); @@ -398,7 +401,7 @@ } [Fact] - public async Task FluentDataGrid_ColumSelect_Selectable_MultiSelect_SelectedItems() + public async Task SelectColumnTests_Selectable_MultiSelect_SelectedItems() { IEnumerable SelectedItems = new List(); @@ -435,7 +438,7 @@ } [Fact] - public async Task FluentDataGrid_ColumSelect_MultiSelect_Property() + public async Task SelectColumnTests_MultiSelect_Property() { var items = new List(People).AsQueryable(); @@ -471,7 +474,7 @@ } [Fact] - public async Task FluentDataGrid_ColumSelect_Selectable_MultiSelect_Property() + public async Task SelectColumnTests_Selectable_MultiSelect_Property() { var items = new List(People).AsQueryable(); @@ -509,7 +512,7 @@ } [Fact] - public async Task FluentDataGrid_ColumSelect_MultiSelect_SelectAll_SelectedItems() + public async Task SelectColumnTests_MultiSelect_SelectAll_SelectedItems() { IEnumerable SelectedItems = new List(); @@ -539,7 +542,7 @@ } [Fact] - public async Task FluentDataGrid_ColumSelect_MultiSelect_SelectAll_Property() + public async Task SelectColumnTests_MultiSelect_SelectAll_Property() { var items = new List(People).AsQueryable(); @@ -572,7 +575,7 @@ } // [Fact] - // public void FluentDataGrid_ColumSelect_SwitchMultiToSingleSelect() + // public void SelectColumnTests_SwitchMultiToSingleSelect() // { // IEnumerable SelectedItems = new List { People.ElementAt(1), People.ElementAt(2) }; @@ -602,7 +605,7 @@ // } [Fact] - public async Task FluentDataGrid_ColumSelect_SelectAll_Disabled() + public async Task SelectColumnTests_SelectAll_Disabled() { IEnumerable SelectedItems = new List(); @@ -628,45 +631,45 @@ } [Fact] - public void FluentDataGrid_ColumSelect_MultiSelect_Customized_Rendering() + public void SelectColumnTests_MultiSelect_Customized_Rendering() { IEnumerable SelectedItems = new List() { People.ElementAt(1), People.ElementAt(2) }; // Arrange var cut = Render( - @ - - - @(context.AllSelected == true ? "✅" : context.AllSelected == null ? "➖" : "⬜") - - - @(SelectedItems.Contains(context) ? "✅" : " ") - - - - - ); + @ + + + @(context.AllSelected == true ? "✅" : context.AllSelected == null ? "➖" : "⬜") + + + @(SelectedItems.Contains(context) ? "✅" : " ") + + + + + ); cut.Verify(); } [Fact] - public async Task FluentDataGrid_ColumSelect_SingleSelect_NotSelectFromEntireRow() + public async Task SelectColumnTests_SingleSelect_NotSelectFromEntireRow() { IEnumerable SelectedItems = new List(); // Arrange var cut = Render( - @ - - - - ); + @ + + + + ); // Act - Click on the second cell => no selection await ClickOnRowAsync(cut, row: 0, col: 1); @@ -684,20 +687,20 @@ } [Fact] - public async Task FluentDataGrid_ColumSelect_MultiSelect_NotSelectFromEntireRow() + public async Task SelectColumnTests_MultiSelect_NotSelectFromEntireRow() { IEnumerable SelectedItems = new List(); // Arrange var cut = Render( - @ - - - - ); + @ + + + + ); // Act - Click on the second cell => no selection await ClickOnRowAsync(cut, row: 0, col: 1); @@ -720,6 +723,393 @@ Assert.Equal(2, SelectedItems.ElementAt(1).PersonId); } + [Fact] + public async Task SelectColumnTests_ClearSelection_SingleSelect() + { + IEnumerable SelectedItems = new List { People.ElementAt(1) }; + + // Arrange + var cut = Render( + @ + + + + ); + + // Pre-Assert - Item is selected + Assert.Single(cut.FindAll("svg[row-selected]")); + Assert.Single(SelectedItems); + + // Act - Clear selection + var selectColumn = cut.FindComponent>(); + + await ClickOnRowAsync(cut, row: 0, col: 0); + selectColumn.Instance.ClearSelection(); + + // Assert - Selection is cleared + Assert.False(SelectedItems.First().Selected); + } + + [Fact] + public async Task SelectColumnTests_ClearSelection_MultiSelect() + { + IEnumerable SelectedItems = new List { People.ElementAt(1), People.ElementAt(2) }; + + // Arrange + var cut = Render( + @ + + + + ); + + // Pre-Assert - Items are selected + Assert.Equal(2, cut.FindAll("svg[row-selected]").Count); + Assert.Equal(2, SelectedItems.Count()); + + // Act - Clear selection + var selectColumn = cut.FindComponent>(); + + await ClickOnAllAsync(cut); + + selectColumn.Instance.ClearSelection(); + + // Assert - Selection is cleared + Assert.Empty(SelectedItems); + } + + [Fact] + public async Task SelectColumnTests_ClearSelectionAsync_SingleSelect() + { + IEnumerable SelectedItems = new List { People.ElementAt(1) }; + + // Arrange + var cut = Render( + @ + + + + ); + + // Pre-Assert - Item is selected + Assert.Single(cut.FindAll("svg[row-selected]")); + Assert.Single(SelectedItems); + + // Act - Clear selection + var selectColumn = cut.FindComponent>(); + + await ClickOnRowAsync(cut, row: 0, col: 0); + await selectColumn.Instance.ClearSelectionAsync(); + + // Assert - Selection is cleared + Assert.False(SelectedItems.First().Selected); + } + + [Fact] + public async Task SelectColumnTests_ClearSelectionAsync_MultiSelect() + { + IEnumerable SelectedItems = new List { People.ElementAt(1), People.ElementAt(2) }; + + // Arrange + var cut = Render( + @ + + + + ); + + // Pre-Assert - Items are selected + Assert.Equal(2, cut.FindAll("svg[row-selected]").Count); + Assert.Equal(2, SelectedItems.Count()); + + // Act - Clear selection + var selectColumn = cut.FindComponent>(); + + await ClickOnAllAsync(cut); + + await selectColumn.Instance.ClearSelectionAsync(); + + // Assert - Selection is cleared + Assert.Empty(SelectedItems); + } + + [Fact] + public async Task SelectColumnTests_SingleSelect_KeyboardSelection() + { + IEnumerable SelectedItems = new List(); + + // Arrange + var cut = Render( + @ + + + + ); + + // Pre-Assert + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(SelectedItems); + + // Act - Press Enter key on Row 0 + await KeyDownOnRowAsync(cut, row: 0, "Enter"); + Assert.Single(cut.FindAll("svg[row-selected]")); + Assert.Single(SelectedItems); + Assert.Equal(1, SelectedItems.First().PersonId); + + // Act - Press NumpadEnter key on Row 1 + await KeyDownOnRowAsync(cut, row: 1, "NumpadEnter"); + Assert.Single(cut.FindAll("svg[row-selected]")); + Assert.Single(SelectedItems); + Assert.Equal(2, SelectedItems.First().PersonId); + + // Act - Press other key on Row 2 (should not select) + await KeyDownOnRowAsync(cut, row: 2, "Space"); + Assert.Single(cut.FindAll("svg[row-selected]")); + Assert.Single(SelectedItems); + Assert.Equal(2, SelectedItems.First().PersonId); // Should still be row 1 + } + + [Fact] + public async Task SelectColumnTests_MultiSelect_KeyboardSelection() + { + IEnumerable SelectedItems = new List(); + + // Arrange + var cut = Render( + @ + + + + ); + + // Pre-Assert + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(SelectedItems); + + // Act - Press Enter key on Row 0 + await KeyDownOnRowAsync(cut, row: 0, "Enter"); + Assert.Single(cut.FindAll("svg[row-selected]")); + Assert.Single(SelectedItems); + + // Act - Press NumpadEnter key on Row 1 + await KeyDownOnRowAsync(cut, row: 1, "NumpadEnter"); + Assert.Equal(2, cut.FindAll("svg[row-selected]").Count); + Assert.Equal(2, SelectedItems.Count()); + + // Act - Press Enter key on Row 0 again (should unselect) + await KeyDownOnRowAsync(cut, row: 0, "Enter"); + Assert.Single(cut.FindAll("svg[row-selected]")); + Assert.Single(SelectedItems); + Assert.Equal(2, SelectedItems.First().PersonId); + } + + [Fact] + public async Task SelectColumnTests_KeyboardSelection_NotSelectFromEntireRow() + { + IEnumerable SelectedItems = new List(); + + // Arrange + var cut = Render( + @ + + + + ); + + // Pre-Assert + Assert.Empty(SelectedItems); + + // Act - Press Enter key on Row 0 (should not select when SelectFromEntireRow is false) + await KeyDownOnRowAsync(cut, row: 0, "Enter"); + Assert.Empty(SelectedItems); + } + + [Fact] + public async Task SelectColumnTests_KeyboardSelection_Selectable() + { + IEnumerable SelectedItems = new List(); + + // Arrange + var cut = Render( + @ + + + + ); + + // Pre-Assert + Assert.Empty(SelectedItems); + + // Act - Press Enter key on Row 0 (should not select - born in 1985) + await KeyDownOnRowAsync(cut, row: 0, "Enter"); + Assert.Empty(SelectedItems); + + // Act - Press Enter key on Row 1 (should select - born in 2004) + await KeyDownOnRowAsync(cut, row: 1, "Enter"); + Assert.Single(SelectedItems); + Assert.Equal(2, SelectedItems.First().PersonId); + } + + [Fact] + public async Task SelectColumnTests_OnRowClick_NotSelectEntireRow() + { + // Arrange + IEnumerable SelectedItems = new List(); + var cut = Render( + @ + + + + ); + + // Act - Click on Row 0 + await ClickOnRowAsync(cut, row: 0); + + // Assert - Verify row selection + Assert.Empty(SelectedItems); + } + + [Fact] + public async Task SelectColumnTests_OnRowKeyDown_NotSelectEntireRow() + { + // Arrange + IEnumerable SelectedItems = new List(); + var cut = Render( + @ + + + + ); + + // Act - key down on Row 0 + await KeyDownOnRowAsync(cut, row: 0, "Enter"); + + // Assert - Verify row selection + Assert.Empty(SelectedItems); + } +#pragma warning disable BL0005 + [Fact] + public async Task SelectColumnTests_KeepOnlyFirstSelectedItemAsync_WithNoSelectedItems_DoesNothing() + { + // Arrange + var selectColumn = new SelectColumn + { + SelectMode = DataGridSelectMode.Single, + SelectedItems = new List() + }; + // Act + var method = selectColumn.GetType().GetMethod("KeepOnlyFirstSelectedItemAsync", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!; + var result = method.Invoke(selectColumn, null); + if (result is Task task) await task; + // Assert + Assert.Empty(selectColumn.SelectedItems); + } + + [Fact] + public async Task SelectColumnTests_KeepOnlyFirstSelectedItemAsync_WithOneSelectedItem_DoesNothing() + { + // Arrange + var person = People.ElementAt(0); + var selectColumn = new SelectColumn + { + SelectMode = DataGridSelectMode.Single, + SelectedItems = new List { person }, + }; + // Act + var method = selectColumn.GetType().GetMethod("KeepOnlyFirstSelectedItemAsync", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!; + var result = method.Invoke(selectColumn, null); + if (result is Task task) await task; + // Assert + Assert.Single(selectColumn.SelectedItems); + Assert.Equal(person, selectColumn.SelectedItems.First()); + } + + [Fact] + public async Task SelectColumnTests_KeepOnlyFirstSelectedItemAsync_WithMultipleSelectedItems_KeepsFirstOnly_Single() + { + // Arrange + bool selectAllCalled = false; + var people = People.ToList(); + + var selectColumn = new SelectColumn + { + SelectMode = DataGridSelectMode.Single, + SelectedItems = new List { people[0], people[1], people[2] }, + SelectedItemsChanged = EventCallback.Factory.Create>(this, () => { }), + SelectAllChanged = EventCallback.Factory.Create(this, () => { selectAllCalled = true; }), + }; + // Act + var method = selectColumn.GetType().GetMethod("KeepOnlyFirstSelectedItemAsync", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!; + var result = method.Invoke(selectColumn, null); + if (result is Task task) await task; + // Assert + Assert.Single(selectColumn.SelectedItems); + Assert.Equal(people[0], selectColumn.SelectedItems.First()); + Assert.True(selectAllCalled); + } + + [Fact] + public async Task SelectColumnTests_KeepOnlyFirstSelectedItemAsync_WithMultipleSelectedItems_KeepsFirstOnly_SingleSticky() + { + // Arrange + var people = People.ToList(); + var selectColumn = new SelectColumn + { + SelectMode = DataGridSelectMode.SingleSticky, + SelectedItems = new List { people[0], people[1], people[2] } + }; + // Act + var method = selectColumn.GetType().GetMethod("KeepOnlyFirstSelectedItemAsync", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!; + var result = method.Invoke(selectColumn, null); + if (result is Task task) await task; + // Assert + Assert.Single(selectColumn.SelectedItems); + Assert.Equal(people[0], selectColumn.SelectedItems.First()); + } +#pragma warning restore BL0005 + /// + /// Simulate a key down event on the DataGrid row number . + /// + /// + /// + /// + /// + private async Task KeyDownOnRowAsync(IRenderedFragment cut, int row, string keyCode) + { + var item = cut.FindComponents>().ElementAt(row + 1); + var keyboardEventArgs = new KeyboardEventArgs + { + Code = keyCode, + Key = keyCode + }; + await item.Instance.HandleOnRowKeyDownAsync(item.Instance.RowId, keyboardEventArgs); + cut.FindComponent>().Render(); + } + /// /// Simulate a click on the DataGrid row number . /// diff --git a/tests/Core/Extensions/DisplayAttributeExtensionsTests.cs b/tests/Core/Extensions/DisplayAttributeExtensionsTests.cs new file mode 100644 index 0000000000..7ba7cdd7c8 --- /dev/null +++ b/tests/Core/Extensions/DisplayAttributeExtensionsTests.cs @@ -0,0 +1,95 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +using System.ComponentModel.DataAnnotations; +using System.Reflection; +using Microsoft.FluentUI.AspNetCore.Components.Extensions; +using Xunit; + +namespace Microsoft.FluentUI.AspNetCore.Components.Tests.Extensions; + +public class DisplayAttributeExtensionTests +{ + + [Fact] + public void Test_DisplayAttribute_Returns_Correct_Name() + { + var expected = "Test Name"; + var actual = typeof(TestModel).GetDisplayAttributeString(nameof(TestModel.TestProperty)); + + Assert.Equal(expected, actual); + } + + [Fact] + public void Test_DisplayAttribute_Returns_Null_When_No_Attribute() + { + var actual = typeof(TestModel).GetDisplayAttributeString(nameof(TestModel.PropertyWithoutAttr)); + + Assert.Null(actual); + } + + [Fact] + public void Test_DisplayAttribute_Returns_Null_When_PropertyInfo_Is_Null() + { + PropertyInfo? nullProperty = null; + var displayAttribute = nullProperty?.GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault() as DisplayAttribute; + Assert.Null(displayAttribute); + } + + [Fact] + public void Test_DisplayAttribute_Returns_Null_When_MetadataType_Used() + { + var metadataType = typeof(TestModel).GetCustomAttributes(typeof(MetadataTypeAttribute), false).FirstOrDefault() as MetadataTypeAttribute; + Assert.NotNull(metadataType); + var property = metadataType?.MetadataClassType.GetProperty(nameof(TestModel.TestProperty)); + var displayAttribute = property?.GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault() as DisplayAttribute; + Assert.Null(displayAttribute); + } + + [Fact] + public void Test_DisplayAttribute_Returns_Null_When_Property_Not_Found() + { + var actual = typeof(TestModel).GetDisplayAttributeString("NonExistentProperty"); + + Assert.Null(actual); + } + + [Fact] + public void Test_DisplayAttribute_Returns_Name_From_MetadataType() + { + var expected = "Metadata Display Name"; + var actual = typeof(TestModelWithMetadata).GetDisplayAttributeString(nameof(TestModelWithMetadata.PropertyWithMetadataDisplay)); + + Assert.Equal(expected, actual); + } + + [MetadataType(typeof(TestModelMetadata))] + private class TestModel + { + public static object PropertyWithoutAttr { get; internal set; } = default!; + + [Display(Name = "Test Name")] + public string? TestProperty { get; set; } + } + + private class TestModelMetadata + { + // This metadata class can contain attributes for TestModel properties + // For the test, we intentionally don't add any DisplayAttribute here + // so that the test can verify the behavior when no DisplayAttribute exists in metadata + public string? TestProperty { get; set; } + } + + [MetadataType(typeof(TestModelWithMetadataMetadata))] + private class TestModelWithMetadata + { + public string? PropertyWithMetadataDisplay { get; set; } + } + + private class TestModelWithMetadataMetadata + { + [Display(Name = "Metadata Display Name")] + public string? PropertyWithMetadataDisplay { get; set; } + } +} From f4b691a913ad4b8a5748d0ebb668645f694e87fe Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Tue, 1 Jul 2025 16:08:59 +0200 Subject: [PATCH 30/44] Update file headers --- src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs | 2 +- src/Core/Components/DataGrid/Columns/ColumnKeyGridSort.cs | 2 +- .../Components/DataGrid/Columns/ColumnOptionsUISettings.cs | 3 ++- .../Components/DataGrid/Columns/ColumnResizeOptions.razor.cs | 2 +- src/Core/Components/DataGrid/Columns/ColumnResizeUISettings.cs | 2 +- src/Core/Components/DataGrid/Columns/ColumnSortUISettings.cs | 3 ++- src/Core/Components/DataGrid/Columns/GridSort.cs | 2 +- src/Core/Components/DataGrid/Columns/IGridSort.cs | 2 +- src/Core/Components/DataGrid/Columns/PropertyColumn.cs | 2 +- src/Core/Components/DataGrid/Columns/SelectColumn.cs | 2 +- src/Core/Components/DataGrid/Columns/SortedProperty.cs | 2 +- src/Core/Components/DataGrid/Columns/TemplateColumn.cs | 2 +- src/Core/Components/DataGrid/FluentDataGrid.razor.cs | 2 +- src/Core/Components/DataGrid/FluentDataGridCell.razor.cs | 2 +- src/Core/Components/DataGrid/FluentDataGridRow.razor.cs | 2 +- src/Core/Components/DataGrid/GridItemsProvider.cs | 2 +- src/Core/Components/DataGrid/GridItemsProviderRequest.cs | 2 +- src/Core/Components/DataGrid/GridItemsProviderResult.cs | 2 +- .../DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs | 2 +- .../DataGrid/Infrastructure/ColumnsCollectedNotifier.cs | 2 +- src/Core/Components/DataGrid/Infrastructure/Defer.cs | 2 +- .../Components/DataGrid/Infrastructure/IAsyncQueryExecutor.cs | 2 +- src/Core/Components/DataGrid/Infrastructure/IBindableColumn.cs | 2 +- .../Components/DataGrid/Infrastructure/InternalGridContext.cs | 2 +- src/Core/Extensions/DisplayAttributeExtensions.cs | 2 +- tests/Core/Components/DataGrid/ColumnKeyGridSortTests.cs | 2 +- tests/Core/Components/DataGrid/GridSortTests.cs | 2 +- tests/Core/Extensions/DisplayAttributeExtensionsTests.cs | 2 +- 28 files changed, 30 insertions(+), 28 deletions(-) diff --git a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs index b0b4abba2a..48cd9ab8c0 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ using System.Diagnostics.CodeAnalysis; diff --git a/src/Core/Components/DataGrid/Columns/ColumnKeyGridSort.cs b/src/Core/Components/DataGrid/Columns/ColumnKeyGridSort.cs index 8143f88765..c2c8adc050 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnKeyGridSort.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnKeyGridSort.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ namespace Microsoft.FluentUI.AspNetCore.Components; diff --git a/src/Core/Components/DataGrid/Columns/ColumnOptionsUISettings.cs b/src/Core/Components/DataGrid/Columns/ColumnOptionsUISettings.cs index 095f65eecf..9565eeaeee 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnOptionsUISettings.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnOptionsUISettings.cs @@ -1,6 +1,7 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ + namespace Microsoft.FluentUI.AspNetCore.Components; /// diff --git a/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor.cs b/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor.cs index 86af83683f..a12a79dd28 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnResizeOptions.razor.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ using System.Globalization; diff --git a/src/Core/Components/DataGrid/Columns/ColumnResizeUISettings.cs b/src/Core/Components/DataGrid/Columns/ColumnResizeUISettings.cs index 12a913bc1d..cebdcbbf64 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnResizeUISettings.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnResizeUISettings.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ namespace Microsoft.FluentUI.AspNetCore.Components; diff --git a/src/Core/Components/DataGrid/Columns/ColumnSortUISettings.cs b/src/Core/Components/DataGrid/Columns/ColumnSortUISettings.cs index 6c11f8c43c..e31d27c088 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnSortUISettings.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnSortUISettings.cs @@ -1,6 +1,7 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ + namespace Microsoft.FluentUI.AspNetCore.Components; /// diff --git a/src/Core/Components/DataGrid/Columns/GridSort.cs b/src/Core/Components/DataGrid/Columns/GridSort.cs index 2ea81362c9..572c6ea4e2 100644 --- a/src/Core/Components/DataGrid/Columns/GridSort.cs +++ b/src/Core/Components/DataGrid/Columns/GridSort.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ using System.Diagnostics.CodeAnalysis; diff --git a/src/Core/Components/DataGrid/Columns/IGridSort.cs b/src/Core/Components/DataGrid/Columns/IGridSort.cs index cd64f3bb66..e9525489ed 100644 --- a/src/Core/Components/DataGrid/Columns/IGridSort.cs +++ b/src/Core/Components/DataGrid/Columns/IGridSort.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ namespace Microsoft.FluentUI.AspNetCore.Components; diff --git a/src/Core/Components/DataGrid/Columns/PropertyColumn.cs b/src/Core/Components/DataGrid/Columns/PropertyColumn.cs index 94a5094fe9..52c21d9d1f 100644 --- a/src/Core/Components/DataGrid/Columns/PropertyColumn.cs +++ b/src/Core/Components/DataGrid/Columns/PropertyColumn.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ using System.Linq.Expressions; diff --git a/src/Core/Components/DataGrid/Columns/SelectColumn.cs b/src/Core/Components/DataGrid/Columns/SelectColumn.cs index 7f96de7e6c..6f76fa4e29 100644 --- a/src/Core/Components/DataGrid/Columns/SelectColumn.cs +++ b/src/Core/Components/DataGrid/Columns/SelectColumn.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ using Microsoft.AspNetCore.Components; diff --git a/src/Core/Components/DataGrid/Columns/SortedProperty.cs b/src/Core/Components/DataGrid/Columns/SortedProperty.cs index 26f22078e0..a581f646be 100644 --- a/src/Core/Components/DataGrid/Columns/SortedProperty.cs +++ b/src/Core/Components/DataGrid/Columns/SortedProperty.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ namespace Microsoft.FluentUI.AspNetCore.Components; diff --git a/src/Core/Components/DataGrid/Columns/TemplateColumn.cs b/src/Core/Components/DataGrid/Columns/TemplateColumn.cs index 667060b41b..5abb3122d2 100644 --- a/src/Core/Components/DataGrid/Columns/TemplateColumn.cs +++ b/src/Core/Components/DataGrid/Columns/TemplateColumn.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ using Microsoft.AspNetCore.Components; diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index f13dd71eb7..f4deddd92b 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ using System.Diagnostics.CodeAnalysis; diff --git a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs index 2e9bc54e1e..d96a285726 100644 --- a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ using System.Globalization; diff --git a/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs b/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs index 254f5e16be..9e5318f0e2 100644 --- a/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ using System.Globalization; diff --git a/src/Core/Components/DataGrid/GridItemsProvider.cs b/src/Core/Components/DataGrid/GridItemsProvider.cs index cd866ffa54..d7376a73c3 100644 --- a/src/Core/Components/DataGrid/GridItemsProvider.cs +++ b/src/Core/Components/DataGrid/GridItemsProvider.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ namespace Microsoft.FluentUI.AspNetCore.Components; diff --git a/src/Core/Components/DataGrid/GridItemsProviderRequest.cs b/src/Core/Components/DataGrid/GridItemsProviderRequest.cs index f6c28cdd12..dbaacf38e3 100644 --- a/src/Core/Components/DataGrid/GridItemsProviderRequest.cs +++ b/src/Core/Components/DataGrid/GridItemsProviderRequest.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ using System.Diagnostics.CodeAnalysis; diff --git a/src/Core/Components/DataGrid/GridItemsProviderResult.cs b/src/Core/Components/DataGrid/GridItemsProviderResult.cs index 36887bd92d..3dfd03e3ab 100644 --- a/src/Core/Components/DataGrid/GridItemsProviderResult.cs +++ b/src/Core/Components/DataGrid/GridItemsProviderResult.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ namespace Microsoft.FluentUI.AspNetCore.Components; diff --git a/src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs b/src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs index c049b15821..ced4aa991c 100644 --- a/src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs +++ b/src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ using System.Collections.Concurrent; diff --git a/src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs b/src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs index cf437e573b..e01b29ec24 100644 --- a/src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs +++ b/src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ using System.ComponentModel; diff --git a/src/Core/Components/DataGrid/Infrastructure/Defer.cs b/src/Core/Components/DataGrid/Infrastructure/Defer.cs index 9cf7d6027b..c6500f5371 100644 --- a/src/Core/Components/DataGrid/Infrastructure/Defer.cs +++ b/src/Core/Components/DataGrid/Infrastructure/Defer.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ using System.ComponentModel; diff --git a/src/Core/Components/DataGrid/Infrastructure/IAsyncQueryExecutor.cs b/src/Core/Components/DataGrid/Infrastructure/IAsyncQueryExecutor.cs index 1fd73c1346..971c3e17c3 100644 --- a/src/Core/Components/DataGrid/Infrastructure/IAsyncQueryExecutor.cs +++ b/src/Core/Components/DataGrid/Infrastructure/IAsyncQueryExecutor.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ namespace Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; diff --git a/src/Core/Components/DataGrid/Infrastructure/IBindableColumn.cs b/src/Core/Components/DataGrid/Infrastructure/IBindableColumn.cs index a5ac7603f9..9ddfaae58a 100644 --- a/src/Core/Components/DataGrid/Infrastructure/IBindableColumn.cs +++ b/src/Core/Components/DataGrid/Infrastructure/IBindableColumn.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ using System.Linq.Expressions; diff --git a/src/Core/Components/DataGrid/Infrastructure/InternalGridContext.cs b/src/Core/Components/DataGrid/Infrastructure/InternalGridContext.cs index bfade94198..ca05956ac2 100644 --- a/src/Core/Components/DataGrid/Infrastructure/InternalGridContext.cs +++ b/src/Core/Components/DataGrid/Infrastructure/InternalGridContext.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ using System.Diagnostics.CodeAnalysis; diff --git a/src/Core/Extensions/DisplayAttributeExtensions.cs b/src/Core/Extensions/DisplayAttributeExtensions.cs index a1573146a0..f196124ef4 100644 --- a/src/Core/Extensions/DisplayAttributeExtensions.cs +++ b/src/Core/Extensions/DisplayAttributeExtensions.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ using System.ComponentModel.DataAnnotations; diff --git a/tests/Core/Components/DataGrid/ColumnKeyGridSortTests.cs b/tests/Core/Components/DataGrid/ColumnKeyGridSortTests.cs index abea2e4706..296e1e7daf 100644 --- a/tests/Core/Components/DataGrid/ColumnKeyGridSortTests.cs +++ b/tests/Core/Components/DataGrid/ColumnKeyGridSortTests.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ using Xunit; diff --git a/tests/Core/Components/DataGrid/GridSortTests.cs b/tests/Core/Components/DataGrid/GridSortTests.cs index dccaf9408d..33b6235b1d 100644 --- a/tests/Core/Components/DataGrid/GridSortTests.cs +++ b/tests/Core/Components/DataGrid/GridSortTests.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ using Xunit; diff --git a/tests/Core/Extensions/DisplayAttributeExtensionsTests.cs b/tests/Core/Extensions/DisplayAttributeExtensionsTests.cs index 7ba7cdd7c8..f637803b7f 100644 --- a/tests/Core/Extensions/DisplayAttributeExtensionsTests.cs +++ b/tests/Core/Extensions/DisplayAttributeExtensionsTests.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ using System.ComponentModel.DataAnnotations; From 1f50f072fc250553d6aa85b732806e4180b2a3c8 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Tue, 1 Jul 2025 19:41:20 +0200 Subject: [PATCH 31/44] Convert DataGrid JS to TS --- .../DataGrid/FluentDataGrid.razor.cs | 17 +- .../DataGrid/FluentDataGrid.razor.ts | 483 ++++++++++++++++++ 2 files changed, 492 insertions(+), 8 deletions(-) create mode 100644 src/Core/Components/DataGrid/FluentDataGrid.razor.ts diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index f4deddd92b..730df47b52 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -23,6 +23,7 @@ namespace Microsoft.FluentUI.AspNetCore.Components; public partial class FluentDataGrid : FluentComponentBase, IHandleEvent, IAsyncDisposable { private const string JAVASCRIPT_FILE = FluentJSModule.JAVASCRIPT_ROOT + "DataGrid/FluentDataGrid.razor.js"; + internal const string EMPTY_CONTENT_ROW_CLASS = "empty-content-row"; internal const string LOADING_CONTENT_ROW_CLASS = "loading-content-row"; @@ -452,11 +453,11 @@ protected override async Task OnAfterRenderAsync(bool firstRender) // Import the JavaScript module await JSModule.ImportJavaScriptModuleAsync(JAVASCRIPT_FILE); - _jsEventDisposable = await JSModule.ObjectReference.InvokeAsync("init", _gridReference, AutoFocus); + _jsEventDisposable = await JSModule.ObjectReference.InvokeAsync("Microsoft.FluentUI.Blazor.DataGrid.Initialize", _gridReference, AutoFocus); if (AutoItemsPerPage) { - await JSModule.ObjectReference.InvokeVoidAsync("dynamicItemsPerPage", _gridReference, DotNetObjectReference.Create(this), (int)RowSize); + await JSModule.ObjectReference.InvokeVoidAsync("Microsoft.FluentUI.Blazor.DataGrid.DynamicItemsPerPage", _gridReference, DotNetObjectReference.Create(this), (int)RowSize); } } @@ -465,18 +466,18 @@ protected override async Task OnAfterRenderAsync(bool firstRender) if (_checkColumnOptionsPosition && _displayOptionsForColumn is not null) { _checkColumnOptionsPosition = false; - await JSModule.ObjectReference.InvokeVoidAsync("checkColumnPopupPosition", _gridReference, ".col-options"); + await JSModule.ObjectReference.InvokeVoidAsync("Microsoft.FluentUI.Blazor.DataGrid.CheckColumnPopupPosition", _gridReference, ".col-options"); } if (_checkColumnResizePosition && _displayResizeForColumn is not null) { _checkColumnResizePosition = false; - await JSModule.ObjectReference.InvokeVoidAsync("checkColumnPopupPosition", _gridReference, ".col-resize"); + await JSModule.ObjectReference.InvokeVoidAsync("Microsoft.FluentUI.Blazor.DataGrid.CheckColumnPopupPosition", _gridReference, ".col-resize"); } if (AutoFit && _gridReference is not null) { - await JSModule.ObjectReference.InvokeVoidAsync("autoFitGridColumns", _gridReference, _columns.Count); + await JSModule.ObjectReference.InvokeVoidAsync("Microsoft.FluentUI.Blazor.DataGrid.AutoFitGridColumns", _gridReference, _columns.Count); } } @@ -1063,7 +1064,7 @@ public async Task SetColumnWidthDiscreteAsync(int? columnIndex, float widthChang { if (_gridReference is not null && JSModule is not null) { - await JSModule.ObjectReference.InvokeVoidAsync("resizeColumnDiscrete", _gridReference, columnIndex, widthChange); + await JSModule.ObjectReference.InvokeVoidAsync("Microsoft.FluentUI.Blazor.DataGrid.ResizeColumnDiscrete", _gridReference, columnIndex, widthChange); } } @@ -1077,7 +1078,7 @@ public async Task SetColumnWidthExactAsync(int columnIndex, int width) { if (_gridReference is not null && JSModule is not null) { - await JSModule.ObjectReference.InvokeVoidAsync("resizeColumnExact", _gridReference, columnIndex, width); + await JSModule.ObjectReference.InvokeVoidAsync("Microsoft.FluentUI.Blazor.DataGrid.ResizeColumnExact", _gridReference, columnIndex, width); } } @@ -1090,7 +1091,7 @@ public async Task ResetColumnWidthsAsync() { if (_gridReference is not null && JSModule is not null) { - await JSModule.ObjectReference.InvokeVoidAsync("resetColumnWidths", _gridReference); + await JSModule.ObjectReference.InvokeVoidAsync("Microsoft.FluentUI.Blazor.DataGrid.ResetColumnWidths", _gridReference); } } } diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.ts b/src/Core/Components/DataGrid/FluentDataGrid.razor.ts new file mode 100644 index 0000000000..eef20e3ce2 --- /dev/null +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.ts @@ -0,0 +1,483 @@ +export namespace Microsoft.FluentUI.Blazor.DataGrid { + + interface Grid { + id: string; + columns: any[]; // or a more specific type if you have one + initialWidths: string; + } + + interface Column { + header: Element; + size: string; + } + + // Use a dictionary for grids for id-based access + let grids: { [id: string]: Grid } = {}; + const minWidth = 100; + + export function Initialize(gridElement: HTMLElement, autoFocus: boolean) { + if (gridElement === undefined || gridElement === null) { + return; + } + + EnableColumnResizing(gridElement); + + let start = gridElement.querySelector('td:first-child') as HTMLElement | null; + + if (autoFocus && start) { + start.focus(); + } + + const bodyClickHandler = (event: MouseEvent) => { + const columnOptionsElement = gridElement?.querySelector('.col-options'); + if (columnOptionsElement && event.composedPath().indexOf(columnOptionsElement) < 0) { + gridElement.dispatchEvent(new CustomEvent('closecolumnoptions', { bubbles: true })); + } + const columnResizeElement = gridElement?.querySelector('.col-resize'); + if (columnResizeElement && event.composedPath().indexOf(columnResizeElement) < 0) { + gridElement.dispatchEvent(new CustomEvent('closecolumnresize', { bubbles: true })); + } + }; + const keyboardNavigation = (sibling: HTMLElement | null) => { + if (sibling !== null) { + if (start) start.focus(); + sibling.focus(); + start = sibling; + } + } + const keyDownHandler = (event: KeyboardEvent) => { + if (document.activeElement?.tagName.toLowerCase() != 'table' && document.activeElement?.tagName.toLowerCase() != 'td' && document.activeElement?.tagName.toLowerCase() != 'th') { + return; + } + const columnOptionsElement = gridElement?.querySelector('.col-options'); + if (columnOptionsElement) { + if (event.key === "Escape") { + gridElement.dispatchEvent(new CustomEvent('closecolumnoptions', { bubbles: true })); + gridElement.focus(); + } + columnOptionsElement.addEventListener( + "keydown", + function (ev) { + const kEvent = ev as KeyboardEvent; + if (kEvent.key === "ArrowRight" || kEvent.key === "ArrowLeft" || kEvent.key === "ArrowDown" || kEvent.key === "ArrowUp") { + kEvent.stopPropagation(); + } + } + ); + } + const columnResizeElement = gridElement?.querySelector('.col-resize'); + if (columnResizeElement) { + if (event.key === "Escape") { + gridElement.dispatchEvent(new CustomEvent('closecolumnresize', { bubbles: true })); + gridElement.focus(); + } + columnResizeElement.addEventListener( + "keydown", + function (ev) { + const kEvent = ev as KeyboardEvent; + if (kEvent.key === "ArrowRight" || kEvent.key === "ArrowLeft" || kEvent.key === "ArrowDown" || kEvent.key === "ArrowUp") { + kEvent.stopPropagation(); + } + } + ); + } + + // check if start is a child of gridElement + if (start !== null && (gridElement.contains(start) || gridElement === start) && document.activeElement === start && document.activeElement.tagName.toLowerCase() !== 'fluent-text-field' && document.activeElement.tagName.toLowerCase() !== 'fluent-menu-item') { + const idx = (start as HTMLTableCellElement).cellIndex; + + if (event.key === "ArrowUp") { + // up arrow + const previousRow = start.parentElement?.previousElementSibling as HTMLTableRowElement | null; + if (previousRow !== null) { + event.preventDefault(); + const previousSibling = previousRow.cells[idx]; + keyboardNavigation(previousSibling); + } + } else if (event.key === "ArrowDown") { + // down arrow + const nextRow = start.parentElement?.nextElementSibling as HTMLTableRowElement | null; + if (nextRow !== null) { + event.preventDefault(); + const nextSibling = nextRow.cells[idx]; + keyboardNavigation(nextSibling); + } + } else if (event.key === "ArrowLeft") { + // left arrow + event.preventDefault(); + const previousSibling = (document.body.dir === '' || document.body.dir === 'ltr') ? start.previousElementSibling as HTMLElement : start.nextElementSibling as HTMLElement; + keyboardNavigation(previousSibling); + event.stopPropagation(); + } else if (event.key === "ArrowRight") { + // right arrow + event.preventDefault(); + const nextsibling = (document.body.dir === '' || document.body.dir === 'ltr') ? start.nextElementSibling as HTMLElement : start.previousElementSibling as HTMLElement; + keyboardNavigation(nextsibling); + event.stopPropagation(); + } + } + else { + start = document.activeElement as HTMLElement; + } + + }; + + const cells = gridElement.querySelectorAll('[role="gridcell"]'); + cells.forEach((cell: any) => { + cell.columnDefinition = { + columnDataKey: "", + cellInternalFocusQueue: true, + cellFocusTargetCallback: (cell: HTMLElement) => { + return cell.children[0]; + } + } + cell.addEventListener( + "keydown", + (event: KeyboardEvent) => { + if ((event.target as HTMLElement).getAttribute('role') !== "gridcell" && (event.key === "ArrowRight" || event.key === "ArrowLeft")) { + event.stopPropagation(); + } + } + ); + }); + + document.body.addEventListener('click', bodyClickHandler); + document.body.addEventListener('mousedown', bodyClickHandler); // Otherwise it seems strange that it doesn't go away until you release the mouse button + gridElement.addEventListener('keydown', keyDownHandler); + + return { + stop: () => { + document.body.removeEventListener('click', bodyClickHandler); + document.body.removeEventListener('mousedown', bodyClickHandler); + gridElement.removeEventListener('keydown', keyDownHandler); + delete grids[gridElement.id]; + } + }; + } + + export function CheckColumnPopupPosition(gridElement: HTMLElement, selector: string) { + const colPopup = gridElement.querySelector(selector) as HTMLElement | null; + if (colPopup) { + const gridRect = gridElement.getBoundingClientRect(); + const popupRect = colPopup.getBoundingClientRect(); + const leftOverhang = Math.max(0, gridRect.left - popupRect.left); + const rightOverhang = Math.max(0, popupRect.right - gridRect.right); + if (leftOverhang || rightOverhang) { + const applyOffset = leftOverhang && rightOverhang ? (leftOverhang - rightOverhang) / 2 : (leftOverhang - rightOverhang); + colPopup.style.transform = `translateX(${applyOffset}px)`; + } + + colPopup.style.visibility = 'visible'; + (colPopup as any).scrollIntoViewIfNeeded?.(); + + const autoFocusElem = colPopup.querySelector('[autofocus]') as HTMLElement | null; + if (autoFocusElem) { + autoFocusElem.focus(); + } + } + } + + export function EnableColumnResizing(gridElement: HTMLElement, resizeColumnOnAllRows: boolean = true) { + const columns: Column[] = []; + const headers = gridElement.querySelectorAll('.column-header.resizable'); + + if (headers.length === 0) { + return; + } + + const isRTL = getComputedStyle(gridElement).direction === 'rtl'; + const isGrid = gridElement.classList.contains('grid'); + + let tableHeight = gridElement.offsetHeight; + // rows have not been loaded yet, so we need to calculate the height + if (tableHeight < 70) { + // by getting the aria rowcount attribute + const rowCount = gridElement.getAttribute('aria-rowcount'); + if (rowCount) { + const rowHeight = (gridElement.querySelector('thead tr th') as HTMLElement)?.offsetHeight; + // and multiply by the itemsize (== height of the header cells) + tableHeight = Number(rowCount) * rowHeight; + } + } + + // Determine the height based on the resizeColumnOnAllRows parameter + let resizeHandleHeight = tableHeight; + if (!resizeColumnOnAllRows) { + // Only use the header height when resizeColumnOnAllRows is false + // Use the first header's height if available + resizeHandleHeight = headers.length > 0 ? ((headers[0] as HTMLElement).offsetHeight - 14) : 30; // fallback to 30px if no headers + } + + headers.forEach((header) => { + columns.push({ + header, + size: `${(header as HTMLElement).clientWidth}px`, + }); + + // remove any previously created divs + const resizedivs = header.querySelectorAll('.actual-resize-handle'); + resizedivs.forEach(div => div.remove()); + + // add a new resize div + const div = createDiv(resizeHandleHeight, isRTL); + header.appendChild(div); + setListeners(div, isRTL); + }); + + let initialWidths: string; + if ((gridElement.style as any).gridTemplateColumns) { + initialWidths = gridElement.style.gridTemplateColumns; + } else { + initialWidths = columns.map(({ size }) => size).join(' '); + + if (isGrid) { + gridElement.style.gridTemplateColumns = initialWidths; + } + } + + const id = gridElement.id; + grids[id] = { + id, + columns, + initialWidths, + }; + + function setListeners(div: HTMLElement, isRTL: boolean) { + let pageX: number | undefined, curCol: HTMLElement | undefined, curColWidth: number | undefined; + + div.addEventListener('pointerdown', function (e: PointerEvent) { + curCol = (e.target as HTMLElement).parentElement as HTMLElement; + pageX = e.pageX; + + const padding = paddingDiff(curCol); + + curColWidth = curCol.offsetWidth - padding; + }); + + div.addEventListener('pointerover', function (e: MouseEvent) { + (e.target as HTMLElement).style.borderInlineEnd = 'var(--fluent-data-grid-resize-handle-width) solid var(--fluent-data-grid-resize-handle-color)'; + if ((e.target as HTMLElement).previousElementSibling) { + ((e.target as HTMLElement).previousElementSibling as HTMLElement).style.visibility = 'hidden'; + } + }); + + div.addEventListener('pointerup', removeBorder); + div.addEventListener('pointercancel', removeBorder); + div.addEventListener('pointerleave', removeBorder); + + document.addEventListener('pointermove', (e: PointerEvent) => + requestAnimationFrame(() => { + gridElement.style.tableLayout = 'fixed'; + + if (curCol) { + const diffX = isRTL ? (pageX! - e.pageX) : (e.pageX - pageX!); + const column: Column = columns.find(({ header }) => header === curCol)!; + + column.size = parseInt(Math.max(minWidth, curColWidth! + diffX) as any, 10) + 'px'; + + columns.forEach((col) => { + if (col.size.startsWith('minmax')) { + col.size = col.header.clientWidth + 'px'; + } + }); + + if (isGrid) { + gridElement.style.gridTemplateColumns = columns + .map(({ size }) => size) + .join(' '); + } + else { + curCol.style.width = column.size; + } + } + }) + ); + + document.addEventListener('pointerup', function () { + curCol = undefined; + curColWidth = undefined; + pageX = undefined; + }); + } + + function createDiv(height: number, isRTL: boolean) { + const div = document.createElement('div'); + div.className = 'actual-resize-handle'; + div.style.top = '5px'; + div.style.position = 'absolute'; + div.style.cursor = 'col-resize'; + div.style.userSelect = 'none'; + div.style.height = height + 'px'; + div.style.width = '6px'; + div.style.opacity = 'var(--fluent-data-grid-header-opacity)'; + + if (isRTL) { + div.style.left = '0px'; + div.style.right = 'unset'; + } else { + div.style.left = 'unset'; + div.style.right = '0px'; + } + return div; + } + + function paddingDiff(col: HTMLElement) { + if (getStyleVal(col, 'box-sizing') === 'border-box') { + return 0; + } + + const padLeft = getStyleVal(col, 'padding-left'); + const padRight = getStyleVal(col, 'padding-right'); + return parseInt(padLeft) + parseInt(padRight); + } + + function getStyleVal(elm: HTMLElement, css: string) { + return window.getComputedStyle(elm, null).getPropertyValue(css); + } + + function removeBorder(e: Event) { + (e.target as HTMLElement).style.borderInlineEnd = ''; + if ((e.target as HTMLElement).previousElementSibling) { + ((e.target as HTMLElement).previousElementSibling as HTMLElement).style.visibility = 'visible'; + } + } + } + + export function ResetColumnWidths(gridElement: HTMLElement) { + const isGrid = gridElement.classList.contains('grid'); + const grid = grids[gridElement.id]; + if (!grid) { + return; + } + + const columnsWidths = grid.initialWidths.split(' '); + + grid.columns.forEach((column: any, index: number) => { + if (isGrid) { + column.size = columnsWidths[index]; + } else { + column.header.style.width = columnsWidths[index]; + } + }); + + if (isGrid) { + gridElement.style.gridTemplateColumns = grid.initialWidths; + } + gridElement.dispatchEvent( + new CustomEvent('closecolumnresize', { bubbles: true }) + ); + gridElement.focus(); + } + + export function ResizeColumnDiscrete(gridElement: HTMLElement, column: string | undefined, change: number) { + const columns: any[] = []; + let headerBeingResized: HTMLElement | null | undefined; + + if (!column) { + const targetElement = (document.activeElement as HTMLElement)?.parentElement?.parentElement?.parentElement?.parentElement; + if (!(targetElement && targetElement.classList.contains('column-header') && targetElement.classList.contains('resizable'))) { + return; + } + headerBeingResized = targetElement; + } + else { + headerBeingResized = gridElement.querySelector('.column-header[col-index="' + column + '"]') as HTMLElement | null; + } + + grids[gridElement.id].columns.forEach((column: any) => { + if (column.header === headerBeingResized) { + const width = headerBeingResized!.getBoundingClientRect().width + change; + + if (change < 0) { + column.size = Math.max(minWidth, width) + 'px'; + } + else { + column.size = width + 'px'; + } + } + else { + if (column.size.startsWith('minmax')) { + column.size = parseInt(column.header.clientWidth, 10) + 'px'; + } + } + columns.push(column.size); + }); + + gridElement.style.gridTemplateColumns = columns.join(' '); + } + + export function ResizeColumnExact(gridElement: HTMLElement, column: string, width: number) { + const columns: any[] = []; + let headerBeingResized = gridElement.querySelector('.column-header[col-index="' + column + '"]') as HTMLElement | null; + + if (!headerBeingResized) { + return; + } + + grids[gridElement.id].columns.forEach((column: any) => { + if (column.header === headerBeingResized) { + column.size = Math.max(minWidth, width) + 'px'; + } + else { + if (column.size.startsWith('minmax')) { + column.size = parseInt(column.header.clientWidth, 10) + 'px'; + } + } + columns.push(column.size); + }); + + gridElement.style.gridTemplateColumns = columns.join(' '); + + gridElement.dispatchEvent(new CustomEvent('closecolumnresize', { bubbles: true })); + gridElement.focus(); + } + + export function AutoFitGridColumns(gridElement: HTMLElement, columnCount: number) { + let gridTemplateColumns = ''; + + for (let i = 0; i < columnCount; i++) { + const columnWidths = Array + .from(gridElement.querySelectorAll(`[col-index="${i + 1}"]`)) + .flatMap((x: any) => x.offsetWidth); + + const maxColumnWidth = Math.max(...columnWidths); + + gridTemplateColumns += ` ${maxColumnWidth}px`; + } + + gridElement.style.gridTemplateColumns = gridTemplateColumns; + gridElement.classList.remove('auto-fit'); + + grids[gridElement.id].initialWidths = gridTemplateColumns; + } + + export function DynamicItemsPerPage(gridElement: HTMLElement, dotNetObject: any, rowSize: number) { + const observer = new ResizeObserver(() => { + const visibleRows = calculateVisibleRows(gridElement, rowSize) + dotNetObject.invokeMethodAsync('UpdateItemsPerPageAsync', visibleRows) + .catch((err: any) => console.error('Error invoking Blazor method:', err)); + }); + + const targetElement = gridElement.parentElement; + if (targetElement) { + observer.observe(targetElement); + } + } + + function calculateVisibleRows(gridElement: HTMLElement, rowHeight: number) { + if (rowHeight <= 0) { + return 0; + } + + const gridContainer = gridElement.parentElement; + + if (!gridContainer) { + return 0; + } + + const availableHeight = gridContainer?.clientHeight || window.visualViewport?.height || window.innerHeight; + + const visibleRows = Math.max(Math.floor(availableHeight / rowHeight), 1); + return visibleRows; + } +} From 097db2f3fa6763424e316d2f68e8f8c85c4775a7 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Tue, 1 Jul 2025 19:44:25 +0200 Subject: [PATCH 32/44] Implement fix for #3963 --- src/Core/Components/DataGrid/FluentDataGrid.razor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.ts b/src/Core/Components/DataGrid/FluentDataGrid.razor.ts index eef20e3ce2..7df38a59dc 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.ts +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.ts @@ -307,7 +307,7 @@ export namespace Microsoft.FluentUI.Blazor.DataGrid { div.style.position = 'absolute'; div.style.cursor = 'col-resize'; div.style.userSelect = 'none'; - div.style.height = height + 'px'; + div.style.height = (height - 5) + 'px'; // adjust for the top offset div.style.width = '6px'; div.style.opacity = 'var(--fluent-data-grid-header-opacity)'; From 713d96b7ec10e4c815a3f138ae1c87e4b65e9f9c Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Tue, 1 Jul 2025 20:20:26 +0200 Subject: [PATCH 33/44] Bring in changes made in v4 with #3949 --- .../Columns/ColumnOptionsUISettings.cs | 3 +- .../DataGrid/Columns/SelectColumn.cs | 38 ++++++++++++++++++- .../DataGrid/Columns/TemplateColumn.cs | 2 +- .../DataGrid/FluentDataGridCell.razor.cs | 2 +- .../Components/DataGrid/GridItemsProvider.cs | 2 +- .../DataGrid/GridItemsProviderRequest.cs | 2 +- .../DataGrid/GridItemsProviderResult.cs | 2 +- .../AsyncQueryExecutorSupplier.cs | 2 +- .../ColumnsCollectedNotifier.cs | 2 +- .../DataGrid/Infrastructure/Defer.cs | 2 +- .../Infrastructure/IBindableColumn.cs | 2 +- .../Infrastructure/InternalGridContext.cs | 10 ++--- .../Extensions/DisplayAttributeExtensions.cs | 2 +- 13 files changed, 50 insertions(+), 21 deletions(-) diff --git a/src/Core/Components/DataGrid/Columns/ColumnOptionsUISettings.cs b/src/Core/Components/DataGrid/Columns/ColumnOptionsUISettings.cs index 9565eeaeee..095f65eecf 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnOptionsUISettings.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnOptionsUISettings.cs @@ -1,7 +1,6 @@ // ------------------------------------------------------------------------ -// This file is licensed to you under the MIT License. +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------------------ - namespace Microsoft.FluentUI.AspNetCore.Components; /// diff --git a/src/Core/Components/DataGrid/Columns/SelectColumn.cs b/src/Core/Components/DataGrid/Columns/SelectColumn.cs index 6f76fa4e29..5e6b981298 100644 --- a/src/Core/Components/DataGrid/Columns/SelectColumn.cs +++ b/src/Core/Components/DataGrid/Columns/SelectColumn.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.Web; +using Microsoft.FluentUI.AspNetCore.Components.Infrastructure; namespace Microsoft.FluentUI.AspNetCore.Components; @@ -12,7 +13,7 @@ namespace Microsoft.FluentUI.AspNetCore.Components; /// Represents a column whose cells render a selected checkbox updated when the user click on a row. /// /// The type of data represented by each row in the grid. -public class SelectColumn : ColumnBase +public class SelectColumn : ColumnBase, IDisposable { /// /// List of keys to press, to select/unselect a row. @@ -28,6 +29,8 @@ public class SelectColumn : ColumnBase private DataGridSelectMode _selectMode = DataGridSelectMode.Single; private readonly List _selectedItems = []; + private readonly EventCallbackSubscriber _itemsChanged; + /// /// Initializes a new instance of . /// @@ -35,6 +38,15 @@ public SelectColumn() { Width = "50px"; ChildContent = GetDefaultChildContent(); + + _itemsChanged = new(EventCallback.Factory.Create(this, UpdateSelectedItemsAsync)); + } + + /// + protected override void OnInitialized() + { + _itemsChanged.SubscribeOrMove(InternalGridContext.ItemsChanged); + base.OnInitialized(); } /// @@ -200,7 +212,7 @@ public DataGridSelectMode SelectMode /// [Parameter] - public override IGridSort? SortBy { get; set; } = null; + public override IGridSort? SortBy { get; set; } /// /// Allows to clear the selection. @@ -334,6 +346,21 @@ Task CallOnSelectAsync(TGridItem item, bool isSelected) } } + private async Task UpdateSelectedItemsAsync() + { + + if (!SelectedItems.Any() || InternalGridContext == null || InternalGridContext.Items == null) + { + return; + } + + var itemsToRemove = _selectedItems.Where(item => !InternalGridContext.Items.Contains(item)).ToList(); + foreach (var item in itemsToRemove) + { + await AddOrRemoveSelectedItemAsync(item); + } + } + private Icon GetIcon(bool? selected) { if (selected == true) @@ -563,6 +590,13 @@ internal async Task OnKeyAllAsync(KeyboardEventArgs e) await OnClickAllAsync(new MouseEventArgs()); } } + + /// + public void Dispose() + { + _itemsChanged.Dispose(); + + } } /// diff --git a/src/Core/Components/DataGrid/Columns/TemplateColumn.cs b/src/Core/Components/DataGrid/Columns/TemplateColumn.cs index 5abb3122d2..667060b41b 100644 --- a/src/Core/Components/DataGrid/Columns/TemplateColumn.cs +++ b/src/Core/Components/DataGrid/Columns/TemplateColumn.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// This file is licensed to you under the MIT License. +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------------------ using Microsoft.AspNetCore.Components; diff --git a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs index d96a285726..2e9bc54e1e 100644 --- a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// This file is licensed to you under the MIT License. +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------------------ using System.Globalization; diff --git a/src/Core/Components/DataGrid/GridItemsProvider.cs b/src/Core/Components/DataGrid/GridItemsProvider.cs index d7376a73c3..cd866ffa54 100644 --- a/src/Core/Components/DataGrid/GridItemsProvider.cs +++ b/src/Core/Components/DataGrid/GridItemsProvider.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// This file is licensed to you under the MIT License. +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------------------ namespace Microsoft.FluentUI.AspNetCore.Components; diff --git a/src/Core/Components/DataGrid/GridItemsProviderRequest.cs b/src/Core/Components/DataGrid/GridItemsProviderRequest.cs index dbaacf38e3..f6c28cdd12 100644 --- a/src/Core/Components/DataGrid/GridItemsProviderRequest.cs +++ b/src/Core/Components/DataGrid/GridItemsProviderRequest.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// This file is licensed to you under the MIT License. +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------------------ using System.Diagnostics.CodeAnalysis; diff --git a/src/Core/Components/DataGrid/GridItemsProviderResult.cs b/src/Core/Components/DataGrid/GridItemsProviderResult.cs index 3dfd03e3ab..36887bd92d 100644 --- a/src/Core/Components/DataGrid/GridItemsProviderResult.cs +++ b/src/Core/Components/DataGrid/GridItemsProviderResult.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// This file is licensed to you under the MIT License. +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------------------ namespace Microsoft.FluentUI.AspNetCore.Components; diff --git a/src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs b/src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs index ced4aa991c..c049b15821 100644 --- a/src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs +++ b/src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// This file is licensed to you under the MIT License. +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------------------ using System.Collections.Concurrent; diff --git a/src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs b/src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs index e01b29ec24..cf437e573b 100644 --- a/src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs +++ b/src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// This file is licensed to you under the MIT License. +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------------------ using System.ComponentModel; diff --git a/src/Core/Components/DataGrid/Infrastructure/Defer.cs b/src/Core/Components/DataGrid/Infrastructure/Defer.cs index c6500f5371..9cf7d6027b 100644 --- a/src/Core/Components/DataGrid/Infrastructure/Defer.cs +++ b/src/Core/Components/DataGrid/Infrastructure/Defer.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// This file is licensed to you under the MIT License. +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------------------ using System.ComponentModel; diff --git a/src/Core/Components/DataGrid/Infrastructure/IBindableColumn.cs b/src/Core/Components/DataGrid/Infrastructure/IBindableColumn.cs index 9ddfaae58a..a5ac7603f9 100644 --- a/src/Core/Components/DataGrid/Infrastructure/IBindableColumn.cs +++ b/src/Core/Components/DataGrid/Infrastructure/IBindableColumn.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// This file is licensed to you under the MIT License. +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------------------ using System.Linq.Expressions; diff --git a/src/Core/Components/DataGrid/Infrastructure/InternalGridContext.cs b/src/Core/Components/DataGrid/Infrastructure/InternalGridContext.cs index ca05956ac2..2d737d4c20 100644 --- a/src/Core/Components/DataGrid/Infrastructure/InternalGridContext.cs +++ b/src/Core/Components/DataGrid/Infrastructure/InternalGridContext.cs @@ -9,7 +9,7 @@ namespace Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; // The grid cascades this so that descendant columns can talk back to it. It's an internal type // so that it doesn't show up by mistake in unrelated components. -internal sealed class InternalGridContext +internal sealed class InternalGridContext(FluentDataGrid grid) { private int _index; private int _rowId; @@ -26,13 +26,9 @@ internal sealed class InternalGridContext [ExcludeFromCodeCoverage(Justification = "This can only be set when a Virtualized grid is scrolled which can't be done by bUnit")] public int TotalViewItemCount { get; set; } - public FluentDataGrid Grid { get; } + public FluentDataGrid Grid { get; } = grid; public EventCallbackSubscribable ColumnsFirstCollected { get; } = new(); - - public InternalGridContext(FluentDataGrid grid) - { - Grid = grid; - } + public EventCallbackSubscribable ItemsChanged { get; } = new(); public int GetNextRowId() { diff --git a/src/Core/Extensions/DisplayAttributeExtensions.cs b/src/Core/Extensions/DisplayAttributeExtensions.cs index f196124ef4..a1573146a0 100644 --- a/src/Core/Extensions/DisplayAttributeExtensions.cs +++ b/src/Core/Extensions/DisplayAttributeExtensions.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// This file is licensed to you under the MIT License. +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------------------ using System.ComponentModel.DataAnnotations; From 2698837c2e7c79cd850a59d5ba57359877e2c8c8 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Tue, 1 Jul 2025 21:07:18 +0200 Subject: [PATCH 34/44] - Update file headers - Add more tests --- .../Columns/ColumnOptionsUISettings.cs | 3 +- .../DataGrid/Columns/SelectColumn.cs | 2 +- .../DataGrid/Columns/TemplateColumn.cs | 2 +- .../DataGrid/FluentDataGridCell.razor.cs | 2 +- .../Components/DataGrid/GridItemsProvider.cs | 2 +- .../DataGrid/GridItemsProviderRequest.cs | 2 +- .../DataGrid/GridItemsProviderResult.cs | 2 +- .../AsyncQueryExecutorSupplier.cs | 2 +- .../ColumnsCollectedNotifier.cs | 2 +- .../DataGrid/Infrastructure/Defer.cs | 2 +- .../Infrastructure/IBindableColumn.cs | 2 +- .../Extensions/DisplayAttributeExtensions.cs | 2 +- .../DataGrid/SelectColumnTests.razor | 157 +++++++++++++++++- 13 files changed, 169 insertions(+), 13 deletions(-) diff --git a/src/Core/Components/DataGrid/Columns/ColumnOptionsUISettings.cs b/src/Core/Components/DataGrid/Columns/ColumnOptionsUISettings.cs index 095f65eecf..9565eeaeee 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnOptionsUISettings.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnOptionsUISettings.cs @@ -1,6 +1,7 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ + namespace Microsoft.FluentUI.AspNetCore.Components; /// diff --git a/src/Core/Components/DataGrid/Columns/SelectColumn.cs b/src/Core/Components/DataGrid/Columns/SelectColumn.cs index 5e6b981298..ab07256269 100644 --- a/src/Core/Components/DataGrid/Columns/SelectColumn.cs +++ b/src/Core/Components/DataGrid/Columns/SelectColumn.cs @@ -212,7 +212,7 @@ public DataGridSelectMode SelectMode /// [Parameter] - public override IGridSort? SortBy { get; set; } + public override IGridSort? SortBy { get; set; } = null; /// /// Allows to clear the selection. diff --git a/src/Core/Components/DataGrid/Columns/TemplateColumn.cs b/src/Core/Components/DataGrid/Columns/TemplateColumn.cs index 667060b41b..5abb3122d2 100644 --- a/src/Core/Components/DataGrid/Columns/TemplateColumn.cs +++ b/src/Core/Components/DataGrid/Columns/TemplateColumn.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ using Microsoft.AspNetCore.Components; diff --git a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs index 2e9bc54e1e..d96a285726 100644 --- a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ using System.Globalization; diff --git a/src/Core/Components/DataGrid/GridItemsProvider.cs b/src/Core/Components/DataGrid/GridItemsProvider.cs index cd866ffa54..d7376a73c3 100644 --- a/src/Core/Components/DataGrid/GridItemsProvider.cs +++ b/src/Core/Components/DataGrid/GridItemsProvider.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ namespace Microsoft.FluentUI.AspNetCore.Components; diff --git a/src/Core/Components/DataGrid/GridItemsProviderRequest.cs b/src/Core/Components/DataGrid/GridItemsProviderRequest.cs index f6c28cdd12..dbaacf38e3 100644 --- a/src/Core/Components/DataGrid/GridItemsProviderRequest.cs +++ b/src/Core/Components/DataGrid/GridItemsProviderRequest.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ using System.Diagnostics.CodeAnalysis; diff --git a/src/Core/Components/DataGrid/GridItemsProviderResult.cs b/src/Core/Components/DataGrid/GridItemsProviderResult.cs index 36887bd92d..3dfd03e3ab 100644 --- a/src/Core/Components/DataGrid/GridItemsProviderResult.cs +++ b/src/Core/Components/DataGrid/GridItemsProviderResult.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ namespace Microsoft.FluentUI.AspNetCore.Components; diff --git a/src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs b/src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs index c049b15821..ced4aa991c 100644 --- a/src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs +++ b/src/Core/Components/DataGrid/Infrastructure/AsyncQueryExecutorSupplier.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ using System.Collections.Concurrent; diff --git a/src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs b/src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs index cf437e573b..e01b29ec24 100644 --- a/src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs +++ b/src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ using System.ComponentModel; diff --git a/src/Core/Components/DataGrid/Infrastructure/Defer.cs b/src/Core/Components/DataGrid/Infrastructure/Defer.cs index 9cf7d6027b..c6500f5371 100644 --- a/src/Core/Components/DataGrid/Infrastructure/Defer.cs +++ b/src/Core/Components/DataGrid/Infrastructure/Defer.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ using System.ComponentModel; diff --git a/src/Core/Components/DataGrid/Infrastructure/IBindableColumn.cs b/src/Core/Components/DataGrid/Infrastructure/IBindableColumn.cs index a5ac7603f9..9ddfaae58a 100644 --- a/src/Core/Components/DataGrid/Infrastructure/IBindableColumn.cs +++ b/src/Core/Components/DataGrid/Infrastructure/IBindableColumn.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ using System.Linq.Expressions; diff --git a/src/Core/Extensions/DisplayAttributeExtensions.cs b/src/Core/Extensions/DisplayAttributeExtensions.cs index a1573146a0..f196124ef4 100644 --- a/src/Core/Extensions/DisplayAttributeExtensions.cs +++ b/src/Core/Extensions/DisplayAttributeExtensions.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------ -// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// This file is licensed to you under the MIT License. // ------------------------------------------------------------------------ using System.ComponentModel.DataAnnotations; diff --git a/tests/Core/Components/DataGrid/SelectColumnTests.razor b/tests/Core/Components/DataGrid/SelectColumnTests.razor index 57cb6ad5ea..08e30c8627 100644 --- a/tests/Core/Components/DataGrid/SelectColumnTests.razor +++ b/tests/Core/Components/DataGrid/SelectColumnTests.razor @@ -1,4 +1,5 @@ -@using Xunit; +@using Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure +@using Xunit; @using Microsoft.AspNetCore.Components; @inherits Bunit.TestContext @@ -915,6 +916,50 @@ Assert.Equal(2, SelectedItems.First().PersonId); } + [Fact] + public void SelectColumnTests_MultiSelect_KeyboardSelection_All() + { + IEnumerable SelectedItems = new List(); + + // Arrange + var cut = Render( + @ + + + + ); + + // Pre-Assert + Assert.Empty(cut.FindAll("svg[row-selected]")); + Assert.Empty(SelectedItems); + + // Act - Press Enter key on Row 0 - Selects all items + var item = cut.FindComponents>().ElementAt(0); + var keyboardEventArgs = new KeyboardEventArgs + { + Code = "Enter", + Key = "Enter" + }; + + item.Find("svg").KeyDown(keyboardEventArgs); + + Assert.Equal(3, SelectedItems.Count()); + + // Act - Press NumpadEnter key on Row 0 - Deselects all items + keyboardEventArgs = new KeyboardEventArgs + { + Code = "NumpadEnter", + Key = "NumpadEnter" + }; + + item.Find("svg").KeyDown(keyboardEventArgs); + + Assert.Empty(SelectedItems); + + } + [Fact] public async Task SelectColumnTests_KeyboardSelection_NotSelectFromEntireRow() { @@ -1090,6 +1135,116 @@ Assert.Single(selectColumn.SelectedItems); Assert.Equal(people[0], selectColumn.SelectedItems.First()); } + + [Fact] + public async Task UpdateSelectedItemsAsync_RemovesDeselectedItems() + { + // Arrange + var people = People.ToList(); + var selectColumn = new SelectColumn + { + SelectedItems = new List { people[0], people[1], people[2] } + }; + + // Simulate InternalGridContext with only people[0] and people[2] + var context = new InternalGridContext(null!); + typeof(SelectColumn) + .GetProperty("InternalGridContext", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public) + ?.SetValue(selectColumn, context); + typeof(InternalGridContext) + .GetProperty("Items", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public) + ?.SetValue(context, new List { people[0], people[2] }); + + // Act + var method = typeof(SelectColumn).GetMethod("UpdateSelectedItemsAsync", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!; + var result = method.Invoke(selectColumn, null); + if (result is Task task) await task; + + // Assert + Assert.Equal(2, selectColumn.SelectedItems.Count()); + Assert.Contains(people[0], selectColumn.SelectedItems); + Assert.Contains(people[2], selectColumn.SelectedItems); + Assert.DoesNotContain(people[1], selectColumn.SelectedItems); + } + + [Fact] + public async Task UpdateSelectedItemsAsync_DoesNothing_WhenNoSelectedItems() + { + // Arrange + var selectColumn = new SelectColumn + { + SelectedItems = new List() + }; + + // Simulate InternalGridContext with some items + var context = new InternalGridContext(null!); + typeof(SelectColumn) + .GetProperty("InternalGridContext", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public) + ?.SetValue(selectColumn, context); + typeof(InternalGridContext) + .GetProperty("Items", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public) + ?.SetValue(context, People.ToList()); + + // Act + var method = typeof(SelectColumn).GetMethod("UpdateSelectedItemsAsync", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!; + var result = method.Invoke(selectColumn, null); + if (result is Task task) await task; + + // Assert + Assert.Empty(selectColumn.SelectedItems); + } + + [Fact] + public async Task UpdateSelectedItemsAsync_DoesNothing_WhenInternalGridContextIsNull() + { + // Arrange + var people = People.ToList(); + var selectColumn = new SelectColumn + { + SelectedItems = new List { people[0] } + }; + + // InternalGridContext is null by default + + // Act + var method = typeof(SelectColumn).GetMethod("UpdateSelectedItemsAsync", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!; + var result = method.Invoke(selectColumn, null); + if (result is Task task) await task; + + // Assert + Assert.Single(selectColumn.SelectedItems); + Assert.Equal(people[0], selectColumn.SelectedItems.First()); + } + + [Fact] + public async Task UpdateSelectedItemsAsync_DoesNothing_WhenInternalGridContextItemsIsNull() + { + // Arrange + var people = People.ToList(); + var selectColumn = new SelectColumn + { + SelectedItems = new List { people[0] } + }; + + // Simulate InternalGridContext with null Items + var context = new InternalGridContext(null!); + typeof(SelectColumn) + .GetProperty("InternalGridContext", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public) + ?.SetValue(selectColumn, context); + typeof(InternalGridContext) + .GetProperty("Items", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public) + ?.SetValue(context, null); + + // Act + var method = typeof(SelectColumn).GetMethod("UpdateSelectedItemsAsync", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!; + var result = method.Invoke(selectColumn, null); + if (result is Task task) await task; + + // Assert + Assert.Single(selectColumn.SelectedItems); + Assert.Equal(people[0], selectColumn.SelectedItems.First()); + } + #pragma warning restore BL0005 /// /// Simulate a key down event on the DataGrid row number . From 088dd551f4842e0f60f90a54ab84e68b7dacb5b9 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Wed, 2 Jul 2025 12:34:34 +0200 Subject: [PATCH 35/44] Process review comments: - Add and use DataGridCellAlignment enum - Use overriden DisposeAsync - Use correct comment format --- .../DataGrid/Examples/DataGridTypical.razor | 10 +++--- .../DataGrid/Columns/ColumnBase.razor | 19 ++++------- .../DataGrid/Columns/ColumnBase.razor.cs | 4 +-- .../DataGrid/Columns/SelectColumn.cs | 2 +- .../DataGrid/FluentDataGrid.razor.cs | 27 ++++++---------- .../DataGrid/FluentDataGridRow.razor.cs | 8 ++--- src/Core/Components/Icons/CoreIcons.cs | 7 ++++ .../Pagination/FluentPaginator.razor.cs | 7 ++-- src/Core/Enums/DataGridCellAlignment.cs | 31 ++++++++++++++++++ .../EventCallbackSubscribable.cs | 11 +++++-- .../DataGrid/FluentDataGridRowTests.razor | 21 ------------ ...olumnOptionsUISettings.verified.razor.html | 8 ++--- ...ColumnResizeUISettings.verified.razor.html | 8 ++--- ...d_ColumnSortUISettings.verified.razor.html | 8 ++--- ...FluentDataGrid_Default.verified.razor.html | 8 ++--- ...luentDataGrid_NoHeader.verified.razor.html | 6 ++-- ...tDataGrid_StickyHeader.verified.razor.html | 8 ++--- .../DataGrid/FluentDataGridTests.razor | 32 +++++++++---------- ...t_Customized_Rendering.verified.razor.html | 10 +++--- ..._MultiSelect_Rendering.verified.razor.html | 8 ++--- ...SingleSelect_Rendering.verified.razor.html | 8 ++--- ...StickySelect_Rendering.verified.razor.html | 8 ++--- .../Paginator/FluentPaginatorTests.razor | 2 +- 23 files changed, 135 insertions(+), 126 deletions(-) create mode 100644 src/Core/Enums/DataGridCellAlignment.cs diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor index 81d683d11d..d8032f99a6 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor @@ -15,7 +15,7 @@ RowStyle="@rowStyle" ShowHover="true" HeaderCellAsButtonWithMenu="true"> - + Flag of @(context.Code) @@ -25,10 +25,10 @@
- - - - + + + +
diff --git a/src/Core/Components/DataGrid/Columns/ColumnBase.razor b/src/Core/Components/DataGrid/Columns/ColumnBase.razor index 6efa477baa..6d7fec4822 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnBase.razor +++ b/src/Core/Components/DataGrid/Columns/ColumnBase.razor @@ -1,5 +1,6 @@ @using Microsoft.AspNetCore.Components.Rendering @using Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure +@using Microsoft.FluentUI.AspNetCore.Components.Extensions @namespace Microsoft.FluentUI.AspNetCore.Components @typeparam TGridItem @{ @@ -40,16 +41,16 @@ { if (Grid.SortByAscending == true) { - + } else { - + } } @if (ColumnOptions is not null && Filtered.GetValueOrDefault()) { - + } } @@ -99,16 +100,10 @@ string? align; // determine align string based on Align value - align = Align switch - { - HorizontalAlignment.Start => "flex-start", - HorizontalAlignment.Center => "center", - HorizontalAlignment.End => "flex-end", - _ => "flex-start" - }; + align = Align.ToAttributeValue();
- @if (Align == HorizontalAlignment.Start || Align == HorizontalAlignment.Center) + @if (Align == DataGridCellAlignment.Start || Align == DataGridCellAlignment.Center) { @if (Grid.ResizeType is not null) { @@ -164,7 +159,7 @@
} - @if (Align == HorizontalAlignment.End) + @if (Align == DataGridCellAlignment.End) { @if (Grid.ResizeType is not null) { diff --git a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs index 48cd9ab8c0..b774b035f7 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs @@ -93,7 +93,7 @@ public abstract partial class ColumnBase /// If specified, controls the justification of header and grid cells for this column. /// [Parameter] - public HorizontalAlignment Align { get; set; } + public DataGridCellAlignment Align { get; set; } /// /// If true, generates a title and aria-label attribute for the cell contents @@ -207,7 +207,7 @@ protected override void OnInitialized() { if (GetType() == typeof(SelectColumn)) { - Align = HorizontalAlignment.Center; + Align = DataGridCellAlignment.Center; } } diff --git a/src/Core/Components/DataGrid/Columns/SelectColumn.cs b/src/Core/Components/DataGrid/Columns/SelectColumn.cs index ab07256269..346354494e 100644 --- a/src/Core/Components/DataGrid/Columns/SelectColumn.cs +++ b/src/Core/Components/DataGrid/Columns/SelectColumn.cs @@ -500,7 +500,7 @@ private void RefreshHeaderContent() builder.OpenElement(0, "div"); if (!SelectAllDisabled) { - builder.AddAttribute(1, "style", "cursor: pointer; margin-left: 12px;"); + builder.AddAttribute(1, "style", "cursor: pointer;"); builder.AddAttribute(2, "onclick", EventCallback.Factory.Create(this, OnClickAllAsync)); builder.AddAttribute(3, "onkeydown", EventCallback.Factory.Create(this, OnKeyAllAsync)); } diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index f4deddd92b..bb3ca6c563 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -19,8 +19,8 @@ namespace Microsoft.FluentUI.AspNetCore.Components; /// The type of data represented by each row in the grid. [CascadingTypeParameter(nameof(TGridItem))] -[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "MA0040:Forward the CancellationToken parameter to methods that take one", Justification = "The available cancellation token are not appropriate to pass along.")] -public partial class FluentDataGrid : FluentComponentBase, IHandleEvent, IAsyncDisposable +[SuppressMessage("Usage", "MA0040:Forward the CancellationToken parameter to methods that take one", Justification = "The available cancellation token are not appropriate to pass along.")] +public partial class FluentDataGrid : FluentComponentBase, IHandleEvent { private const string JAVASCRIPT_FILE = FluentJSModule.JAVASCRIPT_ROOT + "DataGrid/FluentDataGrid.razor.js"; internal const string EMPTY_CONTENT_ROW_CLASS = "empty-content-row"; @@ -925,27 +925,18 @@ private string AriaSortValue(ColumnBase column) private static string? ColumnJustifyClass(ColumnBase column) { return new CssBuilder(column.Class) - .AddClass("col-justify-start", column.Align == HorizontalAlignment.Start) - .AddClass("col-justify-center", column.Align == HorizontalAlignment.Center) - .AddClass("col-justify-end", column.Align == HorizontalAlignment.End) + .AddClass("col-justify-start", column.Align == DataGridCellAlignment.Start) + .AddClass("col-justify-center", column.Align == DataGridCellAlignment.Center) + .AddClass("col-justify-end", column.Align == DataGridCellAlignment.End) .Build(); } - /// - /// Unregister the grid events - /// - /// - /// - [ExcludeFromCodeCoverage] - protected override async ValueTask DisposeAsync(IJSObjectReference jsModule) + /// + [ExcludeFromCodeCoverage(Justification = "Tested via integration tests.")] + public override ValueTask DisposeAsync() { _currentPageItemsChanged.Dispose(); - - if (_jsEventDisposable is not null) - { - await _jsEventDisposable.InvokeVoidAsync("dispose"); - await _jsEventDisposable.DisposeAsync().ConfigureAwait(false); - } + return base.DisposeAsync(); } internal void LoadStateFromQueryString(string queryString) diff --git a/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs b/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs index 9e5318f0e2..c112d477f8 100644 --- a/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs @@ -18,7 +18,7 @@ namespace Microsoft.FluentUI.AspNetCore.Components; /// well as managing the layout and content of the row. /// The type of the data item associated with the row. [CascadingTypeParameter(nameof(TGridItem))] -public partial class FluentDataGridRow : FluentComponentBase, IHandleEvent, IDisposable +public partial class FluentDataGridRow : FluentComponentBase, IHandleEvent { private readonly Dictionary> cells = []; internal string RowId { get; set; } = string.Empty; @@ -94,11 +94,11 @@ public void SetRowIndex(int rowIndex) RowIndex = rowIndex; } - /// - public void Dispose() + /// + public override ValueTask DisposeAsync() { - GC.SuppressFinalize(this); InternalGridContext.Unregister(this); + return base.DisposeAsync(); } /// diff --git a/src/Core/Components/Icons/CoreIcons.cs b/src/Core/Components/Icons/CoreIcons.cs index 214a932b4e..3750c4d109 100644 --- a/src/Core/Components/Icons/CoreIcons.cs +++ b/src/Core/Components/Icons/CoreIcons.cs @@ -78,12 +78,19 @@ internal static partial class Filled internal static partial class Size20 { public class CheckmarkCircle : Icon { public CheckmarkCircle() : base("CheckmarkCircle", IconVariant.Filled, IconSize.Size20, "") { } } + public class DismissCircle : Icon { public DismissCircle() : base("DismissCircle", IconVariant.Filled, IconSize.Size20, "") { } } + public class Info : Icon { public Info() : base("Info", IconVariant.Filled, IconSize.Size20, "") { } } + public class Warning : Icon { public Warning() : base("Warning", IconVariant.Filled, IconSize.Size20, "") { } } + public class CheckboxChecked : Icon { public CheckboxChecked() : base("CheckboxChecked", IconVariant.Filled, IconSize.Size20, "") { } } + public class CheckboxIndeterminate : Icon { public CheckboxIndeterminate() : base("CheckboxIndeterminate", IconVariant.Filled, IconSize.Size20, "") { } } + public class RadioButton : Icon { public RadioButton() : base("RadioButton", IconVariant.Regular, IconSize.Size20, "") { } }; + public class Star : Icon { public Star() : base("Star", IconVariant.Filled, IconSize.Size20, "") { } }; } } diff --git a/src/Core/Components/Pagination/FluentPaginator.razor.cs b/src/Core/Components/Pagination/FluentPaginator.razor.cs index 46429f02bd..3685482ee2 100644 --- a/src/Core/Components/Pagination/FluentPaginator.razor.cs +++ b/src/Core/Components/Pagination/FluentPaginator.razor.cs @@ -10,7 +10,7 @@ namespace Microsoft.FluentUI.AspNetCore.Components; /// /// A component that provides a user interface for . /// -public partial class FluentPaginator : FluentComponentBase, IDisposable +public partial class FluentPaginator : FluentComponentBase { private readonly EventCallbackSubscriber _totalItemCountChanged; private readonly EventCallbackSubscriber _currentPageItemsChanged; @@ -77,12 +77,11 @@ protected override void OnParametersSet() } /// - public void Dispose() + public override ValueTask DisposeAsync() { - GC.SuppressFinalize(this); - _totalItemCountChanged.Dispose(); _currentPageItemsChanged.Dispose(); + return base.DisposeAsync(); } private bool CanGoBack => State.CurrentPageIndex > 0; diff --git a/src/Core/Enums/DataGridCellAlignment.cs b/src/Core/Enums/DataGridCellAlignment.cs new file mode 100644 index 0000000000..1c91944c7c --- /dev/null +++ b/src/Core/Enums/DataGridCellAlignment.cs @@ -0,0 +1,31 @@ +// ------------------------------------------------------------------------ +// This file is licensed to you under the MIT License. +// ------------------------------------------------------------------------ + +using System.ComponentModel; + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// The horizontal alignment of content in a . +/// +public enum DataGridCellAlignment +{ + /// + /// A start aligned cell. + /// + [Description("flex-start")] + Start, + + /// + /// A center aligned cell. + /// + [Description("center")] + Center, + + /// + /// An end aligned cell. + /// + [Description("flex-end")] + End, +} diff --git a/src/Core/Infrastructure/EventCallbackSubscribable.cs b/src/Core/Infrastructure/EventCallbackSubscribable.cs index 13c3426a42..5372d5db1e 100644 --- a/src/Core/Infrastructure/EventCallbackSubscribable.cs +++ b/src/Core/Infrastructure/EventCallbackSubscribable.cs @@ -35,11 +35,18 @@ public async Task InvokeCallbacksAsync(T eventArg) } } - // Don't call this directly - it gets called by EventCallbackSubscription + /// + /// Don't call this directly - it gets called by EventCallbackSubscription + /// + /// The subscriber that owns this callback. + /// The callback to invoke. public void Subscribe(EventCallbackSubscriber owner, EventCallback callback) => _callbacks.TryAdd(owner, callback); - // Don't call this directly - it gets called by EventCallbackSubscription + /// + /// Don't call this directly - it gets called by EventCallbackSubscription + /// The subscriber that owns this callback. + /// public void Unsubscribe(EventCallbackSubscriber owner) => _callbacks.TryRemove(owner, out _); } diff --git a/tests/Core/Components/DataGrid/FluentDataGridRowTests.razor b/tests/Core/Components/DataGrid/FluentDataGridRowTests.razor index b49b5a2761..a4d11084f8 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridRowTests.razor +++ b/tests/Core/Components/DataGrid/FluentDataGridRowTests.razor @@ -354,27 +354,6 @@ Assert.False(OnRowKeyDownInvoked); } - [Fact] - public void FluentDataGridRow_Dispose() - { - // Arrange - var grid = Render>( - @ - - - - - ); - - // Act - var cell = grid.FindComponent>(); - cell.Instance.Dispose(); - cell.Dispose(); - - // Assert - Assert.True(cell.IsDisposed); - } - public bool OnRowClickInvoked { get; set; } public bool OnRowDoubleClickInvoked { get; set; } public bool OnRowFocusInvoked { get; set; } diff --git a/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnOptionsUISettings.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnOptionsUISettings.verified.razor.html index 2f54afb00b..9bea8b7cce 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnOptionsUISettings.verified.razor.html +++ b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnOptionsUISettings.verified.razor.html @@ -2,7 +2,7 @@ - - + - + - +
+
Name
@@ -13,13 +13,13 @@
Denis VoituronDenis Voituron
Vincent BaaijVincent Baaij
Bill GatesBill Gates
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnResizeUISettings.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnResizeUISettings.verified.razor.html index 2f54afb00b..9bea8b7cce 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnResizeUISettings.verified.razor.html +++ b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnResizeUISettings.verified.razor.html @@ -2,7 +2,7 @@ - - + - + - +
+
Name
@@ -13,13 +13,13 @@
Denis VoituronDenis Voituron
Vincent BaaijVincent Baaij
Bill GatesBill Gates
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnSortUISettings.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnSortUISettings.verified.razor.html index 2f54afb00b..9bea8b7cce 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnSortUISettings.verified.razor.html +++ b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnSortUISettings.verified.razor.html @@ -2,7 +2,7 @@ - - + - + - +
+
Name
@@ -13,13 +13,13 @@
Denis VoituronDenis Voituron
Vincent BaaijVincent Baaij
Bill GatesBill Gates
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_Default.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_Default.verified.razor.html index 2f54afb00b..9bea8b7cce 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_Default.verified.razor.html +++ b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_Default.verified.razor.html @@ -2,7 +2,7 @@ - - + - + - +
+
Name
@@ -13,13 +13,13 @@
Denis VoituronDenis Voituron
Vincent BaaijVincent Baaij
Bill GatesBill Gates
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_NoHeader.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_NoHeader.verified.razor.html index c3d66f4c83..53b837a809 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_NoHeader.verified.razor.html +++ b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_NoHeader.verified.razor.html @@ -2,13 +2,13 @@ - + - + - +
Denis VoituronDenis Voituron
Vincent BaaijVincent Baaij
Bill GatesBill Gates
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_StickyHeader.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_StickyHeader.verified.razor.html index 42bce80e07..a28ce71d7b 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_StickyHeader.verified.razor.html +++ b/tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_StickyHeader.verified.razor.html @@ -2,7 +2,7 @@ - - + - + - +
+
Name
@@ -13,13 +13,13 @@
Denis VoituronDenis Voituron
Vincent BaaijVincent Baaij
Bill GatesBill Gates
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/FluentDataGridTests.razor b/tests/Core/Components/DataGrid/FluentDataGridTests.razor index 11ba88e761..fbdc2b4f9f 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridTests.razor +++ b/tests/Core/Components/DataGrid/FluentDataGridTests.razor @@ -589,7 +589,7 @@ var cut = Render>( @ - +
Hello!
@@ -616,7 +616,7 @@ var cut = Render>( @ - +
Hello!
@@ -643,7 +643,7 @@ var cut = Render>( @ - +
Hello!
@@ -785,7 +785,7 @@ ResizableColumns="true" ResizeType="DataGridResizeType.Discrete" HeaderCellAsButtonWithMenu="false"> - +
); @@ -813,7 +813,7 @@ ResizableColumns="true" ResizeType="DataGridResizeType.Discrete" HeaderCellAsButtonWithMenu="false"> - +
); @@ -848,7 +848,7 @@ ResizableColumns="true" ResizeType="DataGridResizeType.Exact" HeaderCellAsButtonWithMenu="false"> - +
); @@ -876,7 +876,7 @@ ResizableColumns="true" ResizeType="DataGridResizeType.Exact" HeaderCellAsButtonWithMenu="false"> - + ); @@ -913,7 +913,7 @@ ResizableColumns="true" ResizeType="DataGridResizeType.Exact" HeaderCellAsButtonWithMenu="false"> - + ); @@ -947,7 +947,7 @@ ResizableColumns="true" ResizeType="DataGridResizeType.Discrete" HeaderCellAsButtonWithMenu="false"> - + ); @@ -969,7 +969,7 @@ ResizableColumns="true" ResizeType="DataGridResizeType.Discrete" HeaderCellAsButtonWithMenu="false"> - + ); @@ -997,7 +997,7 @@ ResizableColumns="true" ResizeType="DataGridResizeType.Discrete" HeaderCellAsButtonWithMenu="false"> - + ); @@ -1024,7 +1024,7 @@ ResizableColumns="true" ResizeType="DataGridResizeType.Discrete" HeaderCellAsButtonWithMenu="false"> - + ); @@ -1257,8 +1257,8 @@ var cut = Render>( @
- - + +
@@ -1284,8 +1284,8 @@ var cut = Render>( @ - - + + ); diff --git a/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_MultiSelect_Customized_Rendering.verified.razor.html b/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_MultiSelect_Customized_Rendering.verified.razor.html index f37fc0a1dc..a0e609b01e 100644 --- a/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_MultiSelect_Customized_Rendering.verified.razor.html +++ b/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_MultiSelect_Customized_Rendering.verified.razor.html @@ -3,9 +3,9 @@
-
+
+
Name
@@ -18,15 +18,15 @@
Jean MartinJean Martin
Kenji SatoKenji Sato
Julie SmithJulie Smith
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_MultiSelect_Rendering.verified.razor.html b/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_MultiSelect_Rendering.verified.razor.html index 561663a7e9..620eff3828 100644 --- a/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_MultiSelect_Rendering.verified.razor.html +++ b/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_MultiSelect_Rendering.verified.razor.html @@ -9,7 +9,7 @@ -
+
Name
@@ -26,7 +26,7 @@ -
Jean MartinJean Martin
@@ -35,7 +35,7 @@ Kenji SatoKenji Sato
@@ -44,7 +44,7 @@ Julie SmithJulie Smith
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_SingleSelect_Rendering.verified.razor.html b/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_SingleSelect_Rendering.verified.razor.html index fcb0991b1d..ecbe61da3e 100644 --- a/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_SingleSelect_Rendering.verified.razor.html +++ b/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_SingleSelect_Rendering.verified.razor.html @@ -3,7 +3,7 @@
+
Name
@@ -20,7 +20,7 @@ -
Jean MartinJean Martin
@@ -29,7 +29,7 @@ Kenji SatoKenji Sato
@@ -38,7 +38,7 @@ Julie SmithJulie Smith
\ No newline at end of file diff --git a/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_SingleStickySelect_Rendering.verified.razor.html b/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_SingleStickySelect_Rendering.verified.razor.html index 47cf692c03..11a584ec4b 100644 --- a/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_SingleStickySelect_Rendering.verified.razor.html +++ b/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_SingleStickySelect_Rendering.verified.razor.html @@ -3,7 +3,7 @@
+
Name
@@ -20,7 +20,7 @@ -
Jean MartinJean Martin
@@ -29,7 +29,7 @@ Kenji SatoKenji Sato
@@ -38,7 +38,7 @@ Julie SmithJulie Smith
\ No newline at end of file diff --git a/tests/Core/Components/Paginator/FluentPaginatorTests.razor b/tests/Core/Components/Paginator/FluentPaginatorTests.razor index 2fb28e1b84..64da9da2ae 100644 --- a/tests/Core/Components/Paginator/FluentPaginatorTests.razor +++ b/tests/Core/Components/Paginator/FluentPaginatorTests.razor @@ -312,7 +312,7 @@ // Dispose the component (this should call Dispose() internally) if (paginator is not null) { - paginator?.Dispose(); + paginator?.DisposeAsync(); } cut.Dispose(); From ede49e6aa5a4aa3acc595b08a456f796d43d3074 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Wed, 2 Jul 2025 12:35:48 +0200 Subject: [PATCH 36/44] Forgot to change the docs --- .../GetStarted/Migration/MigrationFluentDataGrid.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/GetStarted/Migration/MigrationFluentDataGrid.md b/examples/Demo/FluentUI.Demo.Client/Documentation/GetStarted/Migration/MigrationFluentDataGrid.md index 2ed090259d..deb4097719 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/GetStarted/Migration/MigrationFluentDataGrid.md +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/GetStarted/Migration/MigrationFluentDataGrid.md @@ -8,6 +8,6 @@ have now been replaced with our standard Localization capabilities. You can use An example of this can be found in the `Server` project of the demo application, where a custom localizer is registered in the `Program.cs` file. ### Enum changes -- `Align` has been renamed to `HorizontalAlignment` +- `Align` has been renamed to `DataGridCellAlignment` - `GenerateHeaderOption` has been renamed to `DataGridGeneratedHeaderType` - `SortDirection` has been renamed to `DataGridSortDirection` From f921191ea5dc030c6ad995da16c12f00a7d732ff Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Wed, 2 Jul 2025 15:00:11 +0200 Subject: [PATCH 37/44] - Use auto-generated constant - Use local variables in testing cell/row --- examples/Demo/FluentUI.Demo/MyLocalizer.cs | 3 +- .../DataGrid/FluentDataGridCellTests.razor | 75 ++++-------- .../DataGrid/FluentDataGridRowTests.razor | 113 +++++++----------- 3 files changed, 67 insertions(+), 124 deletions(-) diff --git a/examples/Demo/FluentUI.Demo/MyLocalizer.cs b/examples/Demo/FluentUI.Demo/MyLocalizer.cs index d30f8e5256..51d9bc00ff 100644 --- a/examples/Demo/FluentUI.Demo/MyLocalizer.cs +++ b/examples/Demo/FluentUI.Demo/MyLocalizer.cs @@ -3,6 +3,7 @@ // ------------------------------------------------------------------------ using Microsoft.FluentUI.AspNetCore.Components; +using Microsoft.FluentUI.AspNetCore.Components.Localization; using System.Globalization; namespace FluentUI.Demo; @@ -34,7 +35,7 @@ internal class MyLocalizer : IFluentLocalizer // Provide custom translations based on the key return key switch { - "DataGrid_ResizeDiscrete" => "Width (+/- 10px)", + LanguageResource.DataGrid_ResizeDiscrete => "Width (+/- 10px)", // Fallback to the Default/English if no translation is found _ => IFluentLocalizer.GetDefault(key, arguments), diff --git a/tests/Core/Components/DataGrid/FluentDataGridCellTests.razor b/tests/Core/Components/DataGrid/FluentDataGridCellTests.razor index 244592a956..62e88da7fb 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridCellTests.razor +++ b/tests/Core/Components/DataGrid/FluentDataGridCellTests.razor @@ -46,8 +46,10 @@ public async Task FluentDataGridCell_HandleOnCellClickAsync_InvokesCallbacks() { // Arrange + var cellClickInvoked = false; + var cut = Render>( - @ + @ @@ -57,15 +59,16 @@ await cell.Instance.HandleOnCellClickAsync(); // Assert - Assert.True(OnCellClickInvoked); + Assert.True(cellClickInvoked); } [Fact] public async Task FluentDataGridCell_HandleOnCellFocusAsync_InvokesCallbacks() { // Arrange + var cellFocusInvoked = false; var cut = Render>( - @ + @ @@ -75,7 +78,7 @@ var cell = cut.FindComponent>(); await cell.Instance.HandleOnCellFocusAsync(); // Assert - Assert.True(OnCellFocusInvoked); + Assert.True(cellFocusInvoked); } [Fact] @@ -83,6 +86,7 @@ { // Arrange var items = new List(People).AsQueryable(); + var cellKeyDownInvoked = false; var cut = Render>( @ @@ -90,7 +94,7 @@ SelectMode="DataGridSelectMode.Multiple" Selectable="@(x => x.PersonId > 0)" SelectFromEntireRow="false" - OnSelect="HandleCellKeyDown" /> + OnSelect="@(e => cellKeyDownInvoked = e.Selected)" /> ); @@ -100,7 +104,7 @@ await cell.Instance.HandleOnCellKeyDownAsync(keyboardEvent); // Assert - Assert.True(OnCellKeyDownInvoked); + Assert.True(cellKeyDownInvoked); } [Fact] @@ -108,16 +112,18 @@ { // Arrange var items = new List(People).AsQueryable(); + var cellKeyDownInvoked = false; var cut = Render>( @ - - - ); + /> + + + ); // Act var keyboardEvent = new KeyboardEventArgs { Code = "Enter" }; @@ -125,7 +131,7 @@ await cell.Instance.HandleOnCellKeyDownAsync(keyboardEvent); // Assert - Assert.False(OnCellKeyDownInvoked); + Assert.False(cellKeyDownInvoked); } [Fact] @@ -133,6 +139,7 @@ { // Arrange var items = new List(People).AsQueryable(); + var cellKeyDownInvoked = false; var cut = Render>( @ @@ -140,7 +147,7 @@ SelectMode="DataGridSelectMode.Multiple" Selectable="@(x => x.PersonId > 1)" SelectFromEntireRow="false" - OnSelect="HandleCellKeyDown" /> + OnSelect="@(e => cellKeyDownInvoked = e.Selected)" /> ); @@ -150,7 +157,7 @@ await cell.Instance.HandleOnCellKeyDownAsync(keyboardEvent); // Assert - Assert.False(OnCellKeyDownInvoked); + Assert.False(cellKeyDownInvoked); } [Fact] @@ -174,40 +181,4 @@ Assert.True(cell.IsDisposed); } - - public bool OnCellClickInvoked { get; set; } - public bool OnCellFocusInvoked { get; set; } - public bool OnCellKeyDownInvoked { get; set; } - - - private void HandleCellClick() - { - // This method simulates the callback that would be invoked on cell click - // In a real scenario, this would be defined in the FluentDataGrid component - // and passed to the FluentDataGridCell. - OnCellClickInvoked = true; - } - - private void HandleCellFocus() - { - // This method simulates the callback that would be invoked on cell focus - // In a real scenario, this would be defined in the FluentDataGrid component - // and passed to the FluentDataGridCell. - OnCellFocusInvoked = true; - } - - private void HandleCellKeyDown((Person person, bool selected) e) - { - // This method simulates the callback that would be invoked on cell key down - // In a real scenario, this would be defined in the FluentDataGrid component - // and passed to the FluentDataGridCell. - if (e.selected ) - { - OnCellKeyDownInvoked = true; - } - else - { - OnCellKeyDownInvoked = false; - } - } } diff --git a/tests/Core/Components/DataGrid/FluentDataGridRowTests.razor b/tests/Core/Components/DataGrid/FluentDataGridRowTests.razor index a4d11084f8..2cb7b9b3cc 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridRowTests.razor +++ b/tests/Core/Components/DataGrid/FluentDataGridRowTests.razor @@ -51,7 +51,8 @@ - ); + + ); // Act var row = grid.FindComponent>(); @@ -70,8 +71,9 @@ public async Task FluentDataGridRow_HandleOnRowClickAsync_InvokesCallbacks() { // Arrange + var rowClickInvoked = false; var cut = Render>( - @ + @ @@ -82,17 +84,18 @@ await row.Instance.HandleOnRowClickAsync(row.Instance.RowId); // Assert - Assert.True(OnRowClickInvoked); + Assert.True(rowClickInvoked); } [Fact] public async Task FluentDataGridRow_HandleOnRowClickAsync_EmptyRow() { // Arrange + var rowClickInvoked = false; var People = new List() { }.AsQueryable(); var cut = Render>( - @ + @ @@ -104,16 +107,18 @@ await row.Instance.HandleOnRowClickAsync(row.Instance.RowId); // Assert - Assert.False(OnRowClickInvoked); + Assert.False(rowClickInvoked); } [Fact] public async Task FluentDataGridRow_HandleOnRowClickAsync_Class() { // Arrange + + var rowClickInvoked = false; var cut = Render>( - @ + @ ); @@ -122,15 +127,16 @@ await row.Instance.HandleOnRowClickAsync(row.Instance.RowId); // Assert - Assert.True(OnRowClickInvoked); + Assert.True(rowClickInvoked); } [Fact] public async Task FluentDataGridRow_HandleOnRowClickAsync_NoRowPassedIn() { // Arrange + var rowClickInvoked = false; var cut = Render>( - @ + @ @@ -140,7 +146,7 @@ await row.Instance.HandleOnRowClickAsync(string.Empty); // Assert - Assert.False(OnRowClickInvoked); + Assert.False(rowClickInvoked); } [Fact] @@ -148,9 +154,10 @@ { // Arrange var People = new List() { }.AsQueryable(); + var rowDoubleClickInvoked = false; var cut = Render>( - @ + @ @@ -162,7 +169,7 @@ await row.Instance.HandleOnRowDoubleClickAsync(row.Instance.RowId); // Assert - Assert.True(OnRowDoubleClickInvoked); + Assert.True(rowDoubleClickInvoked); } [Fact] @@ -170,9 +177,10 @@ { // Arrange var People = new List() { }.AsQueryable(); + var rowDoubleClickInvoked = false; var cut = Render>( - @ + @ @@ -184,15 +192,16 @@ await row.Instance.HandleOnRowClickAsync(row.Instance.RowId); // Assert - Assert.False(OnRowClickInvoked); + Assert.False(rowDoubleClickInvoked); } [Fact] public async Task FluentDataGridRow_HandleOnRowDoubleClickAsync_NoRowPassedIn() { // Arrange + var rowDoubleClickInvoked = false; var cut = Render>( - @ + @ @@ -202,15 +211,16 @@ await row.Instance.HandleOnRowDoubleClickAsync(string.Empty); // Assert - Assert.False(OnRowDoubleClickInvoked); + Assert.False(rowDoubleClickInvoked); } [Fact] public async Task FluentDataGridRow_HandleOnRowFocusAsync() { // Arrange + var rowFocusInvoked = false; var cut = Render>( - @ + @ @@ -220,7 +230,7 @@ var cell = cut.FindComponent>(); await cell.Instance.HandleOnRowFocusAsync(); // Assert - Assert.True(OnRowFocusInvoked); + Assert.True(rowFocusInvoked); } [Fact] @@ -228,6 +238,7 @@ { // Arrange var items = new List(People).AsQueryable(); + var rowKeyDownInvoked = false; var cut = Render>( @ @@ -235,7 +246,7 @@ SelectMode="DataGridSelectMode.Multiple" Selectable="@(x => x.PersonId > 0)" SelectFromEntireRow="true" - OnSelect="HandleRowKeyDown" /> + OnSelect="@(e => rowKeyDownInvoked = true)" /> ); @@ -245,7 +256,7 @@ await row.Instance.HandleOnRowKeyDownAsync(row.Instance.RowId, keyboardEvent); // Assert - Assert.True(OnRowKeyDownInvoked); + Assert.True(rowKeyDownInvoked); } @@ -254,6 +265,7 @@ { // Arrange var items = new List(People).AsQueryable(); + var rowKeyDownInvoked = false; var cut = Render>( @ @@ -261,7 +273,7 @@ SelectMode="DataGridSelectMode.Multiple" Selectable="@(x => x.PersonId > 1)" SelectFromEntireRow="true" - OnSelect="HandleRowKeyDown" /> + OnSelect="@(e => rowKeyDownInvoked = true)" /> ); @@ -271,7 +283,7 @@ await row.Instance.HandleOnRowKeyDownAsync(row.Instance.RowId, keyboardEvent); // Assert - Assert.False(OnRowKeyDownInvoked); + Assert.False(rowKeyDownInvoked); } [Fact] @@ -279,9 +291,10 @@ { // Arrange var items = new List(People).AsQueryable(); + var rowKeyDownInvoked = false; var cut = Render>( - @ + @ ); @@ -291,7 +304,7 @@ await row.Instance.HandleOnRowKeyDownAsync(row.Instance.RowId, keyboardEvent); // Assert - Assert.False(OnRowKeyDownInvoked); + Assert.False(rowKeyDownInvoked); } [Fact] @@ -299,6 +312,7 @@ { // Arrange var items = new List(People).AsQueryable(); + var rowKeyDownInvoked = false; var cut = Render>( @ @@ -311,7 +325,7 @@ await row.Instance.HandleOnRowKeyDownAsync(row.Instance.RowId, keyboardEvent); // Assert - Assert.False(OnRowKeyDownInvoked); + Assert.False(rowKeyDownInvoked); } [Fact] @@ -319,6 +333,7 @@ { // Arrange var items = new List(People).AsQueryable(); + var rowKeyDownInvoked = false; var cut = Render>( @ @@ -331,7 +346,7 @@ await row.Instance.HandleOnRowKeyDownAsync(row.Instance.RowId, keyboardEvent); // Assert - Assert.False(OnRowKeyDownInvoked); + Assert.False(rowKeyDownInvoked); } [Fact] @@ -339,6 +354,7 @@ { // Arrange var items = new List(People).AsQueryable(); + var rowKeyDownInvoked = false; var cut = Render>( @ @@ -351,51 +367,6 @@ await row.Instance.HandleOnRowKeyDownAsync(string.Empty, keyboardEvent); // Assert - Assert.False(OnRowKeyDownInvoked); - } - - public bool OnRowClickInvoked { get; set; } - public bool OnRowDoubleClickInvoked { get; set; } - public bool OnRowFocusInvoked { get; set; } - public bool OnRowKeyDownInvoked { get; set; } - - - private void HandleRowClick() - { - // This method simulates the callback that would be invoked on cell click - // In a real scenario, this would be defined in the FluentDataGrid component - // and passed to the FluentDataGridCell. - OnRowClickInvoked = true; - } - - private void HandleRowDoubleClick() - { - // This method simulates the callback that would be invoked on cell click - // In a real scenario, this would be defined in the FluentDataGrid component - // and passed to the FluentDataGridCell. - OnRowDoubleClickInvoked = true; - } - - private void HandleRowFocus() - { - // This method simulates the callback that would be invoked on cell focus - // In a real scenario, this would be defined in the FluentDataGrid component - // and passed to the FluentDataGridCell. - OnRowFocusInvoked = true; - } - - private void HandleRowKeyDown((Person person, bool selected) e) - { - // This method simulates the callback that would be invoked on cell key down - // In a real scenario, this would be defined in the FluentDataGrid component - // and passed to the FluentDataGridCell. - if (e.selected ) - { - OnRowKeyDownInvoked = true; - } - else - { - OnRowKeyDownInvoked = false; - } + Assert.False(rowKeyDownInvoked); } } From e6ec4a3cac4ec3c717a2c9318ce19aaaf2d10270 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Wed, 2 Jul 2025 11:18:15 +0200 Subject: [PATCH 38/44] DataGrid docs and examples --- .../DataGrid/Examples/CustomCSS.css | 29 ++++ .../DataGrid/Examples/DataGridAutoFit.razor | 57 +++++++ .../Examples/DataGridAutoItemsPerPage.razor | 31 ++++ .../DataGridColumnHeaderGeneration.razor | 37 +++++ .../Examples/DataGridColumnKeyGridSort.razor | 50 ++++++ .../Examples/DataGridCustomComparer.razor | 147 ++++++++++++++++++ .../Examples/DataGridCustomComparer.razor.css | 6 + .../Examples/DataGridCustomGridSort.razor | 41 +++++ .../Examples/DataGridCustomHeader.razor | 51 ++++++ .../Examples/DataGridCustomPaging.razor | 52 +++++++ .../Examples/DataGridCustomPaging.razor.css | 36 +++++ .../Examples/DataGridDynamicColumns.razor | 32 ++++ .../Examples/DataGridGetStarted.razor | 27 ++++ .../DataGrid/Examples/DataGridManual.razor | 19 +++ .../Examples/DataGridMultilineText.razor | 16 ++ ...ataGridNotVirtualizedLoadingAndEmpty.razor | 76 +++++++++ .../Examples/DataGridRemoteData.razor | 57 +++++++ .../Examples/DataGridRemoteData2.razor | 103 ++++++++++++ .../Examples/DataGridTableScrollbars.razor | 20 +++ .../Examples/DataGridTemplateColumns.razor | 26 ++++ .../DataGridTemplateColumns.razor.css | 6 + .../Examples/DataGridVirtualization.razor | 29 ---- .../Examples/DataGridVirtualize.razor | 83 ++++++++++ .../Components/DataGrid/FluentDataGrid.md | 103 +++++++++++- .../FoodProductRecalls.cs | 48 ++++++ .../DataGrid/Columns/ColumnKeyGridSort.cs | 17 +- .../DataGrid/FluentDataGrid.razor.cs | 5 + 27 files changed, 1165 insertions(+), 39 deletions(-) create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/CustomCSS.css create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridAutoFit.razor create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridAutoItemsPerPage.razor create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridColumnHeaderGeneration.razor create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridColumnKeyGridSort.razor create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomComparer.razor create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomComparer.razor.css create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomGridSort.razor create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomHeader.razor create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomPaging.razor create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomPaging.razor.css create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridDynamicColumns.razor create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridGetStarted.razor create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridManual.razor create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridMultilineText.razor create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridNotVirtualizedLoadingAndEmpty.razor create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridRemoteData.razor create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridRemoteData2.razor create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTableScrollbars.razor create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTemplateColumns.razor create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTemplateColumns.razor.css delete mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridVirtualization.razor create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridVirtualize.razor diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/CustomCSS.css b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/CustomCSS.css new file mode 100644 index 0000000000..09c44e83e8 --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/CustomCSS.css @@ -0,0 +1,29 @@ +#datagrid-container { + height: calc(100% - 3rem); + min-height: 8rem; + overflow-x: auto; + overflow-y: hidden; +} + +article { + min-height: 32rem; + max-height: 80dvh; +} + +.demo-section-content { + height: calc(100% - 10rem); +} + +.demo-section-example { + min-height: 135px !important; + height: 100%; +} + +fluent-tabs { + height: 100%; +} + +#tab-example-autoitemsperpage-panel { + height: 100% !important; + max-height: calc(100% - 2rem) !important; +} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridAutoFit.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridAutoFit.razor new file mode 100644 index 0000000000..2ff7d08f3f --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/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/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridAutoItemsPerPage.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridAutoItemsPerPage.razor new file mode 100644 index 0000000000..0c143485d8 --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridAutoItemsPerPage.razor @@ -0,0 +1,31 @@ +@using static FluentUI.Demo.SampleData.Olympics2024 + +@inject IJSRuntime JSRuntime + +
+ + + + + + + +
+ + + +@code { + + DataGridRowSize rowSize = DataGridRowSize.Small; + IQueryable? items; + PaginationState pagination = new PaginationState { ItemsPerPage = 10 }; + + protected override void OnInitialized() => + items = SampleData.Olympics2024.Countries.AsQueryable(); + +} + diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridColumnHeaderGeneration.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridColumnHeaderGeneration.razor new file mode 100644 index 0000000000..827db84e4a --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridColumnHeaderGeneration.razor @@ -0,0 +1,37 @@ +@using System.ComponentModel.DataAnnotations + + + + + + +@code { + public class Person + { + public Person(int personId, string name, DateOnly birthDate) + { + PersonId = personId; + Name = name; + BirthDate = birthDate; + } + + [Display(Name="Identity")] + public int PersonId { get; set; } + + [Display(Name = "Full _name")] + public string Name { get; set; } + + [Display(Name = "Birth date")] + public DateOnly BirthDate { get; set; } + } + + IQueryable people = new[] + { + new Person(10895, "Jean Martin", new DateOnly(1985, 3, 16)), + new Person(10944, "António Langa", new DateOnly(1991, 12, 1)), + new Person(11203, "Julie Smith", new DateOnly(1958, 10, 10)), + new Person(11205, "Nur Sari", new DateOnly(1922, 4, 27)), + new Person(11898, "Jose Hernandez", new DateOnly(2011, 5, 3)), + new Person(12130, "Kenji Sato", new DateOnly(2004, 1, 9)), + }.AsQueryable(); +} \ No newline at end of file diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridColumnKeyGridSort.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridColumnKeyGridSort.razor new file mode 100644 index 0000000000..19609c8d41 --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridColumnKeyGridSort.razor @@ -0,0 +1,50 @@ + + + @context.Properties["firstname"] + + + + @context.Properties["lastname"] + + + +@code { + private ColumnKeyGridSort _firstNameSort = new ColumnKeyGridSort( + "firstname", + (queryable, sortAscending) => + { + if (sortAscending) + { + return queryable.OrderBy(x => x.Properties["firstname"]); + } + else + { + return queryable.OrderByDescending(x => x.Properties["firstname"]); + } + } + ); + + private ColumnKeyGridSort _lastNameSort = new ColumnKeyGridSort( + "lastname", + (queryable, sortAscending) => + { + if (sortAscending) + { + return queryable.OrderBy(x => x.Properties["lastname"]); + } + else + { + return queryable.OrderByDescending(x => x.Properties["lastname"]); + } + } + ); + + private static readonly IQueryable _gridData = new GridRow[] { + new(new Dictionary{ { "firstname", "Tom" }, { "lastname", "Cruise" } }), + new(new Dictionary{ { "firstname", "Dolly" }, { "lastname", "Parton" } }), + new(new Dictionary{ { "firstname", "Nicole" }, { "lastname", "Kidmon" } }), + new(new Dictionary{ { "firstname", "James" }, { "lastname", "Bond" } }), + }.AsQueryable(); + + public record GridRow(Dictionary Properties); +} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomComparer.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomComparer.razor new file mode 100644 index 0000000000..34e96dc22c --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomComparer.razor @@ -0,0 +1,147 @@ +@using static FluentUI.Demo.SampleData.Olympics2024 + +@if (altcolor) +{ + + +} + + + + + Discrete + Exact + + + + + + + +
+ + + Flag of @(context.Code) + + + + + + + @* + + + + + @(context.Name) + + *@ + + + + + +
+ + + + There are @(pagination.TotalItemCount ?? 0) rows + + + This is page @(pagination.CurrentPageIndex + 1) out of a total of @(pagination.LastPageIndex + 1) pages + + + + + + +@code { + bool altcolor = false; + IQueryable? items; + PaginationState pagination = new PaginationState { ItemsPerPage = 10 }; + string nameFilter = string.Empty; + DataGridResizeType? _resizeType = null; + bool _showActionsMenu; + bool _useMenuService = true; + bool _resizeColumnOnAllRows = true; + + GridSort rankSort = GridSort + .ByDescending(x => x.Medals.Gold) + .ThenDescending(x => x.Medals.Silver) + .ThenDescending(x => x.Medals.Bronze); + + // Uncomment line below when using the TemplateColumn example for the country _name + //GridSort nameSort = GridSort.ByAscending(x => x.Name, StringLengthComparer.Instance); + + + IQueryable? FilteredItems => items?.Where(x => x.Name.Contains(nameFilter, StringComparison.CurrentCultureIgnoreCase)); + + protected override void OnInitialized() + { + items = SampleData.Olympics2024.Countries.AsQueryable(); + } + + private void HandleCountryFilter(ChangeEventArgs args) + { + if (args.Value is string value) + { + nameFilter = value; + } + } + + private void HandleClear() + { + if (string.IsNullOrWhiteSpace(nameFilter)) + { + nameFilter = string.Empty; + } + } + + private void HandleRowFocus(FluentDataGridRow row) + { + Console.WriteLine($"[Custom comparer] Row focused: {row.Item?.Name}"); + } + + public class StringLengthComparer : IComparer + { + public static readonly StringLengthComparer Instance = new StringLengthComparer(); + + public int Compare(string? x, string? y) + { + if (x is null) + { + return y is null ? 0 : -1; + } + + if (y is null) + { + return 1; + } + + return x.Length.CompareTo(y.Length); + } + } +} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomComparer.razor.css b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomComparer.razor.css new file mode 100644 index 0000000000..ae1eb8b29e --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomComparer.razor.css @@ -0,0 +1,6 @@ +/* Ensure all the flags are the same size, and centered */ +.flag { + height: 1rem; + margin: auto; + border: 1px solid var(--neutral-layer-3); +} \ No newline at end of file diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomGridSort.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomGridSort.razor new file mode 100644 index 0000000000..0ac0b567df --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomGridSort.razor @@ -0,0 +1,41 @@ + + + + + @context.Group + + + + +

Keep numbers always sorted ascending inside the group when sorting by group

+ + + + + + + + + +@code { + GridSort groupRank = GridSort + .ByAscending(x => x.Group) + .ThenAscending(x => x.Number); + + GridSort groupRankNumberAlwaysAscending = GridSort + .ByAscending(x => x.Group) + .ThenAlwaysAscending(x => x.Number); + + private static readonly IQueryable _gridData = new GridRow[] { + new(2, "B"), + new(1, "A"), + new(4, "B"), + new(3, "A") + }.AsQueryable(); + + public class GridRow(int number, string group) + { + public int Number { get; } = number; + public string Group { get; } = group; + } +} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomHeader.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomHeader.razor new file mode 100644 index 0000000000..40319a05b1 --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomHeader.razor @@ -0,0 +1,51 @@ +@using Microsoft.FluentUI.AspNetCore.Components + + + + + + + @context.Title + + + + + + + + + Birth date + + + + + +@code { + public class Person + { + public Person(int personId, string name, DateOnly birthDate) + { + PersonId = personId; + Name = name; + BirthDate = birthDate; + } + + public int PersonId { get; set; } + + public string Name { get; set; } + + public DateOnly BirthDate { get; set; } + } + + IQueryable people = new[] + { + new Person(10895, "Jean Martin", new DateOnly(1985, 3, 16)), + new Person(10944, "António Langa", new DateOnly(1991, 12, 1)), + new Person(11203, "Julie Smith", new DateOnly(1958, 10, 10)), + new Person(11205, "Nur Sari", new DateOnly(1922, 4, 27)), + new Person(11898, "Jose Hernandez", new DateOnly(2011, 5, 3)), + new Person(12130, "Kenji Sato", new DateOnly(2004, 1, 9)), + }.AsQueryable(); +} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomPaging.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomPaging.razor new file mode 100644 index 0000000000..86928c18a1 --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomPaging.razor @@ -0,0 +1,52 @@ +@using static FluentUI.Demo.SampleData.Olympics2024 + +
+ + + + + + +
+ +
+ Page: + @if (pagination.TotalItemCount.HasValue) + { + for (var pageIndex = 0; pageIndex <= pagination.LastPageIndex; pageIndex++) + { + var capturedIndex = pageIndex; + + @(capturedIndex + 1) + + } + } +
+ +@code { + PaginationState pagination = new PaginationState { ItemsPerPage = 10 }; + IQueryable? items; + + protected override void OnInitialized() + { + items = SampleData.Olympics2024.Countries.AsQueryable(); + pagination.TotalItemCountChanged += (sender, eventArgs) => StateHasChanged(); + } + + private async Task GoToPageAsync(int pageIndex) + { + await pagination.SetCurrentPageIndexAsync(pageIndex); + } + + private ButtonAppearance PageButtonAppearance(int pageIndex) + => pagination.CurrentPageIndex == pageIndex ? ButtonAppearance.Primary : ButtonAppearance.Default; + + private string? AriaCurrentValue(int pageIndex) + => pagination.CurrentPageIndex == pageIndex ? "page" : null; + + private string AriaLabel(int pageIndex) + => $"Go to page {pageIndex}"; +} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomPaging.razor.css b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomPaging.razor.css new file mode 100644 index 0000000000..05342039fa --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomPaging.razor.css @@ -0,0 +1,36 @@ +/* Fix height and enable scrolling */ +.grid { + overflow-y: auto; +} + + +/* Style the custom page links*/ +.page-buttons { + margin: 1rem 0; + align-items: center; +} + + .page-buttons button { + background: #d6d7d8; + color: black; + padding: 0.25rem 0.75rem; + border-radius: 0.4rem; + transition: transform 0.3s ease-out; + margin: 0.25rem; + } + + .page-buttons button:active { + background: #a7c1ff !important; + color: white; + transform: scale(0.95) translateY(-0.15rem); + transition-duration: 0.05s; + } + + .page-buttons button:hover:not(.current) { + background: #c0c9dc; + } + + .page-buttons button.current { + background: #3771f4; + color: white; + } diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridDynamicColumns.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridDynamicColumns.razor new file mode 100644 index 0000000000..735e695a7b --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridDynamicColumns.razor @@ -0,0 +1,32 @@ +@using static FluentUI.Demo.SampleData.People + +

+ Show: + Name + Birth date +

+ + + + + @if (showName) + { + + @context.LastName, @context.FirstName + + } + @if (showBirthDate) + { + + } + + + +@code { + bool showName = true; + bool showBirthDate = false; +} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridGetStarted.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridGetStarted.razor new file mode 100644 index 0000000000..e39f649cea --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridGetStarted.razor @@ -0,0 +1,27 @@ + + + + + + + + + + +@code { + PaginationState pagination = new PaginationState() { ItemsPerPage = 2 }; + + record Person(int PersonId, string Name, DateOnly BirthDate); + + IQueryable people = new[] + { + new Person(10895, "Jean Martin", new DateOnly(1985, 3, 16)), + new Person(10944, "António Langa", new DateOnly(1991, 12, 1)), + new Person(11203, "Julie Smith", new DateOnly(1958, 10, 10)), + new Person(11205, "Nur Sari", new DateOnly(1922, 4, 27)), + new Person(11898, "Jose Hernandez", new DateOnly(2011, 5, 3)), + new Person(12130, "Kenji Sato", new DateOnly(2004, 1, 9)), + }.AsQueryable(); + + private RenderFragment template = @; +} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridManual.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridManual.razor new file mode 100644 index 0000000000..d5c300b952 --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridManual.razor @@ -0,0 +1,19 @@ + + + Column 1 + Column 2 + + + 1.1 + 1.2 + + + 2.1 + 2.2 + + diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridMultilineText.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridMultilineText.razor new file mode 100644 index 0000000000..bf68608e07 --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridMultilineText.razor @@ -0,0 +1,16 @@ + + + + + + +@code { + record Person(int PersonId, string Name, string Description); + + IQueryable people = new[] + { + new Person(10895, "Jean Martin", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."), + new Person(10944, "António Langa", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."), + new Person(11203, "Julie Smith","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."), + }.AsQueryable(); +} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridNotVirtualizedLoadingAndEmpty.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridNotVirtualizedLoadingAndEmpty.razor new file mode 100644 index 0000000000..1f9bb8cd87 --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridNotVirtualizedLoadingAndEmpty.razor @@ -0,0 +1,76 @@ +
+ + + + + + + + + + +
Nothing to see here. Carry on!
+
+
+
+
+ + + +Simulate data loading + + +@code { + FluentDataGrid? grid; + FluentSwitch? _clearToggle; + + bool _clearItems = false; + + public record SampleGridData(string Item1, string Item2, string Item3, string Item4); + IQueryable? items = Enumerable.Empty().AsQueryable(); + + private IQueryable GenerateSampleGridData(int size) + { + SampleGridData[] data = new SampleGridData[size]; + + for (int i = 0; i < size; i++) + { + data[i] = new SampleGridData($"This {i}-1", $"is {i}-2", $"some {i}-3", $"data {i}-4"); + } + return data.AsQueryable(); + } + + protected override void OnInitialized() + { + items = GenerateSampleGridData(100); + } + + private async Task SimulateDataLoading() + { + _clearItems = false; + + grid?.SetLoadingState(true); + items = null; + + await Task.Delay(1500); + + items = GenerateSampleGridData(100); + grid?.SetLoadingState(false); + } + + private void ToggleItems() + { + if (_clearItems) + { + items = null; + } + else + { + items = GenerateSampleGridData(100); + } + } +} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridRemoteData.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridRemoteData.razor new file mode 100644 index 0000000000..cba0ea5115 --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridRemoteData.razor @@ -0,0 +1,57 @@ +@using FluentUI.Demo.SampleData + +@inject HttpClient Http +@inject NavigationManager NavManager + +
+ + + + + + + + + + + +
+ +

Total: @numResults results found

+ +@code { + GridItemsProvider foodRecallProvider = default!; + int? numResults; + + protected override async Task OnInitializedAsync() + { + // Define the GridRowsDataProvider. Its job is to convert QuickGrid's GridRowsDataProviderRequest into a query against + // an arbitrary data soure. In this example, we need to translate query parameters into the particular URL format + // supported by the external JSON API. It's only possible to perform whatever sorting/filtering/etc is supported + // by the external API. + foodRecallProvider = async req => + { + var url = NavManager.GetUriWithQueryParameters("https://api.fda.gov/food/enforcement.json", new Dictionary + { + { "skip", req.StartIndex }, + { "limit", req.Count }, + }); + + var response = await Http.GetFromJsonAsync(url, req.CancellationToken); + + + return GridItemsProviderResult.From( + items: response!.Results, + totalItemCount: response!.Meta.Results.Total); + }; + + // Display the number of results just for information. This is completely separate from the grid. + numResults = (await Http.GetFromJsonAsync("https://api.fda.gov/food/enforcement.json"))!.Meta.Results.Total; + } +} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridRemoteData2.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridRemoteData2.razor new file mode 100644 index 0000000000..08d8b161f1 --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridRemoteData2.razor @@ -0,0 +1,103 @@ +@using FluentUI.Demo.SampleData + +@inject HttpClient Http +@inject NavigationManager NavManager + + + + + + + + + + + + Clear + + + Search + + + + +
+
+ + + + + + + + + + + + +
+ + +@code { + + FluentDataGrid dataGrid = default!; + IQueryable foodRecallItems = default!; + bool loading = true; + PaginationState pagination = new PaginationState { ItemsPerPage = 10 }; + string? _stateFilter = "NY"; + + protected async Task RefreshItemsAsync(GridItemsProviderRequest req) + { + loading = true; + await InvokeAsync(StateHasChanged); + + var filters = new Dictionary + { + { "skip", req.StartIndex }, + { "limit", req.Count }, + }; + + if (!string.IsNullOrWhiteSpace(_stateFilter)) + filters.Add("search", $"state:{_stateFilter}"); + + var s = req.GetSortByProperties().FirstOrDefault(); + if (req.SortByColumn != null && !string.IsNullOrEmpty(s.PropertyName)) + { + filters.Add("sort", s.PropertyName + (s.Direction == DataGridSortDirection.Ascending ? ":asc" : ":desc")); + } + + var url = NavManager.GetUriWithQueryParameters("https://api.fda.gov/food/enforcement.json", filters); + + var response = await Http.GetFromJsonAsync(url); + + foodRecallItems = response!.Results.AsQueryable(); + await pagination.SetTotalItemCountAsync(response!.Meta.Results.Total); + + loading = false; + await InvokeAsync(StateHasChanged); + + } + + public void ClearFilters() + { + _stateFilter = null; + } + + public async Task DataGridRefreshDataAsync() + { + await dataGrid.RefreshDataAsync(true); + } +} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTableScrollbars.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTableScrollbars.razor new file mode 100644 index 0000000000..0958218f1d --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTableScrollbars.razor @@ -0,0 +1,20 @@ +
+
+ + + + + +
+
+ +@code { + record Person(int PersonId, string Name, string Description); + + IQueryable people = new[] + { + new Person(10895, "Jean Martin", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."), + new Person(10944, "António Langa", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."), + new Person(11203, "Julie Smith","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."), + }.AsQueryable(); +} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTemplateColumns.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTemplateColumns.razor new file mode 100644 index 0000000000..6f7f54e12b --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTemplateColumns.razor @@ -0,0 +1,26 @@ +@using static FluentUI.Demo.SampleData.People + + + + @context.LastName, @context.FirstName + + + Regular + Double + + + + +

@message

+ +@code { + string message = string.Empty; + + GridSort sortByName = GridSort + .ByAscending(p => p.LastName) + .ThenAscending(p => p.FirstName); + + void Bonus(Person p) => message = $"You want to give {p.FirstName} {p.LastName} a regular bonus"; + + void DoubleBonus(Person p) => message = $"You want to give {p.FirstName} {p.LastName} a double bonus"; +} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTemplateColumns.razor.css b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTemplateColumns.razor.css new file mode 100644 index 0000000000..f312941e67 --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTemplateColumns.razor.css @@ -0,0 +1,6 @@ +/* Ensure all the flags are the same size, and centered */ +.flag { + height: 1rem; + margin: auto; + border: 1px solid var(--neutral-layer-3); +} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridVirtualization.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridVirtualization.razor deleted file mode 100644 index e3ff893185..0000000000 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridVirtualization.razor +++ /dev/null @@ -1,29 +0,0 @@ - -
- - - -
- -@code { - private IQueryable? items; - - protected override void OnInitialized() - { - items = GetRandomCustomers(); // Added method call to assign items - } - - private record Customer(int Id, string Name); - - private IQueryable GetRandomCustomers(int size = 500) - { - Customer[] data = new Customer[size]; - - for (int i = 0; i < size; i++) - { - - data[i] = new Customer(i, $"Customer {i} - {Guid.NewGuid().ToString("N").Substring(0, 8)}"); - } - return data.AsQueryable(); - } -} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridVirtualize.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridVirtualize.razor new file mode 100644 index 0000000000..2de9db4f94 --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridVirtualize.razor @@ -0,0 +1,83 @@ +
+ + + + + + + + +   Nothing to see here. Carry on! + + + + Loading...
+ +
+
+
+
+
+ + +Simulate data loading + +@code { + FluentDataGrid? grid; + FluentSwitch? _clearToggle; + + bool _clearItems = false; + public record SampleGridData(string Item1, string Item2, string Item3, string Item4); + + IQueryable? items = Enumerable.Empty().AsQueryable(); + + private IQueryable GenerateSampleGridData(int size) + { + SampleGridData[] data = new SampleGridData[size]; + + for (int i = 0; i < size; i++) + { + data[i] = new SampleGridData($"value {i}-1", $"value {i}-2", $"value {i}-3", $"value {i}-4"); + } + return data.AsQueryable(); + } + protected override void OnInitialized() + { + items = GenerateSampleGridData(5000); + } + + private void ToggleItems() + { + if (_clearItems) + { + items = null; + } + else + { + items = GenerateSampleGridData(5000); + } + } + + private async Task SimulateDataLoading() + { + _clearItems = false; + + items = null; + grid?.SetLoadingState(true); + + await Task.Delay(1500); + + items = GenerateSampleGridData(5000); + grid?.SetLoadingState(false); + } +} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md index e631693acd..1134ea42bf 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md @@ -5,7 +5,106 @@ route: /DataGrid # DataGrid -Overview page and examples to follow +The DataGrid actually comprises several components and used to display tabular data. The `` component is the most important +component and is the one normally put onto a page. Internally it uses the`` and `` components to build up +the grid. Technically it is possible to use these components manually, but that is not the recommended way of working with the DataGrid. + +## Rendering +The DataGrid uses standard HTML table elements for rendering the grid. It supports 2 different display modes through the `DisplayMode` +parameter: `DataGridDisplayMode.Grid` (default) and `DataGridDisplayMode.Table`. +- With the `Grid` mode, the `GridTableColumns` parameter can be +used to specify column widths in fractions. It basically provides an HTML table element with a `display: grid;` style. +- With the `Table` mode, it uses standard HTML table elements and rendering. Column widths are best specified through the `Width` parameter on the columns. + +> [!NOTE] +Specifically when using `Virtualize`, it is **highly recommended** to use the `Table` display mode as the `Grid` mode can exhibit odd scrolling behavior. + + +## Accessibility + +You can use the Arrow keys to navigate through a DataGrid. When a header cell is focused and the column is sortable, you can use the +Tab key to select the sort button. Pressing the Enter key will toggle the sorting direction. Pressing Shift + s +removes the column sorting and restores the default/start situation with regards to sorting. *You cannot remove the default grid sorting with +this key combination*. + +When a header cell is focused and the column allows setting options, you can use the Tab key to select the options button. Pressing +the Enter key then will toggle the options popover. Pressing Esc closes the popover . + +When a grid allows resizing of the columns, you can use the + and - keys to resize the column the focused header belongs +to. Incrementing/decrementing width is done in steps of 10 pixels at a time. You can reset to the original initial column widths by pressing +Shift + r. + +When a row cell is focused, the grid contains a `SelectColumn` (with `SelectFromEntireRow` parameter set to `true` (default)), you can use the Enter key to select or unselect the current +row. + +## Sorting + +The DataGrid supports sorting by clicking on the column headers (when the `HeaderCellAsButtonWithMenu` parameter is `false` (default)). The default sort direction is ascending. Clicking on the same column header +again will toggle the sort direction. When `HeaderCellAsButtonWithMenu` is true, a menu will be used to get trigger the sorting action. + +A sort can be removed by right clicking (or by pressing Shift + r) on the header column (with exception of +the default sort). + +## Row size + +The DataGrid offers a `RowSize` parameter which allows you to use different preset row heights. The value uses the `DataGridRowSize` +enumeration for its type. When using `Virtualize`, the `ItemSize` value **must** still be used to indicate the row height. + +## Change strings used in the UI + +The DataGrid has a number of strings that are used in the UI. These can be changed by leveraging the built-in [localization](/localization) functionality. +The following values can be localized: + +- - DataGrid_OptionsMenu +- DataGrid_ResizeDiscrete +- DataGrid_ResizeExact +- DataGrid_ResizeGrow +- DataGrid_ResizeMenu +- DataGrid_ResizeReset +- DataGrid_ResizeShrink +- DataGrid_ResizeSubmit +- DataGrid_SortMenu +- DataGrid_SortMenuAscending +- DataGrid_SortMenuDescending + +## Using the DataGrid component with EF Core + +If you want to use the `FluentDataGrid` with data provided through EF Core, you need to install an additional package so the grid knows how to resolve queries asynchronously for efficiency. + +### Installation +Install the package by running the command: + +```cshtml +dotnet add package Microsoft.FluentUI.AspNetCore.Components.DataGrid.EntityFrameworkAdapterCopy +``` + +### Usage +In your `Program.cs` file you need to add the following after the `builder.Services.AddFluentUIComponents();` line: + +```csharp +builder.Services.AddDataGridEntityFrameworkAdapter();Copy +``` + +## Using the DataGrid component with OData + +*Added in 4.11.0* + +If you want to use the `FluentDataGrid` with data provided through OData, you need to install an additional package so the grid knows how to resolve queries asynchronously for efficiency. + +### Installation +Install the package by running the command: + +```cshtml +dotnet add package Microsoft.FluentUI.AspNetCore.Components.DataGrid.ODataAdapterCopy +``` + +### Usage +In your `Program.cs` file you need to add the following after the `builder.Services.AddFluentUIComponents();` line: + +```csharp +builder.Services.AddDataGridODataAdapter(); +``` + ## Typical usage Here is an example of a data grid that uses in-memory data and enables features including pagination, sorting, filtering, column options, row highlighting and column resizing. @@ -25,7 +124,7 @@ Pressing enter finishes the filter action by the current input to filter on and The resize options UI is using a customized string for the label ('Width (+/- 10px)' instead of the normal 'Column width'). This is done through the custom localizer which is registered in the Server project's `Program.cs` file. -{{ DataGridVirtualization }} + {{ DataGridTypical }} diff --git a/examples/Tools/FluentUI.Demo.SampleData/FoodProductRecalls.cs b/examples/Tools/FluentUI.Demo.SampleData/FoodProductRecalls.cs index 5045497f47..fda81bc449 100644 --- a/examples/Tools/FluentUI.Demo.SampleData/FoodProductRecalls.cs +++ b/examples/Tools/FluentUI.Demo.SampleData/FoodProductRecalls.cs @@ -115,3 +115,51 @@ public class MetaResults public int Total { get; set; } } } + +#nullable disable +/// +/// Represents the data returned by https://open.fda.gov/apis/food/enforcement/ +/// This is a subset of the fields available on that API +/// +public class FoodRecall +{ + /// + public string Event_Id { get; set; } + /// + public string Status { get; set; } + /// + public string City { get; set; } + /// + public string State { get; set; } + /// + public string Recalling_Firm { get; set; } + /// + public string Termination_Date { get; set; } +} + +/// +/// Represents the result of a food recall query from the FDA API. +/// +public class FoodRecallQueryResult +{ + /// + public Metadata Meta { get; set; } + /// + public FoodRecall[] Results { get; set; } + + /// + public class Metadata + { + /// + public ResultsInfo Results { get; set; } + } + + /// + public class ResultsInfo + { + /// + public int Total { get; set; } + } +} + +#nullable enable diff --git a/src/Core/Components/DataGrid/Columns/ColumnKeyGridSort.cs b/src/Core/Components/DataGrid/Columns/ColumnKeyGridSort.cs index c2c8adc050..a4257ab7e7 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnKeyGridSort.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnKeyGridSort.cs @@ -11,11 +11,12 @@ namespace Microsoft.FluentUI.AspNetCore.Components; public sealed class ColumnKeyGridSort : IGridSort { private readonly string _columnKey; - private readonly Func, bool, IOrderedQueryable> _sortFunction; + private readonly Func, bool, IOrderedQueryable>? _sortFunction; - internal ColumnKeyGridSort( + /// + public ColumnKeyGridSort( string columnKey, - Func, bool, IOrderedQueryable> sortFunction) + Func, bool, IOrderedQueryable>? sortFunction = null) { _columnKey = columnKey; _sortFunction = sortFunction; @@ -29,13 +30,13 @@ internal ColumnKeyGridSort( /// /// The ordered collection public IOrderedQueryable Apply(IQueryable queryable, bool ascending) { - //if (_sortFunction != null) - //{ - return _sortFunction(queryable, ascending); - //} + if (_sortFunction != null) + { + return _sortFunction(queryable, ascending); + } // If no sort is provided, apply a sort that has no affect in order to be able to return an IOrderedQueryable - //return queryable.OrderBy(x => 0); + return queryable.OrderBy(x => 0); } /// diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index 730df47b52..5b7c9f1d27 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -1043,6 +1043,11 @@ public async Task OnKeyDownAsync(FluentKeyCodeEventArgs args) await ResetColumnWidthsAsync(); } + if (args.ShiftKey && args.Key == KeyCode.KeyS) + { + await RemoveSortByColumnAsync(); + } + if (string.Equals(args.Value, "-", StringComparison.Ordinal)) { await SetColumnWidthDiscreteAsync(columnIndex: null, -10); From e3c942f1dbf7205461779fcc373e97f14483dc51 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Thu, 3 Jul 2025 11:30:20 +0200 Subject: [PATCH 39/44] Process review comments --- spelling.dic | 1 - .../DataGrid/Columns/ColumnBase.razor.cs | 2 +- .../DataGrid/Columns/ColumnBase.razor.css | 18 +-- .../DataGrid/Columns/TemplateColumn.cs | 6 +- .../Components/DataGrid/FluentDataGrid.razor | 2 +- .../DataGrid/FluentDataGrid.razor.cs | 26 ++-- .../DataGrid/FluentDataGrid.razor.css | 66 ++++----- .../DataGrid/FluentDataGridCell.razor.cs | 53 +++---- .../DataGrid/FluentDataGridCell.razor.css | 135 +++++++++--------- .../DataGrid/FluentDataGridRow.razor.cs | 20 +-- .../DataGrid/FluentDataGridRow.razor.css | 8 +- .../ColumnsCollectedNotifier.cs | 5 +- .../DataGrid/Infrastructure/Defer.cs | 3 +- .../Infrastructure/IAsyncQueryExecutor.cs | 2 +- .../Infrastructure/IBindableColumn.cs | 6 +- .../Infrastructure/InternalGridContext.cs | 1 - 16 files changed, 178 insertions(+), 176 deletions(-) diff --git a/spelling.dic b/spelling.dic index 46bd281ab0..aa81caf720 100644 --- a/spelling.dic +++ b/spelling.dic @@ -1,5 +1,4 @@ -# *************************** # *************************** # List of misspelled words # Please, sort the list alphabetically diff --git a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs index b774b035f7..5ef17b4320 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs @@ -32,7 +32,7 @@ public abstract partial class ColumnBase /// /// Indicates whether the current column is the active sort column. /// - public bool IsActiveSortColumn; + internal bool IsActiveSortColumn; /// /// Gets or sets a that will be rendered for this column's header cell. diff --git a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.css b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.css index d8dfd0a8e3..f7353c5d3d 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.css +++ b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.css @@ -1,16 +1,16 @@ -.col-title { +.fluent-data-grid .col-title { padding: 6px 16px; user-select: none; } -.col-title-text { - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - font-weight: 600; +.fluent-data-grid .col-title-text { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + font-weight: 600; } -th.col-justify-center svg { - align-content: center; - text-align:center; +.fluent-data-grid th.col-justify-center svg { + align-content: center; + text-align: center; } diff --git a/src/Core/Components/DataGrid/Columns/TemplateColumn.cs b/src/Core/Components/DataGrid/Columns/TemplateColumn.cs index 5abb3122d2..58d30cb705 100644 --- a/src/Core/Components/DataGrid/Columns/TemplateColumn.cs +++ b/src/Core/Components/DataGrid/Columns/TemplateColumn.cs @@ -18,10 +18,12 @@ public class TemplateColumn : ColumnBase /// /// Gets or sets the content to be rendered for each row in the table. /// - [Parameter] public RenderFragment ChildContent { get; set; } = EmptyChildContent; + [Parameter] + public RenderFragment ChildContent { get; set; } = EmptyChildContent; /// - [Parameter] public override IGridSort? SortBy { get; set; } + [Parameter] + public override IGridSort? SortBy { get; set; } /// protected internal override void CellContent(RenderTreeBuilder builder, TGridItem item) diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor b/src/Core/Components/DataGrid/FluentDataGrid.razor index 70739aaa3a..ffdf5a1a8b 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor @@ -20,7 +20,7 @@ : FluentComponentBase, IHandleEve private bool _checkColumnOptionsPosition; private bool _checkColumnResizePosition; private bool _manualGrid; - //private IJSObjectReference? Module; - private IJSObjectReference? _jsEventDisposable; private readonly RenderFragment _renderColumnHeaders; private readonly RenderFragment _renderNonVirtualizedRows; private readonly RenderFragment _renderEmptyContent; @@ -452,7 +450,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) // Import the JavaScript module await JSModule.ImportJavaScriptModuleAsync(JAVASCRIPT_FILE); - _jsEventDisposable = await JSModule.ObjectReference.InvokeAsync("init", _gridReference, AutoFocus); + await JSModule.ObjectReference.InvokeAsync("init", _gridReference, AutoFocus); if (AutoItemsPerPage) { @@ -903,6 +901,13 @@ private string AriaSortValue(ColumnBase column) .AddStyle("width", "100%", DisplayMode == DataGridDisplayMode.Table) .Build(); + private string? GridClass => DefaultClassBuilder + .AddClass("fluent-data-grid") + .AddClass("grid", DisplayMode == DataGridDisplayMode.Grid) + .AddClass("auto-fit", AutoFit) + .AddClass("loading", _pendingDataLoadCancellationTokenSource is not null) + .Build(); + private string? ColumnHeaderClass(ColumnBase column) { return new CssBuilder(Class) @@ -912,16 +917,6 @@ private string AriaSortValue(ColumnBase column) .Build(); } - private string? GridClass() - { - return DefaultClassBuilder - .AddClass("fluent-data-grid") - .AddClass("grid", DisplayMode == DataGridDisplayMode.Grid) - .AddClass("auto-fit", AutoFit) - .AddClass("loading", _pendingDataLoadCancellationTokenSource is not null) - .Build(); - } - private static string? ColumnJustifyClass(ColumnBase column) { return new CssBuilder(column.Class) @@ -1016,11 +1011,6 @@ public async Task UpdateItemsPerPageAsync(int visibleRows) await Pagination.SetItemsPerPageAsync(visibleRows - 1); // subtract 1 for the table header } - //public void SetPageReference(Type page) - //{ - // _dotNetObjectReference = DotNetObjectReference.Create(page); - //} - /// /// Checks if key pressed should be handled /// diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.css b/src/Core/Components/DataGrid/FluentDataGrid.razor.css index 37e7df7bca..dc08949a64 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.css +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.css @@ -14,58 +14,58 @@ display: grid; } -.grid thead, -.grid tbody { - display: contents; -} + .fluent-data-grid.grid thead, + .grid tbody { + display: contents; + } -.grid tr { - display: contents; -} + .fluent-data-grid.grid tr { + display: contents; + } .fluent-data-grid tbody tr .hover { background: var(--colorNeutralBackground5); } -.col-options, .col-resize { - position: absolute; - min-width: 250px; - top: 2.7rem; - background: var(--colorNeutralBackground1); - border: 1px solid var(--colorNeutralBackground1Pressed); - border-radius: 0.3rem; - box-shadow: var(--shadow8); - padding: 12px; - visibility: hidden; - z-index: 1; -} + .fluent-data-grid .col-options, .fluent-data-grid .col-resize { + position: absolute; + min-width: 250px; + top: 2.7rem; + background: var(--colorNeutralBackground1); + border: 1px solid var(--colorNeutralBackground1Pressed); + border-radius: 0.3rem; + box-shadow: var(--shadow8); + padding: 12px; + visibility: hidden; + z-index: 1; + } -[dir=rtl] .col-options { - left: unset; +[dir=rtl] .fluent-data-grid .col-options { + left: unset; } -.col-justify-end .col-options, -.col-justify-right .col-options { - left: unset; - margin-right: 0.6rem; +.fluent-data-grid .col-justify-end .col-options, +.fluent-data-grid .col-justify-right .col-options { + left: unset; + margin-right: 0.6rem; } -[dir=rtl] .col-justify-end .col-options, -[dir=rtl] .col-justify-right .col-options { +[dir=rtl] .fluent-data-grid .col-justify-end .col-options, +[dir=rtl] .fluent-data-grid .col-justify-right .col-options { right: unset; margin-left: 0.6rem; } -.resize-options { +.fluent-data-grid .resize-options { display: flex; width: 100%; justify-content: center; align-items: center; } -.resize-handle { +.fluent-data-grid .resize-handle { position: absolute; top: 5px; right: 0; @@ -78,12 +78,12 @@ opacity: var(--fluent-data-grid-header-opacity); } -.header { - padding: 0; - z-index: 3; +.fluent-data-grid .header { + padding: 0; + z-index: 3; } -tr[row-type='sticky-header'] > th { +.fluent-data-grid tr[row-type='sticky-header'] > th { position: sticky; top: 0; background-color: var(--colorNeutralBackground4); diff --git a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs index d96a285726..d17667eee6 100644 --- a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs @@ -26,6 +26,30 @@ public FluentDataGridCell(LibraryConfiguration configuration) : base(configurati { } + /// + protected string? ClassValue => DefaultClassBuilder + .AddClass("column-header", when: CellType == DataGridCellType.ColumnHeader) + .AddClass("select-all", when: CellType == DataGridCellType.ColumnHeader && Column is SelectColumn) + .AddClass("multiline-text", when: Grid.MultiLine && (Grid.Items is not null || Grid.ItemsProvider is not null) && CellType != DataGridCellType.ColumnHeader) + .AddClass(Owner.Class) + .Build(); + + /// + protected string? StyleValue => DefaultStyleBuilder + .AddStyle("grid-column", GridColumn.ToString(CultureInfo.InvariantCulture), () => !Grid.EffectiveLoadingValue && (Grid.Items is not null || Grid.ItemsProvider is not null) && Grid.DisplayMode == DataGridDisplayMode.Grid) + .AddStyle("text-align", "center", Column is SelectColumn) + .AddStyle("align-content", "center", Column is SelectColumn) + .AddStyle("padding-top", "10px", Column is SelectColumn && (Grid.RowSize == DataGridRowSize.Medium || Owner.RowType == DataGridRowType.Header)) + .AddStyle("padding-top", "6px", Column is SelectColumn && Grid.RowSize == DataGridRowSize.Small && Owner.RowType == DataGridRowType.Default) + .AddStyle("width", Column?.Width, !string.IsNullOrEmpty(Column?.Width) && Grid.DisplayMode == DataGridDisplayMode.Table) + .AddStyle("height", $"{Grid.ItemSize.ToString(CultureInfo.InvariantCulture):0}px", () => !Grid.EffectiveLoadingValue && Grid.Virtualize) + .AddStyle("height", $"{((int)Grid.RowSize).ToString(CultureInfo.InvariantCulture)}px", () => !Grid.EffectiveLoadingValue && !Grid.Virtualize && !Grid.MultiLine && (Grid.Items is not null || Grid.ItemsProvider is not null)) + .AddStyle("height", "100%", Grid.MultiLine) + .AddStyle("min-height", "44px", Owner.RowType != DataGridRowType.Default) + .AddStyle("z-index", ZIndex.DataGridHeaderPopup.ToString(CultureInfo.InvariantCulture), CellType == DataGridCellType.ColumnHeader && Grid._columns.Count > 0 && Grid.UseMenuService) + .AddStyle(Owner.Style) + .Build(); + /// /// Gets or sets the reference to the item that holds this cell's values. /// @@ -68,33 +92,14 @@ public FluentDataGridCell(LibraryConfiguration configuration) : base(configurati /// protected FluentDataGrid Grid => InternalGridContext.Grid; - /// - protected string? ClassValue => DefaultClassBuilder - .AddClass("column-header", when: CellType == DataGridCellType.ColumnHeader) - .AddClass("select-all", when: CellType == DataGridCellType.ColumnHeader && Column is SelectColumn) - .AddClass("multiline-text", when: Grid.MultiLine && (Grid.Items is not null || Grid.ItemsProvider is not null) && CellType != DataGridCellType.ColumnHeader) - .AddClass(Owner.Class) - .Build(); - /// - protected string? StyleValue => DefaultStyleBuilder - .AddStyle("grid-column", GridColumn.ToString(CultureInfo.InvariantCulture), () => !Grid.EffectiveLoadingValue && (Grid.Items is not null || Grid.ItemsProvider is not null) && Grid.DisplayMode == DataGridDisplayMode.Grid) - .AddStyle("text-align", "center", Column is SelectColumn) - .AddStyle("align-content", "center", Column is SelectColumn) - //.AddStyle("padding-inline-start", "calc(((var(--design-unit)* 3) + var(--focus-stroke-width) - var(--stroke-width))* 1px)", Column is SelectColumn && Owner.RowType == DataGridRowType.Default) - .AddStyle("padding-top", "10px", Column is SelectColumn && (Grid.RowSize == DataGridRowSize.Medium || Owner.RowType == DataGridRowType.Header)) - .AddStyle("padding-top", "6px", Column is SelectColumn && Grid.RowSize == DataGridRowSize.Small && Owner.RowType == DataGridRowType.Default) - .AddStyle("width", Column?.Width, !string.IsNullOrEmpty(Column?.Width) && Grid.DisplayMode == DataGridDisplayMode.Table) - .AddStyle("height", $"{Grid.ItemSize.ToString(CultureInfo.InvariantCulture):0}px", () => !Grid.EffectiveLoadingValue && Grid.Virtualize) - .AddStyle("height", $"{((int)Grid.RowSize).ToString(CultureInfo.InvariantCulture)}px", () => !Grid.EffectiveLoadingValue && !Grid.Virtualize && !Grid.MultiLine && (Grid.Items is not null || Grid.ItemsProvider is not null)) - .AddStyle("height", "100%", Grid.MultiLine) - .AddStyle("min-height", "44px", Owner.RowType != DataGridRowType.Default) - .AddStyle("z-index", ZIndex.DataGridHeaderPopup.ToString(CultureInfo.InvariantCulture), CellType == DataGridCellType.ColumnHeader && Grid._columns.Count > 0 && Grid.UseMenuService) - .AddStyle(Owner.Style) - .Build(); /// - public void Dispose() => Owner.Unregister(this); + public override ValueTask DisposeAsync() + { + Owner.Unregister(this); + return base.DisposeAsync(); + } /// internal async Task HandleOnCellClickAsync() diff --git a/src/Core/Components/DataGrid/FluentDataGridCell.razor.css b/src/Core/Components/DataGrid/FluentDataGridCell.razor.css index 244b7b2367..eb20bc4a77 100644 --- a/src/Core/Components/DataGrid/FluentDataGridCell.razor.css +++ b/src/Core/Components/DataGrid/FluentDataGridCell.razor.css @@ -1,108 +1,109 @@ -th, td { +.fluent-data-grid th, +.fluent-data-grid td { border-bottom: var(--strokeWidthThin) solid var(--colorNeutralStencil1); } -td { - padding: 6px 16px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - align-content: center; +.fluent-data-grid td { + padding: 6px 16px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + align-content: center; } - td.col-justify-center { - text-align: center; - } + .fluent-data-grid td.col-justify-center { + text-align: center; + } - td.col-justify-end, - td.col-justify-right { - text-align: end; - } + .fluent-data-grid td.col-justify-end, + .fluent-data-grid td.col-justify-right { + text-align: end; + } -th.col-justify-end > div { - justify-content: flex-end; +.fluent-data-grid th.col-justify-end > div { + justify-content: flex-end; } -td.grid-cell-placeholder:after { - content: '\2026'; /*horizontal ellipsis*/ - opacity: 0.75; +.fluent-data-grid td.grid-cell-placeholder:after { + content: '\2026'; /*horizontal ellipsis*/ + opacity: 0.75; } -.empty-content-cell, -.loading-content-cell { - font-weight: 600; - text-align: center; - height: 100%; - user-select: none; +.fluent-data-grid .empty-content-cell, +.fluent-data-grid .loading-content-cell { + font-weight: 600; + text-align: center; + height: 100%; + user-select: none; } -.multiline-text { - white-space: inherit; - overflow: auto; - word-break: break-word; - align-content: start; +.fluent-data-grid .multiline-text { + white-space: inherit; + overflow: auto; + word-break: break-word; + align-content: start; } -.column-header { - font-weight: 600; - text-align: center; - position: relative; - padding: 5px 1px; +.fluent-data-grid .column-header { + font-weight: 600; + text-align: center; + position: relative; + padding: 5px 1px; } -.col-sort-button { - width: calc(100% - 20px); - overflow: hidden; - text-overflow: ellipsis; +.fluent-data-grid .col-sort-button { + width: calc(100% - 20px); + overflow: hidden; + text-overflow: ellipsis; } - .col-sort-button::part(content) { - overflow: hidden; - } +.fluent-data-grid .col-sort-button::part(content) { + overflow: hidden; +} -.col-options-button { - padding-inline-start: 4px; +.fluent-data-grid .col-options-button { + padding-inline-start: 4px; } -.col-justify-start .col-sort-button::part(control) { - justify-content: start; - overflow: hidden; - opacity: 1 +.fluent-data-grid .col-justify-start .col-sort-button::part(control) { + justify-content: start; + overflow: hidden; + opacity: 1 } -.col-justify-center .col-sort-button::part(control) { - justify-content: center; - overflow: hidden; - opacity: 1 +.fluent-data-grid .col-justify-center .col-sort-button::part(control) { + justify-content: center; + overflow: hidden; + opacity: 1 } -.col-justify-end .col-sort-button::part(control) { - justify-content: end; - overflow: hidden; - opacity: 1 +.fluent-data-grid .col-justify-end .col-sort-button::part(control) { + justify-content: end; + overflow: hidden; + opacity: 1 } -.col-justify-end .col-sort-button::part(start) { - margin-inline-end: 2px; +.fluent-data-grid .col-justify-end .col-sort-button::part(start) { + margin-inline-end: 2px; } -.col-justify-start .col-sort-button::part(end), -.col-justify-center .col-sort-button::part(end) { - margin-inline-start: 2px; +.fluent-data-grid .col-justify-start .col-sort-button::part(end), +.fluent-data-grid .col-justify-center .col-sort-button::part(end) { + margin-inline-start: 2px; } -.col-justify-start .col-title { - text-align: left; +.fluent-data-grid .col-title { + text-align: left; } -.col-justify-center .col-title { - text-align: center; +.fluent-data-grid .col-justify-center .col-title { + text-align: center; } -.col-justify-end .col-title { - text-align: end; +.fluent-data-grid .col-justify-end .col-title { + text-align: end; } diff --git a/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs b/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs index c112d477f8..8b515e45bb 100644 --- a/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs @@ -28,6 +28,16 @@ public FluentDataGridRow(LibraryConfiguration configuration) : base(configuratio { } + /// + protected string? ClassValue => DefaultClassBuilder + .AddClass("fluent-data-grid-row") + .AddClass("hover", when: Grid.ShowHover) + .Build(); + + /// + protected string? StyleValue => DefaultStyleBuilder + .Build(); + /// /// Gets or sets the reference to the item that holds this row's values. /// @@ -76,16 +86,6 @@ public FluentDataGridRow(LibraryConfiguration configuration) : base(configuratio /// protected FluentDataGrid Grid => InternalGridContext.Grid; - /// - protected string? ClassValue => DefaultClassBuilder - .AddClass("fluent-data-grid-row") - .AddClass("hover", when: Grid.ShowHover) - .Build(); - - /// - protected string? StyleValue => DefaultStyleBuilder - .Build(); - /// /// Sets the RowIndex for this row. /// diff --git a/src/Core/Components/DataGrid/FluentDataGridRow.razor.css b/src/Core/Components/DataGrid/FluentDataGridRow.razor.css index 361826c2ce..3534d166d5 100644 --- a/src/Core/Components/DataGrid/FluentDataGridRow.razor.css +++ b/src/Core/Components/DataGrid/FluentDataGridRow.razor.css @@ -1,8 +1,10 @@ -.sticky-header { - z-index: 3; +.fluent-data-grid .sticky-header { + z-index: 3; } -.hover:not([row-type='header'],[row-type='sticky-header'],.loading-content-row):hover td:not(.empty-content-cell) { +.fluent-data-grid .hover:not([row-type='header'], +.fluent-data-grid [row-type='sticky-header'], +.fluent-data-grid .loading-content-row):hover td:not(.empty-content-cell) { cursor: pointer; background-color: var(--datagrid-hover-color, var(--colorNeutralStroke2)); } diff --git a/src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs b/src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs index e01b29ec24..45a6db93b2 100644 --- a/src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs +++ b/src/Core/Components/DataGrid/Infrastructure/ColumnsCollectedNotifier.cs @@ -41,7 +41,8 @@ public sealed class ColumnsCollectedNotifier : Microsoft.AspNetCore.C { private bool _isFirstRender = true; - [CascadingParameter] internal InternalGridContext InternalGridContext { get; set; } = default!; + [CascadingParameter] + internal InternalGridContext InternalGridContext { get; set; } = default!; /// public void Attach(RenderHandle renderHandle) @@ -56,7 +57,7 @@ public Task SetParametersAsync(ParameterView parameters) { _isFirstRender = false; parameters.SetParameterProperties(this); - return InternalGridContext.ColumnsFirstCollected.InvokeCallbacksAsync(null); + return InternalGridContext.ColumnsFirstCollected.InvokeCallbacksAsync(eventArg: null); } return Task.CompletedTask; diff --git a/src/Core/Components/DataGrid/Infrastructure/Defer.cs b/src/Core/Components/DataGrid/Infrastructure/Defer.cs index c6500f5371..da42c48088 100644 --- a/src/Core/Components/DataGrid/Infrastructure/Defer.cs +++ b/src/Core/Components/DataGrid/Infrastructure/Defer.cs @@ -20,7 +20,8 @@ public sealed class Defer : ComponentBase /// /// For internal use only. Do not use. /// - [Parameter] public RenderFragment? ChildContent { get; set; } + [Parameter] + public RenderFragment? ChildContent { get; set; } /// /// For internal use only. Do not use. diff --git a/src/Core/Components/DataGrid/Infrastructure/IAsyncQueryExecutor.cs b/src/Core/Components/DataGrid/Infrastructure/IAsyncQueryExecutor.cs index 971c3e17c3..15c7830991 100644 --- a/src/Core/Components/DataGrid/Infrastructure/IAsyncQueryExecutor.cs +++ b/src/Core/Components/DataGrid/Infrastructure/IAsyncQueryExecutor.cs @@ -7,7 +7,7 @@ namespace Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; /// /// Provides methods for asynchronous evaluation of queries against an . /// -public interface IAsyncQueryExecutor +internal interface IAsyncQueryExecutor { /// /// Determines whether the is supported by this type. diff --git a/src/Core/Components/DataGrid/Infrastructure/IBindableColumn.cs b/src/Core/Components/DataGrid/Infrastructure/IBindableColumn.cs index 9ddfaae58a..c90321943a 100644 --- a/src/Core/Components/DataGrid/Infrastructure/IBindableColumn.cs +++ b/src/Core/Components/DataGrid/Infrastructure/IBindableColumn.cs @@ -10,7 +10,7 @@ namespace Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure; /// /// A column that can bind to a property of model /// -public interface IBindableColumn +internal interface IBindableColumn { /// /// The info for the property that this column binds to. @@ -24,6 +24,8 @@ public interface IBindableColumn /// Type of property internal interface IBindableColumn : IBindableColumn { - + /// + /// The expression that represents the property to which this column binds. + /// public Expression> Property { get; set; } } diff --git a/src/Core/Components/DataGrid/Infrastructure/InternalGridContext.cs b/src/Core/Components/DataGrid/Infrastructure/InternalGridContext.cs index 2d737d4c20..6d0f1a600e 100644 --- a/src/Core/Components/DataGrid/Infrastructure/InternalGridContext.cs +++ b/src/Core/Components/DataGrid/Infrastructure/InternalGridContext.cs @@ -16,7 +16,6 @@ internal sealed class InternalGridContext(FluentDataGrid g private int _cellId; public (ColumnBase? Column, DataGridSortDirection? Direction) DefaultSortColumn { get; set; } - //public SortDirection? DefaultSortDirection { get; set; } public Dictionary> Rows { get; set; } = []; From f6d458babab5aed1b4333f59d1648b68b4721eb0 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Thu, 3 Jul 2025 11:34:35 +0200 Subject: [PATCH 40/44] More review comments --- src/Core/Components/DataGrid/GridItemsProvider.cs | 4 ++-- src/Core/Components/DataGrid/GridItemsProviderRequest.cs | 2 +- src/Core/Components/DataGrid/GridItemsProviderResult.cs | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Core/Components/DataGrid/GridItemsProvider.cs b/src/Core/Components/DataGrid/GridItemsProvider.cs index d7376a73c3..8780c60d51 100644 --- a/src/Core/Components/DataGrid/GridItemsProvider.cs +++ b/src/Core/Components/DataGrid/GridItemsProvider.cs @@ -9,6 +9,6 @@ namespace Microsoft.FluentUI.AspNetCore.Components; /// /// The type of data represented by each row in the grid. /// Parameters describing the data being requested. -/// A that gives the data to be displayed. -public delegate ValueTask> GridItemsProvider( +/// A that gives the data to be displayed. +internal delegate ValueTask> GridItemsProvider( GridItemsProviderRequest request); diff --git a/src/Core/Components/DataGrid/GridItemsProviderRequest.cs b/src/Core/Components/DataGrid/GridItemsProviderRequest.cs index dbaacf38e3..52aed5ec7c 100644 --- a/src/Core/Components/DataGrid/GridItemsProviderRequest.cs +++ b/src/Core/Components/DataGrid/GridItemsProviderRequest.cs @@ -10,7 +10,7 @@ namespace Microsoft.FluentUI.AspNetCore.Components; /// Parameters for data to be supplied by a 's . /// /// The type of data represented by each row in the grid. -public readonly struct GridItemsProviderRequest +internal readonly struct GridItemsProviderRequest { /// /// Gets or sets the zero-based index of the first item to be supplied. diff --git a/src/Core/Components/DataGrid/GridItemsProviderResult.cs b/src/Core/Components/DataGrid/GridItemsProviderResult.cs index 3dfd03e3ab..2fc137045f 100644 --- a/src/Core/Components/DataGrid/GridItemsProviderResult.cs +++ b/src/Core/Components/DataGrid/GridItemsProviderResult.cs @@ -8,7 +8,7 @@ namespace Microsoft.FluentUI.AspNetCore.Components; /// Holds data being supplied to a 's . /// /// The type of data represented by each row in the grid. -public readonly struct GridItemsProviderResult +internal readonly struct GridItemsProviderResult { /// /// Gets or sets the items being supplied. @@ -27,7 +27,7 @@ public readonly struct GridItemsProviderResult /// /// Provides convenience methods for constructing instances. /// -public static class GridItemsProviderResult +internal static class GridItemsProviderResult { // This is just to provide generic type inference, so you don't have to specify TGridItem yet again. @@ -36,7 +36,7 @@ public static class GridItemsProviderResult /// /// The type of data represented by each row in the grid. /// The items being supplied. - /// The total numer of items that exist. See for details. + /// The total number of items that exist. See for details. /// An instance of . public static GridItemsProviderResult From(ICollection items, int totalItemCount) => new() { Items = items, TotalItemCount = totalItemCount }; From 85154bff1429ec84cda796b67a419bbc198ce629 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Thu, 3 Jul 2025 12:26:16 +0200 Subject: [PATCH 41/44] - Undo make internal GridItemsProvider* - Implement SelectColumn localization - Fix CSS issue with select column --- .../DataGrid/Columns/ColumnBase.razor.cs | 3 +- .../DataGrid/Columns/SelectColumn.cs | 36 +++++++++++-------- .../DataGrid/FluentDataGrid.razor.cs | 1 + .../DataGrid/FluentDataGridCell.razor.cs | 2 -- .../DataGrid/FluentDataGridCell.razor.css | 10 +++--- .../Components/DataGrid/GridItemsProvider.cs | 2 +- .../DataGrid/GridItemsProviderRequest.cs | 2 +- .../DataGrid/GridItemsProviderResult.cs | 2 +- src/Core/Localization/LanguageResource.resx | 15 ++++++++ 9 files changed, 47 insertions(+), 26 deletions(-) diff --git a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs index 5ef17b4320..cb8f5edb01 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs @@ -23,8 +23,9 @@ public abstract partial class ColumnBase private readonly string _columnId = Identifier.NewId(); private FluentMenu? _menu; + /// [Inject] - private IFluentLocalizer Localizer { get; set; } = default!; + protected IFluentLocalizer Localizer { get; set; } = default!; [CascadingParameter] internal InternalGridContext InternalGridContext { get; set; } = default!; diff --git a/src/Core/Components/DataGrid/Columns/SelectColumn.cs b/src/Core/Components/DataGrid/Columns/SelectColumn.cs index 346354494e..590c417bcf 100644 --- a/src/Core/Components/DataGrid/Columns/SelectColumn.cs +++ b/src/Core/Components/DataGrid/Columns/SelectColumn.cs @@ -46,6 +46,13 @@ public SelectColumn() protected override void OnInitialized() { _itemsChanged.SubscribeOrMove(InternalGridContext.ItemsChanged); + + TitleAllChecked = Localizer[Localization.LanguageResource.DataGrid_SelectColumn_AllChecked]; + TitleAllUnchecked = Localizer[Localization.LanguageResource.DataGrid_SelectColumn_AllUnchecked]; + TitleAllIndeterminate = Localizer[Localization.LanguageResource.DataGrid_SelectColumn_AllIndeterminate]; + TitleChecked = Localizer[Localization.LanguageResource.DataGrid_SelectColumn_RowChecked]; + TitleUnchecked = Localizer[Localization.LanguageResource.DataGrid_SelectColumn_RowUnchecked]; + base.OnInitialized(); } @@ -123,58 +130,57 @@ public DataGridSelectMode SelectMode } /// - /// Gets or sets the Icon to be rendered when the row is non selected. + /// Gets or sets the Icon to be rendered when the row is selected. /// [Parameter] - public Icon? IconUnchecked { get; set; } + public Icon? IconChecked { get; set; } /// - /// Gets or sets the Icon title display as a tooltip and used with Accessibility. - /// The default text is "Row unselected". + /// Gets or sets the Icon to be rendered when the row is non selected. /// [Parameter] - public string TitleUnchecked { get; set; } = "Row unselected"; + public Icon? IconUnchecked { get; set; } /// - /// Gets or sets the Icon to be rendered when the row is selected. + /// Gets or sets the Icon to be rendered when some but not all rows are selected. + /// Only when is Multiple. /// [Parameter] - public Icon? IconChecked { get; set; } + public Icon? IconIndeterminate { get; set; } = new CoreIcons.Filled.Size20.CheckboxIndeterminate().WithColor(Color.Primary); /// /// Gets or sets the Icon title display as a tooltip and used with Accessibility. /// The default text is "Row selected". /// [Parameter] - public string TitleChecked { get; set; } = "Row selected."; - + public string? TitleChecked { get; set; } /// - /// Gets or sets the Icon to be rendered when some but not all rows are selected. - /// Only when is Multiple. + /// Gets or sets the Icon title display as a tooltip and used with Accessibility. + /// The default text is "Row unselected". /// [Parameter] - public Icon? IconIndeterminate { get; set; } = new CoreIcons.Filled.Size20.CheckboxIndeterminate().WithColor(Color.Primary); + public string? TitleUnchecked { get; set; } /// /// Gets or sets the Icon title display as a tooltip and used with Accessibility. /// The default text is "All rows are selected.". /// [Parameter] - public string TitleAllChecked { get; set; } = "All rows are selected."; + public string? TitleAllChecked { get; set; } /// /// Gets or sets the Icon title display as a tooltip and used with Accessibility. /// The default text is "No rows are selected.". /// [Parameter] - public string TitleAllUnchecked { get; set; } = "No rows are selected."; + public string? TitleAllUnchecked { get; set; } /// /// Gets or sets the Icon title display as a tooltip and used with Accessibility. /// The default text is "Some rows are selected.". /// [Parameter] - public string TitleAllIndeterminate { get; set; } = "Some rows are selected."; + public string? TitleAllIndeterminate { get; set; } /// /// Gets or sets the action to be executed when the row is selected or unselected. diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index 9175ddbdb8..d254ba7f85 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -923,6 +923,7 @@ private string AriaSortValue(ColumnBase column) .AddClass("col-justify-start", column.Align == DataGridCellAlignment.Start) .AddClass("col-justify-center", column.Align == DataGridCellAlignment.Center) .AddClass("col-justify-end", column.Align == DataGridCellAlignment.End) + .AddClass("col-select", column.GetType() == typeof(SelectColumn)) .Build(); } diff --git a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs index d17667eee6..d094b050c6 100644 --- a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs @@ -92,8 +92,6 @@ public FluentDataGridCell(LibraryConfiguration configuration) : base(configurati /// protected FluentDataGrid Grid => InternalGridContext.Grid; - - /// public override ValueTask DisposeAsync() { diff --git a/src/Core/Components/DataGrid/FluentDataGridCell.razor.css b/src/Core/Components/DataGrid/FluentDataGridCell.razor.css index eb20bc4a77..f487165838 100644 --- a/src/Core/Components/DataGrid/FluentDataGridCell.razor.css +++ b/src/Core/Components/DataGrid/FluentDataGridCell.razor.css @@ -3,7 +3,7 @@ border-bottom: var(--strokeWidthThin) solid var(--colorNeutralStencil1); } -.fluent-data-grid td { +.fluent-data-grid td:not(.col-select) { padding: 6px 16px; overflow: hidden; text-overflow: ellipsis; @@ -60,7 +60,7 @@ text-overflow: ellipsis; } -.fluent-data-grid .col-sort-button::part(content) { +.fluent-data-grid .col-sort-button { overflow: hidden; } @@ -68,19 +68,19 @@ padding-inline-start: 4px; } -.fluent-data-grid .col-justify-start .col-sort-button::part(control) { +.fluent-data-grid .col-justify-start .col-sort-button { justify-content: start; overflow: hidden; opacity: 1 } -.fluent-data-grid .col-justify-center .col-sort-button::part(control) { +.fluent-data-grid .col-justify-center .col-sort-button { justify-content: center; overflow: hidden; opacity: 1 } -.fluent-data-grid .col-justify-end .col-sort-button::part(control) { +.fluent-data-grid .col-justify-end .col-sort-button { justify-content: end; overflow: hidden; opacity: 1 diff --git a/src/Core/Components/DataGrid/GridItemsProvider.cs b/src/Core/Components/DataGrid/GridItemsProvider.cs index 8780c60d51..be917d84a0 100644 --- a/src/Core/Components/DataGrid/GridItemsProvider.cs +++ b/src/Core/Components/DataGrid/GridItemsProvider.cs @@ -10,5 +10,5 @@ namespace Microsoft.FluentUI.AspNetCore.Components; /// The type of data represented by each row in the grid. /// Parameters describing the data being requested. /// A that gives the data to be displayed. -internal delegate ValueTask> GridItemsProvider( +public delegate ValueTask> GridItemsProvider( GridItemsProviderRequest request); diff --git a/src/Core/Components/DataGrid/GridItemsProviderRequest.cs b/src/Core/Components/DataGrid/GridItemsProviderRequest.cs index 52aed5ec7c..dbaacf38e3 100644 --- a/src/Core/Components/DataGrid/GridItemsProviderRequest.cs +++ b/src/Core/Components/DataGrid/GridItemsProviderRequest.cs @@ -10,7 +10,7 @@ namespace Microsoft.FluentUI.AspNetCore.Components; /// Parameters for data to be supplied by a 's . /// /// The type of data represented by each row in the grid. -internal readonly struct GridItemsProviderRequest +public readonly struct GridItemsProviderRequest { /// /// Gets or sets the zero-based index of the first item to be supplied. diff --git a/src/Core/Components/DataGrid/GridItemsProviderResult.cs b/src/Core/Components/DataGrid/GridItemsProviderResult.cs index 2fc137045f..34a9c7c83b 100644 --- a/src/Core/Components/DataGrid/GridItemsProviderResult.cs +++ b/src/Core/Components/DataGrid/GridItemsProviderResult.cs @@ -8,7 +8,7 @@ namespace Microsoft.FluentUI.AspNetCore.Components; /// Holds data being supplied to a 's . /// /// The type of data represented by each row in the grid. -internal readonly struct GridItemsProviderResult +public readonly struct GridItemsProviderResult { /// /// Gets or sets the items being supplied. diff --git a/src/Core/Localization/LanguageResource.resx b/src/Core/Localization/LanguageResource.resx index bb9927bf5c..3de8c4cca9 100644 --- a/src/Core/Localization/LanguageResource.resx +++ b/src/Core/Localization/LanguageResource.resx @@ -279,4 +279,19 @@ Hide this message + + All rows are selected + + + No rows areselected + + + Some rows are selected + + + Row selected + + + Row not selected + \ No newline at end of file From ed02449b3a3e02f56f8df04e388c5bf21343be38 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Thu, 3 Jul 2025 12:39:31 +0200 Subject: [PATCH 42/44] Update test verified files --- .../DataGrid/FluentDataGridCellTests.razor | 2 +- ...lect_Customized_Rendering.verified.razor.html | 8 ++++---- ...sts_MultiSelect_Rendering.verified.razor.html | 16 ++++++++-------- ...ts_SingleSelect_Rendering.verified.razor.html | 14 +++++++------- ...gleStickySelect_Rendering.verified.razor.html | 14 +++++++------- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/tests/Core/Components/DataGrid/FluentDataGridCellTests.razor b/tests/Core/Components/DataGrid/FluentDataGridCellTests.razor index 62e88da7fb..a01c62587e 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridCellTests.razor +++ b/tests/Core/Components/DataGrid/FluentDataGridCellTests.razor @@ -174,7 +174,7 @@ // Act var cell = grid.FindComponent>(); - cell.Instance.Dispose(); + cell.Instance.DisposeAsync(); cell.Dispose(); // Assert diff --git a/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_MultiSelect_Customized_Rendering.verified.razor.html b/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_MultiSelect_Customized_Rendering.verified.razor.html index a0e609b01e..700eac4e11 100644 --- a/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_MultiSelect_Customized_Rendering.verified.razor.html +++ b/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_MultiSelect_Customized_Rendering.verified.razor.html @@ -2,7 +2,7 @@
- - - + - + diff --git a/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_MultiSelect_Rendering.verified.razor.html b/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_MultiSelect_Rendering.verified.razor.html index 620eff3828..8974a8ccc7 100644 --- a/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_MultiSelect_Rendering.verified.razor.html +++ b/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_MultiSelect_Rendering.verified.razor.html @@ -2,9 +2,9 @@
+
@@ -16,16 +16,16 @@
+ Jean Martin
Kenji Sato
Julie Smith
- - - - diff --git a/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_SingleSelect_Rendering.verified.razor.html b/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_SingleSelect_Rendering.verified.razor.html index ecbe61da3e..8e92f37643 100644 --- a/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_SingleSelect_Rendering.verified.razor.html +++ b/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_SingleSelect_Rendering.verified.razor.html @@ -2,7 +2,7 @@
+ @@ -20,27 +20,27 @@
+ Jean Martin
+ Kenji Sato
+
- + - - - diff --git a/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_SingleStickySelect_Rendering.verified.razor.html b/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_SingleStickySelect_Rendering.verified.razor.html index 11a584ec4b..4a1b203cee 100644 --- a/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_SingleStickySelect_Rendering.verified.razor.html +++ b/tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_SingleStickySelect_Rendering.verified.razor.html @@ -2,7 +2,7 @@
@@ -14,27 +14,27 @@
+ Jean Martin
+ Kenji Sato
+
- + - - - From 36dc27bab15a2ab5f3cc5d82836703510fe86857 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Thu, 3 Jul 2025 17:47:56 +0200 Subject: [PATCH 43/44] - Fix when to show header menu - Make all examples work - Create example pages --- .../Examples/DataGridCustomComparer.razor | 10 ++-- .../Examples/DataGridCustomPaging.razor | 6 +-- ...ataGridNotVirtualizedLoadingAndEmpty.razor | 4 +- .../Examples/DataGridRemoteData.razor | 4 +- .../Examples/DataGridRemoteData2.razor | 2 +- .../DataGridTemplateColumns.razor.css | 6 --- .../DataGrid/Examples/DataGridTypical.razor | 26 ++++++++- .../Examples/DataGridVirtualize.razor | 4 +- .../Components/DataGrid/FluentDataGrid.md | 45 +++++++++++++--- .../DataGrid/Pages/DataGridAutoFitPage.md | 25 +++++++++ .../Pages/DataGridAutoItemsPerPagePage.md | 54 +++++++++++++++++++ .../DataGridColumnHeaderGenerationPage.md | 25 +++++++++ .../Pages/DataGridCustomComparerPage.md | 25 +++++++++ .../Pages/DataGridCustomGridSortPage.md | 22 ++++++++ .../Pages/DataGridCustomPagingPage.md | 11 ++++ .../Pages/DataGridDynamicColumnsPage.md | 12 +++++ .../Pages/DataGridGettingStartedPage.md | 12 +++++ .../DataGrid/Pages/DataGridManualPage.md | 10 ++++ .../DataGrid/Pages/DataGridMultiSelectPage.md | 52 ++++++++++++++++++ .../Pages/DataGridMultilineTextPage.md | 10 ++++ ...taGridNotVirtualizedLoadingAndEmptyPage.md | 12 +++++ .../DataGrid/Pages/DataGridRemoteDataPage.md | 41 ++++++++++++++ .../Pages/DataGridTableScrollbarsPage.md | 13 +++++ .../Pages/DataGridTemplateColumns2Page.md | 11 ++++ .../Pages/DataGridTemplateColumnsPage.md | 11 ++++ .../DataGrid/Pages/DataGridTypicalPage.md | 21 ++++++++ .../DataGrid/Pages/DataGridVirtualizePage.md | 21 ++++++++ .../FoodProductRecalls.cs | 6 +-- spelling.dic | 3 ++ .../DataGrid/Columns/ColumnBase.razor.cs | 32 +++++++---- .../DataGrid/FluentDataGrid.razor.cs | 2 +- .../DataGrid/GridItemsProviderResult.cs | 2 +- 32 files changed, 497 insertions(+), 43 deletions(-) delete mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTemplateColumns.razor.css create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridAutoFitPage.md create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridAutoItemsPerPagePage.md create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridColumnHeaderGenerationPage.md create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridCustomComparerPage.md create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridCustomGridSortPage.md create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridCustomPagingPage.md create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridDynamicColumnsPage.md create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridGettingStartedPage.md create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridManualPage.md create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridMultiSelectPage.md create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridMultilineTextPage.md create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridNotVirtualizedLoadingAndEmptyPage.md create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridRemoteDataPage.md create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTableScrollbarsPage.md create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTemplateColumns2Page.md create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTemplateColumnsPage.md create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTypicalPage.md create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridVirtualizePage.md diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomComparer.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomComparer.razor index 34e96dc22c..f6ef843bc8 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomComparer.razor +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomComparer.razor @@ -39,7 +39,7 @@ OnRowFocus="HandleRowFocus" GridTemplateColumns="0.2fr 1fr 0.2fr 0.2fr 0.2fr 0.2fr" ShowHover="true"> - + Flag of @(context.Code) @@ -59,10 +59,10 @@ @(context.Name) *@ - - - - + + + + diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomPaging.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomPaging.razor index 86928c18a1..87f53dc909 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomPaging.razor +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomPaging.razor @@ -3,9 +3,9 @@
- - - + + +
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridNotVirtualizedLoadingAndEmpty.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridNotVirtualizedLoadingAndEmpty.razor index 1f9bb8cd87..cdb34f370d 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridNotVirtualizedLoadingAndEmpty.razor +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridNotVirtualizedLoadingAndEmpty.razor @@ -3,8 +3,8 @@ - - + + diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridRemoteData.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridRemoteData.razor index cba0ea5115..c507ecb3a9 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridRemoteData.razor +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridRemoteData.razor @@ -16,7 +16,7 @@ - + @@ -32,7 +32,7 @@ protected override async Task OnInitializedAsync() { // Define the GridRowsDataProvider. Its job is to convert QuickGrid's GridRowsDataProviderRequest into a query against - // an arbitrary data soure. In this example, we need to translate query parameters into the particular URL format + // an arbitrary data source. In this example, we need to translate query parameters into the particular URL format // supported by the external JSON API. It's only possible to perform whatever sorting/filtering/etc is supported // by the external API. foodRecallProvider = async req => diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridRemoteData2.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridRemoteData2.razor index 08d8b161f1..d5da2ab45c 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridRemoteData2.razor +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridRemoteData2.razor @@ -43,7 +43,7 @@ - + diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTemplateColumns.razor.css b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTemplateColumns.razor.css deleted file mode 100644 index f312941e67..0000000000 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTemplateColumns.razor.css +++ /dev/null @@ -1,6 +0,0 @@ -/* Ensure all the flags are the same size, and centered */ -.flag { - height: 1rem; - margin: auto; - border: 1px solid var(--neutral-layer-3); -} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor index d8032f99a6..02e77a470b 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor @@ -21,7 +21,20 @@ @@ -109,11 +122,20 @@ private async Task HandleCloseFilterAsync(KeyboardEventArgs args) { + if (grid is null) + { + return; + } + if (args.Key == "Escape") { + if (string.IsNullOrEmpty(nameFilter)) + { + await grid.CloseColumnOptionsAsync(); + } nameFilter = string.Empty; } - if (args.Key == "Enter" && grid is not null) + if (args.Key == "Enter") { await grid.CloseColumnOptionsAsync(); } diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridVirtualize.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridVirtualize.razor index 2de9db4f94..e2f41ce173 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridVirtualize.razor +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridVirtualize.razor @@ -9,8 +9,8 @@ - - + +   Nothing to see here. Carry on! diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md index 98515ee2e4..8790673475 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md @@ -55,7 +55,7 @@ enumeration for its type. When using `Virtualize`, the `ItemSize` value **must** The DataGrid has a number of strings that are used in the UI. These can be changed by leveraging the built-in [localization](/localization) functionality. The following values can be localized: -- - DataGrid_OptionsMenu +- DataGrid_OptionsMenu - DataGrid_ResizeDiscrete - DataGrid_ResizeExact - DataGrid_ResizeGrow @@ -63,10 +63,17 @@ The following values can be localized: - DataGrid_ResizeReset - DataGrid_ResizeShrink - DataGrid_ResizeSubmit +- DataGrid_SelectColumn_AllChecked +- DataGrid_SelectColumn_AllIndeterminate +- DataGrid_SelectColumn_AllUnchecked +- DataGrid_SelectColumn_RowChecked +- DataGrid_SelectColumn_RowUnchecked - DataGrid_SortMenu - DataGrid_SortMenuAscending - DataGrid_SortMenuDescending + + ## Using the DataGrid component with EF Core If you want to use the `FluentDataGrid` with data provided through EF Core, you need to install an additional package so the grid knows how to resolve queries asynchronously for efficiency. @@ -142,8 +149,34 @@ Pressing enter finishes the filter action by the current input to filter on and The resize options UI is using a customized string for the label ('Width (+/- 10px)' instead of the normal 'Column width'). This is done through the custom localizer which is registered in the Server project's `Program.cs` file. -{{ DataGridVirtualization }} - -{{ DataGridTypical }} - -{{ DataGridMultiSelect }} +## Examples +The following examples show how to use the DataGrid component in different scenarios: + +### Basics +- [Getting started](/DataGrid/GettingStarted) +- [Typical grid usage](/DataGrid/Typical) + +### Layout +- [Loading and empty content](/DataGrid/LoadingAndEmptyContent) +- [Auto fit columns](/DataGrid/AutoFit) +- [Auto items per page](/DataGrid/AutoItemsPerPage) +- [Custom paging](/DataGrid/CustomPaging) +- [Multi line text in cells](/DataGrid/MultiLine) +- [Table with scrollbars](/DataGrid/Scrollbars) + +### Sorting +- [Sorting](/DataGrid/Sorting) +- [Custom comparer for sorting](/DataGrid/CustomComparerSort) + +### Columns +- [Single/Multi select](/DataGrid/SingleMultiSelect) +- [Dynamic columns](/DataGrid/DynamicColumns)] +- [Header generation](/DataGrid/HeaderGeneration) +- [Template columns](/DataGrid/TemplateColumns) +- [Template columns 2](/DataGrid/TemplateColumns2) + +### Advanced +- [Custom comparer](/DataGrid/CustomComparerSort) +- [Virtualized grid](/DataGrid/Virtualize) +- [Remote data](/DataGrid/RemoteData) +- [Manual grid](/DataGrid/Manual) diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridAutoFitPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridAutoFitPage.md new file mode 100644 index 0000000000..fd0c5a3afa --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridAutoFitPage.md @@ -0,0 +1,25 @@ +--- +title: Auto fit columns +route: /DataGrid/AutoFit +--- + +# Auto fit columns +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. + + +{{ DataGridAutoFit }} }} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridAutoItemsPerPagePage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridAutoItemsPerPagePage.md new file mode 100644 index 0000000000..7370822ac1 --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridAutoItemsPerPagePage.md @@ -0,0 +1,54 @@ +--- +title: Auto items per page +route: /DataGrid/AutoItemsPerPage +--- + +# Auto items per page + +The example and code below show what you need to get auto items per page functionality for the pagination of a datagrid. + +Resize the page vertically to see the number of rows being displayed per page adapt to the available height. + +The `AutoItemsPerPage` parameter must be set to true and obviously `Pagination` must be used as well for this to work. + +Also, the DataGrid **container** must have styling that makes it automatically adapt to the available height. + +An example of how that can be done for this demo site layout is shown in the ` +``` + +{{ DataGridAutoItemsPerPage Files=Code:DataGridAutoItemsPerPage.razor;CustomCSS.css:CustomCSS.css}} + diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridColumnHeaderGenerationPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridColumnHeaderGenerationPage.md new file mode 100644 index 0000000000..d719ef9b4e --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridColumnHeaderGenerationPage.md @@ -0,0 +1,25 @@ +--- +title: Header generation +route: /DataGrid/HeaderGeneration +--- + + +# Header generation + +## Use a DisplayAttribute +The DataGrid can generate column headers by using the `System.ComponentModel.DataAnnotations.DisplayAttribute` on properties +shown in the grid. + +See the 'Razor' tab on how these attributes have been applied to the class properties. + +{{ DataGridColumnHeaderGeneration}} + +The DataGrid can display custom column header titles by setting the `HeaderCellTitleTemplate` property on columns +shown in the grid. + +## Custom headers +{{ DataGridCustomHeader} } + +See the 'Razor' tab on how these properties have been applied to the columns. + +{{ DataGridCustomHeader }} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridCustomComparerPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridCustomComparerPage.md new file mode 100644 index 0000000000..598b504551 --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridCustomComparerPage.md @@ -0,0 +1,25 @@ +--- +title: Custom comparer for sorting +route: /DataGrid/CustomComparerSort +--- + +# Custom comparer for sorting + +Here a custom comparer is being used to sort counties by the length of their name. The code has examples for both +`PropertyColumn` and `TemplateColumn` implementations (see the Razor tab). + +For this example the code for the comparer is placed in the `DataGridCustomComparer.razor.cs` file but it +could of course be placed in its own file. + +For the paginator, this example also shows how to use the `SummaryTemplate` and `PaginationTextTemplate` parameters. + +This example also shows using an `OnRowFocus` event callback to detect which row the cursor is over. By setting `ShowHover` +to true, the current row will be highlighted. By default the system will use the designated hover color for this but you can specify an alternative +color by setting the `--datagrid-hover-color` CSS variable. See the Razor tab for how this is done in this example. + +To show how the resize handle can be altered, when choosing to use the alternate hover color, the handle color is set to a different value. + +[!Note]: once a value has been selected for the Resize type, it cannot be set to null again. You need to refresh the page to start with a null value again.

+ + +{{ DataGridCustomComparer Files=Code:DataGridCustomComparer.razor;CSS:DataGridCustomComparer.razor.css }} }} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridCustomGridSortPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridCustomGridSortPage.md new file mode 100644 index 0000000000..1724b58fae --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridCustomGridSortPage.md @@ -0,0 +1,22 @@ +--- +title: Custom sort +route: /DataGrid/CustomSort +--- + +# Custom sort + +## Sort by Rank +Here is an example that demonstrates some of the sort order specification that are available in the GridSort class. + +{{ DataGridCustomGridSort }} + +## Column Key Sort +In some instances, your dataset may not have a property on the class that can be used for sorting. +In this instance, you can supply a ColumnKeyGridSort that allows you to specify the name of your column and supply the logic for sorting. + +{{ DataGridColumnKeyGridSort } }} + +## Documentation + +{{ API Type=GridSort }} + diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridCustomPagingPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridCustomPagingPage.md new file mode 100644 index 0000000000..1ceef6fd00 --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridCustomPagingPage.md @@ -0,0 +1,11 @@ +--- +title: Custom Paging +route: /DataGrid/CustomPaging +--- + +# Custom Paging + +## Custom paging UI +You can customize the appearance of `Paginator` by supplying a `SummaryTemplate`. If you want further customization, you can create your own alternative UI that works with `PaginationState`. Example: + +{{ DataGridCustomPaging Files=Code:DataGridCustomPaging.razor;CSS:DataGridCustomPaging.razor.css }} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridDynamicColumnsPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridDynamicColumnsPage.md new file mode 100644 index 0000000000..6a1fbfb39b --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridDynamicColumnsPage.md @@ -0,0 +1,12 @@ +--- +title: Dynamic columns +route: /DataGrid/DynamicColumns +--- + +# Dynamic columns + +You can make columns appear conditionally using normal Razor logic such as `@if`. Example: + +Also, in this example the column's Width parameter is being set instead of specifying all widths for all columns in the `GridTemplateColumn` parameter. + +{{ DataGridDynamicColumns }} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridGettingStartedPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridGettingStartedPage.md new file mode 100644 index 0000000000..f2737cf090 --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridGettingStartedPage.md @@ -0,0 +1,12 @@ +--- +title: Getting started +route: /DataGrid/GettingStarted +--- + +# Getting started + +The example and code below show what you need to add to one of your Blazor page components to render a very simple grid (with sortable columns). + +It also shows how you can add pagination to your grid. In fact there are two paginators used here. One at the top and one at the bottom of the grid. They automatically stay synchronized. The top paginator does not show the total count summary. + +{{ DataGridGettingStarted }} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridManualPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridManualPage.md new file mode 100644 index 0000000000..aad30e1162 --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridManualPage.md @@ -0,0 +1,10 @@ +--- +title: Manual grid +route: /DataGrid/ManualDataGrid +--- + +# Manual grid + +Although it is possible to create a data grid declaratively, we advise to not use this way of working with a DataGrid. Things like setting headers and specifying sorting are much harder and sometimes not possible to achieve. Best result will be achieved when using `DisplayMode="DataGridDisplayMode.Table"`. + +{{ DataGridManual }} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridMultiSelectPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridMultiSelectPage.md new file mode 100644 index 0000000000..771d8a5e30 --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridMultiSelectPage.md @@ -0,0 +1,52 @@ +--- +title: Multi select +route: /DataGrid/MultiSelect +--- + +# Multi select + + +## Multi select + +The same example, adding a `SelectColumn`, to allow multi-select rows. + +To utilize the **SelectColumn** feature in the Fluent DataGrid, there are two approaches available: + +**Automatic Management via `SelectedItems`** +- Provide a list of data via the `Items` property. +- Let the grid handle selected rows entirely through the `SelectedItems` property. + +**Manual Management via `Property` and `OnSelect`:** +- Control how selected lines are saved manually. +- Utilize the `Property`, `OnSelect`, and `SelectAll` attributes. + +This method offers more flexibility but requires additional configuration, making it particularly useful when using `Virtualize` or directly managing a custom `IsSelected` property. + +> By default the Fluent Design System recommends to only use the checkbox to indicate selected rows. +> It is possible to change this behavior by using a CSS style like this to set a background on selected rows: +> +> ```css +> .fluent-data-grid-row:has([row-selected]) > td { +> background-color: var(--neutral-fill-stealth-hover) +> } +> ``` + +{{ DataGridMultiSelect } }} + + + +Using this `SelectColumn`, you can customize the checkboxes by using `ChildContent` to define the contents of the selection for each row of the grid; +or `SelectAllTemplate` to customize the header of this column. +If you don't want the user to be able to interact (click and change) on the SelectAll header, you can set the `SelectAllDisabled="true"` attribute. + + +Example: +```razor + + @(context.AllSelected == true ? "✅" : context.AllSelected == null ? "➖" : "⬜") + + + @(SelectedItems.Contains(context) ? "✅" : " ") @@* Using SelectedItems *@@ + @(context.Selected ? "✅" : " ") @@* Using Property and OnSelect *@@ + +``` diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridMultilineTextPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridMultilineTextPage.md new file mode 100644 index 0000000000..7c19d816cb --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridMultilineTextPage.md @@ -0,0 +1,10 @@ +--- +title: Multi line text +route: /DataGrid/MultilineText +--- + +# Multi line text content + +Set the grid parameter `MultiLine` to true when you have cells in your data that will take up more than a single line. + +{{ DataGridMultilineText }} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridNotVirtualizedLoadingAndEmptyPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridNotVirtualizedLoadingAndEmptyPage.md new file mode 100644 index 0000000000..6ff18c05ab --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridNotVirtualizedLoadingAndEmptyPage.md @@ -0,0 +1,12 @@ +--- +title: Using LoadingContent and EmptyContent +route: /DataGrid/NotVirtualizedLoadingAndEmpty +--- + +# Using LoadingContent and EmptyContent + +This example shows the usage of the templated EmptyContent and LoadingContent parameters. + +Use the switch and the button at the bottom to toggle between loading and empty content. + +{{ DataGridNotVirtualizedLoadingAndEmpty } }} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridRemoteDataPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridRemoteDataPage.md new file mode 100644 index 0000000000..e926dd495d --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridRemoteDataPage.md @@ -0,0 +1,41 @@ +--- +title: Remote data +route: /DataGrid/RemoteData +--- + +# Remote data + +## Remote data + +If you're using Blazor WebAssembly, it's very common to fetch data from a JSON API on a server. If you want to +fetch only the data that's needed for the current page/viewport and apply any sorting or filtering rules on the +server, you can use the `ItemsProvider` parameter. + +You can also use `ItemsProvider` with Blazor Server if it needs to query an external endpoint, or in any +other case where your requirements aren't covered by an `IQueryable`. + +To do this, supply a callback matching the `GridItemsProvider<TGridItem>` delegate type, where `TGridItem` +is the type of data displayed in the grid. Your callback will be given a parameter of type `GridItemsProviderRequest<TGridItem>` +which specifies the start index, maximum row count, and sort order of data to return. As well as returning the matching items, you need +to return a `totalItemCount` so that paging or virtualization can work. + +Here is an example of connecting a grid to the public [OpenFDA Food Enforcement database](https://open.fda.gov/apis/food/enforcement/). + +This grid is using a 'sticky' header (i.e. the header is always visible when scrolling). The buttons in the last column disappear under the header when scrolling. +In this example they don't really do anything more than writing a message in the console log' + +The second column has a custom `Style` parameter set and applied to it. The 4th column has its `Tooltip` +parameter set to true. This will show the full content of the cell when hovering over it. See the 'Razor' tab for how these +parameters have been applied. + +{{ DataGridRemoteData }} + +## Remote data with RefreshItems + +If the external endpoint controls filtering, paging and sorting you can use `Items` combined with `RefreshItems`. + +The method defined in `RefreshItems` will be called once, and only once, if there is a change in the pagination or ordering. + +Meanwhile, you can control the filtering with elements present on the page itself and force a call to `RefreshItems` with the force option in the RefreshDataAsync. + +{{ DataGridRemoteData2 }} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTableScrollbarsPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTableScrollbarsPage.md new file mode 100644 index 0000000000..9b5335b66e --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTableScrollbarsPage.md @@ -0,0 +1,13 @@ +--- +title: Table scrollbars +route: /DataGrid/TableScrollbars +--- + +# Table scrollbars + +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 rendered out of the view initially. +You can fix this by adding the following `Style` to your data grid: `Style="min-width: max-content;"` + +{{ DataGridTableScrollbars }} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTemplateColumns2Page.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTemplateColumns2Page.md new file mode 100644 index 0000000000..678d61b656 --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTemplateColumns2Page.md @@ -0,0 +1,11 @@ +--- +title: More template columns +route: /DataGrid/TemplateColumns2 +--- + +# More template columns + +## Template columns 2 +`TemplateColumn` uses arbitrary Razor fragments to supply contents for its cells. It can't infer the column's title or sort order automatically. + +{{ DataGridTemplateColumns2 }} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTemplateColumnsPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTemplateColumnsPage.md new file mode 100644 index 0000000000..201063460a --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTemplateColumnsPage.md @@ -0,0 +1,11 @@ +--- +title: Template columns +route: /DataGrid/TemplateColumns +--- + +# Template columns + +## Template columns +`TemplateColumn` uses arbitrary Razor fragments to supply contents for its cells. It can't infer the column's title or sort order automatically. + +{{ DataGridTemplateColumns }} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTypicalPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTypicalPage.md new file mode 100644 index 0000000000..9b3474dcb9 --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTypicalPage.md @@ -0,0 +1,21 @@ +--- +title: Typical usage +route: /DataGrid/Typical +--- + +# Typical usage + +Here is an example of a data grid that uses in-memory data and enables features including pagination, sorting, filtering, column options, row highlighting and column resizing. + +All columns, except 'Bronze', have a `Tooltip` parameter value of `true`.
+When using this for a `TemplateColumn` (like 'Rank' here), you need to also supply a value for the `TooltipText` parameter. No value given equals no tooltip shown.
+When using this for a `PropertyColumn`, a value for the `TooltipText` is not required. By default, the value given for `Property` +will be re-used for the tooltip. If you do supply a value for `TooltipText` its outcome will be used instead. + +`TooltipText` is a lambda function that takes the current item as input and returns the text to show in the tooltip (and `aria-label`). +Look at the Razor tab to see how this is done and how it can be customized. + +The Country filter option can be used to quickly filter the list of countries shown. Pressing the ESC key just closes the option popup without changing the filtering currently being used. +Pressing enter finishes the filter action by the current input to filter on and closes the option popup. + +{{ DataGridTypical Files=Code:DataGridTypical.razor;CSS:DataGridTypical.razor.css }} }} diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridVirtualizePage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridVirtualizePage.md new file mode 100644 index 0000000000..545366e4bc --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridVirtualizePage.md @@ -0,0 +1,21 @@ +--- +title: Virtualize +route: /DataGrid/Virtualize +--- +# Virtualize + +It can be expensive both to fetch and to render large numbers of items. If the amount of data you're +displaying might be large, you should use either paging or virtualization. + +Virtualization provides the appearance of continuous scrolling through an arbitrarily-large data set, +while only needing to fetch and render the rows that are currently in the scroll viewport. This can provide +excellent performance even when the data set is vast. FluentDataGrid's virtualization feature is built on Blazor's +built-in [Virtualize component](https://docs.microsoft.com/en-us/aspnet/core/blazor/components/virtualization?view=aspnetcore-6.0), +so it shares the same capabilities, requirements, and limitations. + +Enabling virtualization is just a matter of passing `Virtualize="true"`. For it to work +properly and reliably, every row rendered must have the same known height. + +**This is handled by the `FluentDataGrid` code** + +{{ DataGridVirtualize }} diff --git a/examples/Tools/FluentUI.Demo.SampleData/FoodProductRecalls.cs b/examples/Tools/FluentUI.Demo.SampleData/FoodProductRecalls.cs index fda81bc449..aa369694a3 100644 --- a/examples/Tools/FluentUI.Demo.SampleData/FoodProductRecalls.cs +++ b/examples/Tools/FluentUI.Demo.SampleData/FoodProductRecalls.cs @@ -15,12 +15,12 @@ namespace FluentUI.Demo.SampleData; public class RemoteFoodProductRecalls { private const string FDA_API = "https://api.fda.gov/food/enforcement.json"; - private static readonly HttpClient _client = new HttpClient(); + private static readonly HttpClient _client = new(); /// /// Options for JSON serialization and deserialization. /// - private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions + private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull @@ -47,7 +47,7 @@ public class FoodProductRecalls public Meta Meta { get; set; } = new(); /// - public IEnumerable Results { get; set; } = Array.Empty(); + public IEnumerable Results { get; set; } = []; } /// diff --git a/spelling.dic b/spelling.dic index aa81caf720..9affac6c7b 100644 --- a/spelling.dic +++ b/spelling.dic @@ -100,3 +100,6 @@ colspan Voituron firstname lastname +oninput +altcolor +wdelta diff --git a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs index cb8f5edb01..888493e58c 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs @@ -18,7 +18,6 @@ namespace Microsoft.FluentUI.AspNetCore.Components; /// The type of data represented by each row in the grid. public abstract partial class ColumnBase { - private bool _isMenuOpen; private static readonly string[] KEYBOARD_MENU_SELECT_KEYS = ["Enter", "NumpadEnter"]; private readonly string _columnId = Identifier.NewId(); private FluentMenu? _menu; @@ -286,14 +285,21 @@ private async Task HandleColumnHeaderClickedAsync() var hasSorting = Sortable is true || IsDefaultSortColumn; var hasResize = Grid.ResizableColumns; var hasOptions = ColumnOptions is not null; - var hasMultiple = (hasSorting && hasResize) || (hasSorting && hasOptions) || (hasResize && hasOptions); + var hasMultiple = (Convert.ToInt32(hasSorting) + Convert.ToInt32(hasResize) + Convert.ToInt32(hasOptions)) > 1; + var hideMenu = (hasSorting ^ hasResize ^ hasOptions) && !(hasSorting && hasResize && hasOptions); + + if (_menu is not null && hideMenu) + { + await _menu.CloseMenuAsync(); + } if (hasMultiple) { - _isMenuOpen = !_isMenuOpen; - StateHasChanged(); + return; + //StateHasChanged(); } - else if (hasSorting) + + if (hasSorting) { await Grid.SortByColumnAsync(this); } @@ -312,8 +318,10 @@ private async Task HandleSortMenuKeyDownAsync(KeyboardEventArgs args) if (KEYBOARD_MENU_SELECT_KEYS.Contains(args.Key, StringComparer.OrdinalIgnoreCase)) { await Grid.SortByColumnAsync(this); - StateHasChanged(); - _isMenuOpen = false; + if (_menu is not null) + { + await _menu.CloseMenuAsync(); + } } } @@ -322,7 +330,10 @@ private async Task HandleResizeMenuKeyDownAsync(KeyboardEventArgs args) if (KEYBOARD_MENU_SELECT_KEYS.Contains(args.Key, StringComparer.OrdinalIgnoreCase)) { await Grid.ShowColumnResizeAsync(this); - _isMenuOpen = false; + if (_menu is not null) + { + await _menu.CloseMenuAsync(); + } } } @@ -331,7 +342,10 @@ private async Task HandleOptionsMenuKeyDownAsync(KeyboardEventArgs args) if (KEYBOARD_MENU_SELECT_KEYS.Contains(args.Key, StringComparer.OrdinalIgnoreCase)) { await Grid.ShowColumnOptionsAsync(this); - _isMenuOpen = false; + if (_menu is not null) + { + await _menu.CloseMenuAsync(); + } } } diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index dad58dc43f..e003389fdb 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -451,7 +451,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) // Import the JavaScript module await JSModule.ImportJavaScriptModuleAsync(JAVASCRIPT_FILE); - await JSModule.ObjectReference.InvokeAsync("init", _gridReference, AutoFocus); + await JSModule.ObjectReference.InvokeAsync("Microsoft.FluentUI.Blazor.DataGrid.Initialize", _gridReference, AutoFocus); if (AutoItemsPerPage) { diff --git a/src/Core/Components/DataGrid/GridItemsProviderResult.cs b/src/Core/Components/DataGrid/GridItemsProviderResult.cs index 34a9c7c83b..cc0e70704b 100644 --- a/src/Core/Components/DataGrid/GridItemsProviderResult.cs +++ b/src/Core/Components/DataGrid/GridItemsProviderResult.cs @@ -27,7 +27,7 @@ public readonly struct GridItemsProviderResult /// /// Provides convenience methods for constructing instances. /// -internal static class GridItemsProviderResult +public static class GridItemsProviderResult { // This is just to provide generic type inference, so you don't have to specify TGridItem yet again. From 150c1fd031bae3c4f9ae977004397df11c0613b5 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Thu, 3 Jul 2025 21:21:51 +0200 Subject: [PATCH 44/44] - Rename getting started - Remove double section - Use primary color on paginator buttos (and apply better style when button is diabled) --- ...rted.razor => DataGridGettingStarted.razor} | 0 .../Components/DataGrid/FluentDataGrid.md | 18 ------------------ .../DataGrid/Pages/DataGridVirtualizePage.md | 4 +--- .../Components/Button/FluentButton.razor.css | 4 ++++ .../Pagination/FluentPaginator.razor | 8 ++++---- 5 files changed, 9 insertions(+), 25 deletions(-) rename examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/{DataGridGetStarted.razor => DataGridGettingStarted.razor} (100%) diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridGetStarted.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridGettingStarted.razor similarity index 100% rename from examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridGetStarted.razor rename to examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridGettingStarted.razor diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md index 8790673475..6f9e8fc701 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md @@ -113,24 +113,6 @@ builder.Services.AddDataGridODataAdapter(); ``` -## Typical usage -Here is an example of a data grid that uses in-memory data and enables features including pagination, sorting, filtering, column options, row highlighting and column resizing. - -All columns, except 'Bronze', have a `Tooltip` parameter value of `true`. - -- When using this for a `TemplateColumn` (like 'Rank' here), you need to also supply a value for the `TooltipText` parameter. **No value given equals no tooltip shown**. -- When using this for a `PropertyColumn`, a value for the `TooltipText` is **not** required. By default, the value given for `Property` -will be re-used for the tooltip. If you do supply a value for `TooltipText` its outcome will be used instead. - -`TooltipText` is a lambda function that takes the current item as input and returns the text to show in the tooltip (and `aria-label`). -Look at the Razor tab to see how this is done and how it can be customized. - -The Country filter option can be used to quickly filter the list of countries shown. Pressing the ESC key just closes the option popup without changing the filtering currently being used. -Pressing enter finishes the filter action by the current input to filter on and closes the option popup. - -The resize options UI is using a customized string for the label ('Width (+/- 10px)' instead of the normal 'Column width'). This is done through -the custom localizer which is registered in the Server project's `Program.cs` file. - ## Typical usage Here is an example of a data grid that uses in-memory data and enables features including pagination, sorting, filtering, column options, row highlighting and column resizing. diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridVirtualizePage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridVirtualizePage.md index 545366e4bc..0b298035f6 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridVirtualizePage.md +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridVirtualizePage.md @@ -14,8 +14,6 @@ built-in [Virtualize component](https://docs.microsoft.com/en-us/aspnet/core/bla so it shares the same capabilities, requirements, and limitations. Enabling virtualization is just a matter of passing `Virtualize="true"`. For it to work -properly and reliably, every row rendered must have the same known height. - -**This is handled by the `FluentDataGrid` code** +properly and reliably, every row rendered must have the same known height. **This is handled by the `FluentDataGrid` code** {{ DataGridVirtualize }} diff --git a/src/Core/Components/Button/FluentButton.razor.css b/src/Core/Components/Button/FluentButton.razor.css index 1559a8a5ba..e2703e80e7 100644 --- a/src/Core/Components/Button/FluentButton.razor.css +++ b/src/Core/Components/Button/FluentButton.razor.css @@ -21,3 +21,7 @@ fluent-button svg[slot="end"], fluent-button fluent-spinner[slot="end"] { margin-inline-start: 8px; } + +fluent-button[disabled] svg { + opacity: 0.4; +} diff --git a/src/Core/Components/Pagination/FluentPaginator.razor b/src/Core/Components/Pagination/FluentPaginator.razor index 2c39d9d36c..c926fec6a3 100644 --- a/src/Core/Components/Pagination/FluentPaginator.razor +++ b/src/Core/Components/Pagination/FluentPaginator.razor @@ -22,12 +22,12 @@ Disabled="@(!CanGoBack || Disabled)" Title="@Localizer[Localization.LanguageResource.Paginator_GoFirstPage]" aria-label="@Localizer[Localization.LanguageResource.Paginator_GoFirstPage]" - IconStart="@(new CoreIcons.Regular.Size20.ChevronDoubleLeft())" /> + IconStart="@(new CoreIcons.Regular.Size20.ChevronDoubleLeft().WithColor(Color.Primary))" /> + IconStart="@(new CoreIcons.Regular.Size20.ChevronLeft().WithColor(Color.Primary))" />
@if (PaginationTextTemplate is not null) { @@ -43,12 +43,12 @@ Disabled="@(!CanGoForwards || Disabled)" Title="@Localizer[Localization.LanguageResource.Paginator_GoNextPage]" aria-label="@Localizer[Localization.LanguageResource.Paginator_GoNextPage]" - IconStart="@(new CoreIcons.Regular.Size20.ChevronRight())" /> + IconStart="@(new CoreIcons.Regular.Size20.ChevronRight().WithColor(Color.Primary))" /> + IconStart="@(new CoreIcons.Regular.Size20.ChevronDoubleRight().WithColor(Color.Primary))" /> }
@@ -14,27 +14,27 @@
+ Jean Martin
+ Kenji Sato
+