- @ChildContent
-
- @if (MoreButtonTemplate != null)
- {
- @MoreButtonTemplate(this)
- }
- else
- {
-
- }
-
+
+ @ChildContent
+
+ @if (MoreTemplate != null)
+ {
+ @MoreTemplate(this)
+ }
+ else
+ {
+
+ }
- @if (OverflowTemplate != null)
- {
- @OverflowTemplate(this)
- }
- else
- {
-
- @foreach (var item in ItemsOverflow)
- {
- @item.Text
- }
-
- }
-
+
+@if (OverflowTemplate != null)
+{
+ @OverflowTemplate(this)
+}
+else
+{
+
+ @foreach (var item in ItemsOverflow)
+ {
+ @item.Text
+ }
+
+}
diff --git a/src/Core/Components/Overflow/FluentOverflow.razor.cs b/src/Core/Components/Overflow/FluentOverflow.razor.cs
index b0e32afc0b..dce42f6c2e 100644
--- a/src/Core/Components/Overflow/FluentOverflow.razor.cs
+++ b/src/Core/Components/Overflow/FluentOverflow.razor.cs
@@ -11,9 +11,8 @@ namespace Microsoft.FluentUI.AspNetCore.Components;
///
public partial class FluentOverflow : FluentComponentBase
{
- private const string JAVASCRIPT_FILE = FluentJSModule.JAVASCRIPT_ROOT + "Overflow/FluentOverflow.razor.js";
- private readonly List
_items = [];
- private DotNetObjectReference? _dotNetHelper;
+ private readonly List _items = [];
+ private int _overflowCount;
///
protected virtual string? ClassValue => DefaultClassBuilder
@@ -31,11 +30,6 @@ public FluentOverflow(LibraryConfiguration configuration) : base(configuration)
Id = Identifier.NewId();
}
- internal FluentOverflow(LibraryConfiguration configuration, List items) : this(configuration)
- {
- _items = items;
- }
-
///
/// Gets or sets the template to display elements.
///
@@ -44,17 +38,17 @@ internal FluentOverflow(LibraryConfiguration configuration, List
/// Gets or sets whether overflow items are visible immediately on load.
- /// Set to false to hide items until the component is fully loaded,
- /// preventing a flickering effect. Defaults to true.
+ /// Set to to hide items until the component is fully loaded,
+ /// preventing a flickering effect. Defaults to .
///
[Parameter]
public bool VisibleOnLoad { get; set; } = true;
///
- /// Gets or sets the template to display the More button.
+ /// Gets or sets the template to display the overflow trigger content.
///
[Parameter]
- public RenderFragment? MoreButtonTemplate { get; set; }
+ public RenderFragment? MoreTemplate { get; set; }
///
/// Gets or sets the orientation of the items flow.
@@ -63,22 +57,36 @@ internal FluentOverflow(LibraryConfiguration configuration, List
- /// Gets or sets the CSS selectors of the items to include in the overflow.
+ /// Gets or sets the CSS selector of direct children to include in the overflow.
+ /// If null or empty, all direct children except the built-in More button are considered.
+ ///
+ [Parameter]
+ public string? Selector { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets whether overflow items are cached in JavaScript memory.
+ ///
+ [Parameter]
+ public bool StoreOverflowInMemory { get; set; }
+
+ ///
+ /// Gets or sets the maximum number of overflow items returned to the Blazor wrapper.
+ /// Values less than or equal to zero return all overflow items.
///
[Parameter]
- public string? Selectors { get; set; } = string.Empty;
+ public int MaxRenderedItems { get; set; } = 25;
///
/// Gets or sets whether the tooltip is displayed using the TooltipService.
///
[Parameter]
- public bool UseTooltipService { get; set; } = false;
+ public bool UseTooltipService { get; set; }
///
- /// Event raised when a enter or leave the current panel.
+ /// Event raised when overflow items change.
///
[Parameter]
- public EventCallback> OnOverflowRaised { get; set; }
+ public EventCallback> OnOverflowRaised { get; set; }
///
/// Gets or sets the content to display.
@@ -88,26 +96,31 @@ internal FluentOverflow(LibraryConfiguration configuration, List
- /// Gets all items with assigned to True.
+ /// Gets the rendered overflow items returned from the web component.
///
- public IEnumerable ItemsOverflow => _items.Where(i => i.Overflow == true);
+ public IEnumerable ItemsOverflow => _items;
+
+ ///
+ /// Gets the total number of overflowed items.
+ ///
+ public int OverflowCount => _overflowCount;
///
/// Gets the unique identifier associated to the more button ([Id]-more).
///
public string IdMoreButton => $"{Id}-more";
- private bool IsHorizontal => Orientation == Orientation.Horizontal;
+ ///
+ protected virtual string? MoreButtonStyleValue => new StyleBuilder()
+ .AddStyle("visibility", "hidden", OverflowCount == 0)
+ .AddStyle("anchor-name", $"--{IdMoreButton}")
+ .Build();
///
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
- await JSModule.ImportJavaScriptModuleAsync(JAVASCRIPT_FILE);
-
- _dotNetHelper = DotNetObjectReference.Create(this);
- await JSModule.ObjectReference.InvokeVoidAsync("Microsoft.FluentUI.Blazor.Overflow.Initialize", _dotNetHelper, Id, IsHorizontal, Selectors, 25);
VisibleOnLoad = true;
}
}
@@ -115,34 +128,39 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
///
/// Asynchronously refreshes the overflow state of the associated UI element.
///
- /// Call this method to update the overflow indicators when the content or layout of the element
- /// changes. This method has no effect if the underlying JavaScript module is not loaded.
- /// A task that represents the asynchronous refresh operation.
public async Task RefreshAsync()
{
- if (JSModule is not null)
+ if (JSRuntime is null)
{
- await JSModule.ObjectReference.InvokeVoidAsync("Microsoft.FluentUI.Blazor.Overflow.Refresh", _dotNetHelper, Id, IsHorizontal, Selectors, 25);
+ return;
}
+
+ await JSRuntime.InvokeVoidAsync("Microsoft.FluentUI.Blazor.Components.Overflow.Refresh", Id);
+ await LoadOverflowItemsAsync();
}
///
- [JSInvokable]
public async Task OverflowRaisedAsync(OverflowItem[] items)
{
- if (items == null || items.Length == 0)
+ SetOverflowItems(items, items.Count(item => item.Overflow));
+
+ if (OnOverflowRaised.HasDelegate)
{
- return;
+ await OnOverflowRaised.InvokeAsync(ItemsOverflow);
}
- // Update Item components
- foreach (var item in items)
+ await InvokeAsync(StateHasChanged);
+ }
+
+ private async Task OnOverflowChangedAsync(OverflowChangedEventArgs args)
+ {
+ if (!string.Equals(args.Id, Id, StringComparison.Ordinal))
{
- var element = _items.FirstOrDefault(i => string.Equals(i.Id, item.Id, StringComparison.OrdinalIgnoreCase));
- element?.SetProperties(item.Overflow, item.Text);
+ return;
}
- // Raise event
+ SetOverflowItems(args.Items, args.OverflowCount);
+
if (OnOverflowRaised.HasDelegate)
{
await OnOverflowRaised.InvokeAsync(ItemsOverflow);
@@ -150,37 +168,45 @@ public async Task OverflowRaisedAsync(OverflowItem[] items)
await InvokeAsync(StateHasChanged);
}
- internal async Task RegisterAsync(FluentOverflowItem item)
- {
- await InvokeAsync(() => _items.Add(item));
- }
- internal async Task UnregisterAsync(FluentOverflowItem item)
+ private async Task LoadOverflowItemsAsync()
{
- _items.Remove(item);
- await JSModule.ObjectReference.InvokeVoidAsync("Microsoft.FluentUI.Blazor.Overflow.Dispose", item.Id);
+ var state = await JSRuntime.InvokeAsync("Microsoft.FluentUI.Blazor.Components.Overflow.GetOverflowState", [Id]);
+ SetOverflowItems(state?.OverflowItems, state?.OverflowCount ?? 0);
}
- ///
- protected override async ValueTask DisposeAsync(IJSObjectReference jsModule)
+ private void SetOverflowItems(IEnumerable? items, int overflowCount)
{
- await JSModule.ObjectReference.InvokeVoidAsync("Microsoft.FluentUI.Blazor.Overflow.Dispose", Id);
- _dotNetHelper?.Dispose();
+ if (items is null)
+ {
+ return;
+ }
+
+ _items.Clear();
+ _overflowCount = Math.Max(overflowCount, 0);
+
+ _items.AddRange(items.Where(item => item.Overflow));
}
- ///
- /// Represents an item that may be subject to overflow handling, typically used in scenarios where content or data
- /// exceeds a predefined limit.
- ///
- public class OverflowItem
+ private void SetOverflowItems(IEnumerable? items, int overflowCount)
{
- ///
- public string? Id { get; set; }
+ _items.Clear();
+ _overflowCount = Math.Max(overflowCount, 0);
- ///
- public bool? Overflow { get; set; }
+ if (items is null)
+ {
+ return;
+ }
- ///
- public string? Text { get; set; }
+ _items.AddRange(items
+ .Where(item => item.Overflow)
+ .Select(item => new OverflowItem
+ {
+ Id = item.Id,
+ Overflow = item.Overflow,
+ Text = item.Text,
+ Behavior = item.Behavior,
+ Index = item.Index,
+ }));
}
}
diff --git a/src/Core/Components/Overflow/FluentOverflow.razor.css b/src/Core/Components/Overflow/FluentOverflow.razor.css
index 5d84d50cef..4638495903 100644
--- a/src/Core/Components/Overflow/FluentOverflow.razor.css
+++ b/src/Core/Components/Overflow/FluentOverflow.razor.css
@@ -2,19 +2,30 @@
display: flex;
flex-direction: row;
overflow-x: hidden;
+ overflow-y: hidden;
gap: 5px;
}
.fluent-overflow[orientation="vertical"] {
display: flex;
flex-direction: column;
+ overflow-x: hidden;
overflow-y: hidden;
}
+/* Prevent flex from shrinking items — sizes must be measured at natural width */
+.fluent-overflow > * {
+ flex-shrink: 0;
+}
+
.fluent-overflow-more {
min-width: 32px;
max-width: 32px;
flex-shrink: 0;
+ align-self: stretch;
+ display: flex;
+ align-items: center;
+ justify-content: center;
}
.fluent-overflow-more[style*="visibility: hidden"] {
@@ -29,10 +40,11 @@
flex-shrink: 0;
}
-.fluent-overflow .fluent-overflow-item[fixed='ellipsis'] {
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- min-width: 0;
- max-width: fit-content;
+.fluent-overflow > *[fixed='ellipsis'] {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ min-width: 0;
+ max-width: fit-content;
+ flex-shrink: 1;
}
diff --git a/src/Core/Components/Overflow/FluentOverflow.razor.ts b/src/Core/Components/Overflow/FluentOverflow.razor.ts
deleted file mode 100644
index fa920891df..0000000000
--- a/src/Core/Components/Overflow/FluentOverflow.razor.ts
+++ /dev/null
@@ -1,187 +0,0 @@
-import { DotNet } from "../../../Core.Scripts/src/d-ts/Microsoft.JSInterop";
-
-export namespace Microsoft.FluentUI.Blazor.Overflow {
- interface OverflowItem {
- Id: string;
- Overflow: boolean;
- Text: string;
- }
-
- interface OverflowElement extends HTMLElement {
- overflowSize?: number | null;
- }
-
- interface LastHandledState {
- id: string | null;
- isHorizontal: boolean | null;
- }
-
- let resizeObserver: ResizeObserver | undefined;
- let observerAddRemove: MutationObserver | undefined;
- let lastHandledState: LastHandledState = { id: null, isHorizontal: null };
-
- export function Initialize(dotNetHelper: DotNet.DotNetObject, id: string, isHorizontal: boolean, querySelector: string | null, threshold: number): void {
- let localSelector = querySelector;
- if (!localSelector) {
- localSelector = ".fluent-overflow-item";
- }
-
- observerAddRemove = new MutationObserver(mutations => {
- mutations.forEach(mutation => {
- if (mutation.type !== 'childList' && (mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0)) {
- return;
- }
- const node = mutation.addedNodes.length > 0 ? mutation.addedNodes[0] : mutation.removedNodes[0];
- if (node.nodeType !== Node.ELEMENT_NODE || !(node as Element).matches(localSelector!)) {
- return;
- }
- Refresh(dotNetHelper, id, isHorizontal, querySelector, threshold);
- });
- });
-
- const el = document.getElementById(id);
- if (resizeObserver && el) {
- resizeObserver.unobserve(el);
- }
-
- let resizeTimeout: number | undefined;
- resizeObserver = new ResizeObserver(() => {
- clearTimeout(resizeTimeout);
- resizeTimeout = window.setTimeout(() => {
- Refresh(dotNetHelper, id, isHorizontal, querySelector, threshold);
- }, 100);
- });
-
- if (el) {
- resizeObserver.observe(el);
- observerAddRemove.observe(el, { childList: true, subtree: false });
- }
-
- lastHandledState.id = id;
- lastHandledState.isHorizontal = isHorizontal;
- }
-
- export function Refresh(dotNetHelper: DotNet.DotNetObject, id: string, isHorizontal: boolean, querySelector: string | null, threshold: number): void {
- const container = document.getElementById(id);
- if (!container) return;
-
- let localQuerySelector: string;
- if (!querySelector) {
- localQuerySelector = ":scope .fluent-overflow-item";
- } else {
- localQuerySelector = ":scope >" + querySelector;
- }
-
- const allItems = container.querySelectorAll(localQuerySelector);
- const items = container.querySelectorAll(localQuerySelector + ":not([fixed])");
-
- const fixedItemsFromSelector = container.querySelectorAll(localQuerySelector + "[fixed]");
- const otherFixedItems = container.querySelectorAll(":scope > [fixed]:not(" + localQuerySelector + ")");
- const fixedItems = [
- ...Array.from(fixedItemsFromSelector),
- ...Array.from(otherFixedItems)
- ].filter(el => el.getAttribute("fixed") !== "ellipsis");
-
- const ellipsisItems = Array.from(container.querySelectorAll(localQuerySelector + "[fixed='ellipsis']"));
-
- let ellipsisTotal = 0;
- let containerGap = parseFloat(window.getComputedStyle(container).gap);
- if (!containerGap) containerGap = 0;
-
- ellipsisItems.forEach((el, idx) => {
- el.overflowSize = isHorizontal ? getElementWidth(el) : getElementHeight(el);
- ellipsisTotal += el.overflowSize || 0;
- if (idx > 0) ellipsisTotal += containerGap;
- });
-
- let itemsTotalSize = threshold > 0 ? 10 : 0;
- let containerMaxSize = isHorizontal ? container.offsetWidth : container.offsetHeight;
- let overflowChanged = false;
-
- containerMaxSize -= threshold;
-
- const availableSize = containerMaxSize - fixedItems.reduce((sum, el, idx) => sum + (el.overflowSize || 0) + (idx > 0 ? containerGap : 0), 0);
-
- if (ellipsisTotal > availableSize) {
- ellipsisItems.forEach(el => {
- el.style.flexShrink = "1";
- });
- } else {
- ellipsisItems.forEach(el => {
- el.style.flexShrink = "0";
- });
- }
-
- if (lastHandledState.id === id && lastHandledState.isHorizontal !== isHorizontal) {
- allItems.forEach(element => {
- element.removeAttribute("overflow");
- element.overflowSize = null;
- });
- }
-
- fixedItems.forEach(element => {
- element.overflowSize = isHorizontal ? getElementWidth(element) : getElementHeight(element);
- element.overflowSize = (element.overflowSize || 0) + containerGap;
- itemsTotalSize += element.overflowSize;
- });
-
- items.forEach(element => {
- const isOverflow = element.hasAttribute("overflow");
- if (!isOverflow) {
- element.overflowSize = isHorizontal ? getElementWidth(element) : getElementHeight(element);
- element.overflowSize = (element.overflowSize || 0) + containerGap;
- }
- itemsTotalSize += element.overflowSize || 0;
- if (containerMaxSize > 0) {
- if (itemsTotalSize > containerMaxSize) {
- if (!isOverflow) {
- element.setAttribute("overflow", "");
- overflowChanged = true;
- }
- } else {
- if (isOverflow) {
- element.removeAttribute("overflow");
- overflowChanged = true;
- }
- }
- }
- });
-
- if (overflowChanged) {
- const listOfOverflow: OverflowItem[] = [];
- items.forEach(element => {
- listOfOverflow.push({
- Id: element.id,
- Overflow: element.hasAttribute("overflow"),
- Text: element.innerText.trim()
- });
- });
- dotNetHelper.invokeMethodAsync("OverflowRaisedAsync", listOfOverflow);
- }
-
- lastHandledState.id = id;
- lastHandledState.isHorizontal = isHorizontal;
- }
-
- export function Dispose(id: string): void {
- const el = document.getElementById(id);
- if (el) {
- resizeObserver?.unobserve(el);
- observerAddRemove?.disconnect();
- }
- }
-
- function getElementWidth(element: HTMLElement): number {
- const style = window.getComputedStyle(element);
- const width = element.offsetWidth;
- const margin = parseFloat(style.marginLeft) + parseFloat(style.marginRight);
- return width + margin;
- }
-
- function getElementHeight(element: HTMLElement): number {
- const style = window.getComputedStyle(element);
- const height = element.offsetHeight;
- const margin = parseFloat(style.marginTop) + parseFloat(style.marginBottom);
- return height + margin;
- }
-}
diff --git a/src/Core/Components/Overflow/FluentOverflowItem.razor b/src/Core/Components/Overflow/FluentOverflowItem.razor
deleted file mode 100644
index f9ef6aa39e..0000000000
--- a/src/Core/Components/Overflow/FluentOverflowItem.razor
+++ /dev/null
@@ -1,7 +0,0 @@
-@namespace Microsoft.FluentUI.AspNetCore.Components
-@using Microsoft.FluentUI.AspNetCore.Components.Extensions
-@inherits FluentComponentBase
-
-
- @ChildContent
-
diff --git a/src/Core/Components/Overflow/FluentOverflowItem.razor.cs b/src/Core/Components/Overflow/FluentOverflowItem.razor.cs
deleted file mode 100644
index 17fabe4851..0000000000
--- a/src/Core/Components/Overflow/FluentOverflowItem.razor.cs
+++ /dev/null
@@ -1,89 +0,0 @@
-// ------------------------------------------------------------------------
-// This file is licensed to you under the MIT License.
-// ------------------------------------------------------------------------
-
-using Microsoft.AspNetCore.Components;
-using Microsoft.FluentUI.AspNetCore.Components.Utilities;
-
-namespace Microsoft.FluentUI.AspNetCore.Components;
-
-///
-public partial class FluentOverflowItem : FluentComponentBase, IAsyncDisposable
-{
- //private bool _disposed;
-
- ///
- protected string? ClassValue => DefaultClassBuilder
- .AddClass("fluent-overflow-item")
- .Build();
-
- ///
- protected string? StyleValue => DefaultStyleBuilder
- .Build();
-
- ///
- /// Gets or sets the reference to the associated container.
- ///
- /// The splitter.
- [CascadingParameter]
- internal FluentOverflow? Owner { get; set; }
-
- ///
- /// Gets or sets the content to display. All first HTML elements are included in the items flow.
- ///
- [Parameter]
- public RenderFragment? ChildContent { get; set; }
-
- ///
- /// Gets or sets whether this element does not participate in overflow logic, and will always be visible.
- /// Defaults to false
- ///
- [Parameter]
- public OverflowItemFixed Fixed { get; set; } = OverflowItemFixed.None;
-
- ///
- /// Gets True if this component is out of panel.
- ///
- public bool? Overflow { get; private set; }
-
- ///
- /// Gets the InnerText of .
- ///
- public string Text { get; private set; } = string.Empty;
-
- ///
- public FluentOverflowItem(LibraryConfiguration configuration) : base(configuration)
- {
- Id = Identifier.NewId();
- }
-
- internal FluentOverflowItem(LibraryConfiguration configuration, bool isOverflow, string text) : this(configuration)
- {
- Overflow = isOverflow;
- Text = text;
- }
-
- ///
- protected override async Task OnInitializedAsync()
- {
- if (Owner is not null)
- {
- await Owner.RegisterAsync(this);
- }
- }
-
- ///
- internal void SetProperties(bool? isOverflow, string? text)
- {
- Overflow = isOverflow == true ? isOverflow : null;
- Text = text ?? string.Empty;
- }
-
- ///
- public override async ValueTask DisposeAsync()
- {
- Owner?.UnregisterAsync(this);
- await base.DisposeAsync();
-
- }
-}
diff --git a/src/Core/Components/Overflow/OverflowItem.cs b/src/Core/Components/Overflow/OverflowItem.cs
new file mode 100644
index 0000000000..f3d37d5ebc
--- /dev/null
+++ b/src/Core/Components/Overflow/OverflowItem.cs
@@ -0,0 +1,37 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+namespace Microsoft.FluentUI.AspNetCore.Components;
+
+///
+/// Represents an item that may be subject to overflow handling.
+///
+public record OverflowItem
+{
+ ///
+ /// Gets the unique identifier of the overflow item.
+ ///
+ public string? Id { get; init; }
+
+ ///
+ /// Gets a value indicating whether the item is in overflow.
+ ///
+ public bool Overflow { get; init; }
+
+ ///
+ /// Gets the text associated with the overflow item.
+ ///
+ public string? Text { get; init; }
+
+ ///
+ /// Gets the overflow behavior of the item.
+ ///
+ public OverflowBehavior? Behavior { get; init; }
+
+ ///
+ /// Gets the index of the overflow item.
+ ///
+ public int Index { get; init; }
+}
+
diff --git a/src/Core/Components/Overflow/OverflowState.cs b/src/Core/Components/Overflow/OverflowState.cs
new file mode 100644
index 0000000000..bea4e55773
--- /dev/null
+++ b/src/Core/Components/Overflow/OverflowState.cs
@@ -0,0 +1,32 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+namespace Microsoft.FluentUI.AspNetCore.Components;
+
+///
+/// Represents the current overflow state.
+///
+public record OverflowState
+{
+ ///
+ /// Gets the items that are currently in overflow.
+ ///
+ public OverflowItem[]? OverflowItems { get; init; }
+
+ ///
+ /// Gets the count of items in overflow.
+ ///
+ public int OverflowCount { get; init; }
+
+ ///
+ /// Gets the index of the first item in overflow.
+ ///
+ public int FirstOverflowIndex { get; init; }
+
+ ///
+ /// Gets the ordered item identifiers.
+ ///
+ public string[]? OrderedItemIds { get; init; }
+}
+
diff --git a/src/Core/Enums/OverflowItemFixed.cs b/src/Core/Enums/OverflowBehavior.cs
similarity index 51%
rename from src/Core/Enums/OverflowItemFixed.cs
rename to src/Core/Enums/OverflowBehavior.cs
index ea0539dcac..bdf5e32363 100644
--- a/src/Core/Enums/OverflowItemFixed.cs
+++ b/src/Core/Enums/OverflowBehavior.cs
@@ -7,25 +7,19 @@
namespace Microsoft.FluentUI.AspNetCore.Components;
///
-/// Possibility for an element not to participate in the overflow logic and always remain displayed.
+/// Specifies the overflow behavior for an item.
///
-public enum OverflowItemFixed
+public enum OverflowBehavior
{
///
- /// If the item is out of the display, it disappears.
- ///
- [Description("none")]
- None = 0,
-
- ///
- /// The element is always visible
+ /// The item is kept fixed in place and not subject to overflow.
///
[Description("fixed")]
- Fixed = 1,
+ Fixed,
///
- /// The element is always visible, but its width can be reduced to display "...".
+ /// The item can be hidden with an ellipsis indicator when space is constrained.
///
[Description("ellipsis")]
- Ellipsis = 2,
+ Ellipsis,
}
diff --git a/src/Core/Events/EventHandlers.cs b/src/Core/Events/EventHandlers.cs
index 02c542ad74..c9f4bed81b 100644
--- a/src/Core/Events/EventHandlers.cs
+++ b/src/Core/Events/EventHandlers.cs
@@ -39,6 +39,7 @@ namespace Microsoft.FluentUI.AspNetCore.Components;
[EventHandler("onclosecolumnheaderui", typeof(EventArgs), enableStopPropagation: true, enablePreventDefault: true)]
[EventHandler("onradiochange", typeof(RadioEventArgs), enableStopPropagation: true, enablePreventDefault: true)]
[EventHandler("ontextimmediate", typeof(ChangeEventArgs), enableStopPropagation: true, enablePreventDefault: true)]
+[EventHandler("onoverflowchange", typeof(OverflowChangedEventArgs), enableStopPropagation: true, enablePreventDefault: true)]
public static class EventHandlers
{
}
diff --git a/src/Core/Events/OverflowChangedEventArgs.cs b/src/Core/Events/OverflowChangedEventArgs.cs
new file mode 100644
index 0000000000..f6242796a7
--- /dev/null
+++ b/src/Core/Events/OverflowChangedEventArgs.cs
@@ -0,0 +1,36 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+namespace Microsoft.FluentUI.AspNetCore.Components;
+
+///
+/// Event arguments for the FluentOverflow overflow change event.
+///
+public class OverflowChangedEventArgs : EventArgs
+{
+ ///
+ /// Gets or sets the ID of the overflow component.
+ ///
+ public string? Id { get; set; }
+
+ ///
+ /// Gets or sets the rendered overflow items included in the event payload.
+ ///
+ public IReadOnlyList? Items { get; set; }
+
+ ///
+ /// Gets or sets the total number of items currently in overflow.
+ ///
+ public int OverflowCount { get; set; }
+
+ ///
+ /// Gets or sets the index of the first overflowed managed item (selector match, excluding fixed items).
+ ///
+ public int FirstOverflowIndex { get; set; } = -1;
+
+ ///
+ /// Gets or sets the ordered item IDs in the same DOM order used by overflow calculations.
+ ///
+ public IReadOnlyList? OrderedItemIds { get; set; }
+}
diff --git a/src/Core/Events/OverflowChangedItem.cs b/src/Core/Events/OverflowChangedItem.cs
new file mode 100644
index 0000000000..7d535d13fe
--- /dev/null
+++ b/src/Core/Events/OverflowChangedItem.cs
@@ -0,0 +1,36 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+namespace Microsoft.FluentUI.AspNetCore.Components;
+
+///
+/// Represents one overflow item state passed by the overflowchange custom event.
+///
+public class OverflowChangedItem
+{
+ ///
+ /// Gets or sets the item identifier.
+ ///
+ public string? Id { get; set; }
+
+ ///
+ /// Gets or sets whether the item is currently in overflow.
+ ///
+ public bool Overflow { get; set; }
+
+ ///
+ /// Gets or sets the item text.
+ ///
+ public string? Text { get; set; }
+
+ ///
+ /// Gets or sets the overflow behavior.
+ ///
+ public OverflowBehavior? Behavior { get; set; }
+
+ ///
+ /// Gets or sets the item index.
+ ///
+ public int Index { get; set; }
+}
diff --git a/tests/Core/Components/AppBar/FluentAppBarTests.razor b/tests/Core/Components/AppBar/FluentAppBarTests.razor
index fcc7ba5b0f..e6d2085062 100644
--- a/tests/Core/Components/AppBar/FluentAppBarTests.razor
+++ b/tests/Core/Components/AppBar/FluentAppBarTests.razor
@@ -64,6 +64,44 @@
Assert.NotNull(popup);
}
+ [Fact]
+ public async Task FluentAppBar_OnOverflowChangedAsync_UsesOrderedItemIds()
+ {
+ // Arrange
+ var items = new List
+ {
+ new TestAppBarItem { Id = "1", Text = "Item 1", IconRest = new Samples.Icons.Samples.Info() },
+ new TestAppBarItem { Id = "2", Text = "Item 2", IconRest = new Samples.Icons.Samples.Warning() },
+ new TestAppBarItem { Id = "3", Text = "Item 3", IconRest = new Samples.Icons.Samples.Info() }
+ };
+ var cut = Render(
+ @
+ );
+ var method = typeof(FluentAppBar).GetMethod("OnOverflowChangedAsync", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
+ var context = GetPrivateField(cut.Instance, "_internalAppBarContext");
+ Assert.NotNull(method);
+ Assert.NotNull(context);
+
+ var args = new OverflowChangedEventArgs
+ {
+ Id = $"{cut.Instance.Id}-overflow",
+ FirstOverflowIndex = 1,
+ OrderedItemIds = ["3", "2", "1"]
+ };
+
+ // Act
+ var result = method!.Invoke(cut.Instance, [args]);
+ if (result is Task task)
+ {
+ await task;
+ }
+
+ // Assert
+ Assert.False(context!.Apps["3"].Overflow ?? false);
+ Assert.True(context.Apps["2"].Overflow ?? false);
+ Assert.True(context.Apps["1"].Overflow ?? false);
+ }
+
[Fact]
public async Task FluentAppBar_NoPopoverSearch()
{
diff --git a/tests/Core/Components/Overflow/FluentOverflowTests.razor b/tests/Core/Components/Overflow/FluentOverflowTests.razor
index 42c40b6089..4a33cb4179 100644
--- a/tests/Core/Components/Overflow/FluentOverflowTests.razor
+++ b/tests/Core/Components/Overflow/FluentOverflowTests.razor
@@ -1,4 +1,4 @@
-@using Microsoft.FluentUI.AspNetCore.Components.Utilities
+@using System.Reflection
@using Xunit
@using static Microsoft.FluentUI.AspNetCore.Components.FluentOverflow
@inherits FluentUITestContext
@@ -11,276 +11,240 @@
}
[Fact]
- public void FluentOverflow_Renders_WithoutOverflow()
+ public void FluentOverflow_Renders_Direct_Children()
{
- // Arrange
var cut = Render(@
- Item 1
- Item 2
+ Item 1
+ Item 2
);
- // Assert
- Assert.Equal(2, cut.FindAll(".fluent-overflow-item").Count);
- Assert.Empty(cut.FindAll(".fluent-overflow-item[overflow]"));
+ Assert.Single(cut.FindAll("fluent-overflow"));
+ Assert.Equal(3, cut.FindAll("fluent-overflow > *").Count);
}
[Fact]
- public void FluentOverflow_Handles_FixedEllipsis_Item()
+ public void FluentOverflow_Respects_Orientation()
{
- // Arrange
- var cut = Render(@
- Long text that should ellipsis
- Item 2
+ var cut = Render(@);
- // Assert
- var ellipsisItem = cut.Find(".fluent-overflow-item[fixed='ellipsis']");
- Assert.NotNull(ellipsisItem);
- Assert.Contains("ellipsis", ellipsisItem.GetAttribute("fixed"));
+ var container = cut.Find("fluent-overflow");
+ Assert.Equal("vertical", container.GetAttribute("orientation"));
}
[Fact]
- public void FluentOverflow_Triggers_Overflow_Event()
+ public void FluentOverflow_Renders_MaxRenderedItems_Attribute()
{
- // Arrange
- var overflowRaised = false;
- var cut = Render(@
- @for (int i = 0; i < 20; i++)
- {
- Item @i
- }
+ var cut = Render(@);
- // Act: Simulate resize or trigger refresh (in a real scenario, this might involve JS interop)
- // For simplicity, assume overflow is triggered
+ var container = cut.Find("fluent-overflow");
+ Assert.Equal("5", container.GetAttribute("max-rendered-items"));
+ }
+ [Fact]
+ public void FluentOverflow_Custom_MoreTemplate()
+ {
+ var cut = Render(@
+
+ Custom More
+
+
+ Item 1
+
+ );
- // Assert
- // Note: Actual overflow detection requires JS interop, so this is a placeholder
- Assert.False(overflowRaised); // Adjust based on implementation
+ var customButton = cut.Find("p");
+ Assert.NotNull(customButton);
+ Assert.Equal("Custom More", customButton.TextContent);
}
[Fact]
- public void FluentOverflow_Renders_MoreButton_When_Overflow()
+ public void FluentOverflow_Custom_OverflowTemplate()
{
- // Arrange
var cut = Render(@
- @for (int i = 0; i < 10; i++)
- {
- Very long item text @i
- }
-
);
+
+ Custom Overflow Content
+
+
+ Item 1
+
+ );
- // Assert
- var moreButton = cut.Find(".fluent-overflow-more");
- Assert.NotNull(moreButton);
- Assert.Contains("visibility: hidden", moreButton.GetAttribute("style")); // Initially hidden
+ var tooltip = cut.Find("fluent-tooltip");
+ Assert.NotNull(tooltip);
+ Assert.Contains("Custom Overflow Content", tooltip.TextContent);
}
[Fact]
- public void FluentOverflow_Respects_Orientation()
+ public async Task FluentOverflow_Calls_OverflowRaisedAsync()
{
- // Arrange
- var cut = Render(@
- Item 1
+ var overflowRaised = false;
+ var cut = Render(@);
- // Assert
- var container = cut.Find(".fluent-overflow");
- Assert.Equal("vertical", container.GetAttribute("orientation"));
+ var overflow = cut.FindComponent();
+ await overflow.Instance.OverflowRaisedAsync([
+ new OverflowItem { Id = "item1", Overflow = true, Text = "Item 1", Index = 0 },
+ new OverflowItem { Id = "item2", Overflow = false, Text = "Item 2", Index = 1 }
+ ]);
+
+ Assert.True(overflowRaised);
+ Assert.Single(overflow.Instance.ItemsOverflow);
+ Assert.Equal("Item 1", overflow.Instance.ItemsOverflow.First().Text);
}
[Fact]
- public void FluentOverflow_Item_With_Fixed_Attribute()
+ public void FluentOverflow_Renders_Default_Tooltip_When_OverflowTemplate_Is_Null()
{
- // Arrange
var cut = Render(@
- Fixed Item
- Regular Item
+ Item 1
);
- // Assert
- var fixedItem = cut.Find(".fluent-overflow-item[fixed='fixed']");
- Assert.NotNull(fixedItem);
- Assert.Equal("Fixed Item", fixedItem.TextContent);
+ var tooltip = cut.Find("fluent-tooltip");
+ Assert.NotNull(tooltip);
+ Assert.Equal("below-start", tooltip.GetAttribute("positioning"));
}
[Fact]
- public void FluentOverflow_Custom_MoreButtonTemplate()
+ public async Task FluentOverflow_RefreshAsync_LoadsOverflowItemsAndCount()
{
- // Arrange
- var cut = Render(@
-
- Custom More
-
-
- @for (int i = 0; i < 10; i++)
- {
- Item 1
- }
-
+ var cut = Render(@);
- // Assert
- var customButton = cut.Find("p");
- Assert.NotNull(customButton);
- Assert.Equal("Custom More", customButton.TextContent);
+ JSInterop.SetupVoid("Microsoft.FluentUI.Blazor.Components.Overflow.Refresh", args => (string?)args.Arguments[0] == "overflow")
+ .SetVoidResult();
+ JSInterop.Setup("Microsoft.FluentUI.Blazor.Components.Overflow.GetOverflowStatus", args => (string?)args.Arguments[0] == "overflow")
+ .SetResult([
+ new OverflowItem { Id = "item1", Overflow = true, Text = "Item 1", Behavior = OverflowBehavior.Fixed, Index = 0 },
+ new OverflowItem { Id = "item2", Overflow = false, Text = "Item 2", Behavior = null, Index = 1 }
+ ]);
+ JSInterop.Setup("Microsoft.FluentUI.Blazor.Components.Overflow.GetOverflowStatus", args => (string?)args.Arguments[0] == "overflow")
+ .SetResult(0);
+
+ var overflow = cut.FindComponent();
+ await overflow.Instance.RefreshAsync();
+
+ Assert.Equal(0, overflow.Instance.OverflowCount);
+ var items = overflow.Instance.ItemsOverflow.ToList();
+ Assert.Empty(items);
+ Assert.Contains(JSInterop.Invocations, x => x.Identifier == "Microsoft.FluentUI.Blazor.Components.Overflow.Refresh");
+ Assert.Contains(JSInterop.Invocations, x => x.Identifier == "Microsoft.FluentUI.Blazor.Components.Overflow.GetOverflowState");
}
[Fact]
- public void FluentOverflow_Custom_OverflowTemplate()
+ public async Task FluentOverflow_RefreshAsync_NoJsRuntime_DoesNothing()
{
- // Arrange
- var cut = Render(@
-
- Custom Overflow Content
-
-
- @for (int i = 0; i < 10; i++)
- {
- Item @i
- }
-
-
);
+ var overflow = new FluentOverflow(new LibraryConfiguration());
- // Assert
- var tooltip = cut.Find("fluent-tooltip");
- Assert.NotNull(tooltip);
- Assert.Contains("Custom Overflow Content", tooltip.TextContent);
+ await overflow.RefreshAsync();
+
+ Assert.Equal(0, overflow.OverflowCount);
+ Assert.Empty(overflow.ItemsOverflow);
}
[Fact]
- public async Task FluentOverflow_Calls_OverflowRaisedAsync()
+ public async Task FluentOverflow_OnOverflowChangedAsync_UpdatesStateAndRaisesCallback()
{
- // Arrange
var overflowRaised = false;
var cut = Render(@
- @for (int i = 0; i < 20; i++)
- {
- Item @i
- }
+ Item 1
+ Item 2
);
- // Act
- var overflow = cut.FindComponent();
- await overflow.Instance.OverflowRaisedAsync(new OverflowItem[] { new OverflowItem() { Id = "item1", Overflow = true, Text = "Item 1" } });
+ var overflow = cut.FindComponent();
+ var method = GetPrivateMethod("OnOverflowChangedAsync", typeof(OverflowChangedEventArgs));
+ var args = new OverflowChangedEventArgs
+ {
+ Id = "overflow",
+ OverflowCount = 2,
+ FirstOverflowIndex = 1,
+ Items =
+ [
+ new OverflowChangedItem { Id = "item1", Overflow = true, Text = "Item 1", Behavior = OverflowBehavior.Fixed, Index = 0 },
+ new OverflowChangedItem { Id = "item2", Overflow = false, Text = "Item 2", Behavior = null, Index = 1 }
+ ]
+ };
+
+ await ((Task)method.Invoke(overflow.Instance, [args])!);
- // Assert: Verify the event was raised
Assert.True(overflowRaised);
+ Assert.Equal(2, overflow.Instance.OverflowCount);
+ var items = overflow.Instance.ItemsOverflow.ToList();
+ Assert.Single(items);
+ Assert.Equal("item1", items[0].Id);
+ Assert.Equal(OverflowBehavior.Fixed, items[0].Behavior);
}
- [Fact]
- public async Task FluentOverflow_Calls_OverflowRaisedAsync_With_No_Result()
- {
- // Arrange
- var overflowRaised = false;
- var cut = Render(@
- @for (int i = 0; i < 20; i++)
- {
- Item @i
- }
-
);
-
- // Act
- var overflow = cut.FindComponent();
- await overflow.Instance.OverflowRaisedAsync(new OverflowItem[] { });
-
- // Assert: Verify the event was raised
- Assert.False(overflowRaised);
- }
-
- [Fact]
- public async Task FluentOverflow_Calls_RefreshAsync()
- {
- // Arrange
- var overflowRaised = false;
- var cut = Render(@
- @for (int i = 0; i < 20; i++)
- {
- Item @i
- }
-
- );
-
- // Act
- var overflow = cut.FindComponent();
- await overflow.Instance.RefreshAsync();
-
- // Assert: Verify the event was raised
- Assert.False(overflowRaised);
- }
-
- [Fact]
- public async Task FluentOverflow_Has_Overflow()
- {
- // Arrange
- var overflowRaised = false;
- FluentOverflowItem fluentOverflowItem1 = new(new LibraryConfiguration(), false, "Item 1");
- FluentOverflowItem fluentOverflowItem2 = new(new LibraryConfiguration(), false, "Item 2");
- FluentOverflowItem fluentOverflowItem3 = new(new LibraryConfiguration(), true, "Item 3");
- FluentOverflowItem fluentOverflowItem4 = new(new LibraryConfiguration(), true, "Item 4");
-
- FluentOverflow fluentOverflow = new(new LibraryConfiguration(), new List() { fluentOverflowItem1, fluentOverflowItem2, fluentOverflowItem3, fluentOverflowItem4 });
-
- var cut = Render(@
- Item 1
- Item 2
- Item 3
- Item 4
-
);
-
- // Act
-
- var overflow = cut.FindComponent();
- await overflow.Instance.OverflowRaisedAsync(new OverflowItem[] {
- new OverflowItem() { Id = "item3", Overflow = true, Text = "Item 3" },
- new OverflowItem() { Id = "item4", Overflow = true, Text = "Item 4" }
- });
- overflow.Render();
-
- // Assert: Verify the event was raised
- Assert.True(overflowRaised);
-}
+ [Fact]
+ public async Task FluentOverflow_OnOverflowChangedAsync_IgnoresDifferentComponentId()
+ {
+ var overflowRaised = false;
+ var cut = Render(@);
+
+ var overflow = cut.FindComponent();
+ await overflow.Instance.OverflowRaisedAsync([
+ new OverflowItem { Id = "item1", Overflow = true, Text = "Item 1", Index = 0 }
+ ]);
+ overflowRaised = false;
+
+ var method = GetPrivateMethod("OnOverflowChangedAsync", typeof(OverflowChangedEventArgs));
+ var args = new OverflowChangedEventArgs
+ {
+ Id = "other-overflow",
+ OverflowCount = 0,
+ Items =
+ [
+ new OverflowChangedItem { Id = "item1", Overflow = false, Text = "Item 1", Index = 0 }
+ ]
+ };
+
+ await ((Task)method.Invoke(overflow.Instance, [args])!);
+
+ Assert.False(overflowRaised);
+ Assert.Equal(1, overflow.Instance.OverflowCount);
+ Assert.Single(overflow.Instance.ItemsOverflow);
+ }
+
+ [Fact]
+ public void FluentOverflow_SetOverflowItems_OverflowChangedItemOverload_SetsExpectedState()
+ {
+ var cut = Render(@);
+ var overflow = cut.FindComponent();
+ var method = GetPrivateMethod("SetOverflowItems", typeof(IEnumerable), typeof(int));
+
+ method.Invoke(overflow.Instance, new object?[]
+ {
+ new OverflowChangedItem[]
+ {
+ new() { Id = "item1", Overflow = true, Text = "Item 1", Behavior = OverflowBehavior.Fixed, Index = 2 },
+ new() { Id = "item2", Overflow = false, Text = "Item 2", Behavior = null, Index = 3 }
+ },
+ 6
+ });
+
+ Assert.Equal(6, overflow.Instance.OverflowCount);
+ var items = overflow.Instance.ItemsOverflow.ToList();
+ Assert.Single(items);
+ Assert.Equal("item1", items[0].Id);
+ Assert.Equal(OverflowBehavior.Fixed, items[0].Behavior);
+ Assert.Equal(2, items[0].Index);
+
+ method.Invoke(overflow.Instance, new object?[] { null, -1 });
+ Assert.Equal(0, overflow.Instance.OverflowCount);
+ Assert.Empty(overflow.Instance.ItemsOverflow);
+ }
- [Fact]
- public void FluentOverflow_Renders_Default_Tooltip_When_OverflowTemplate_Is_Null()
- {
- // Arrange
- var cut = Render(@
- @for (int i = 0; i < 10; i++)
- {
- Item @i
- }
-
);
-
- // Assert - Default FluentTooltip should be rendered
- var tooltip = cut.Find("fluent-tooltip");
- Assert.NotNull(tooltip);
- Assert.Equal("below-start", tooltip.GetAttribute("positioning"));
- }
-
- [Fact]
- public void FluentOverflow_Renders_Custom_OverflowTemplate_Instead_Of_Default_Tooltip()
- {
- // Arrange
- var cut = Render(@
-
- Custom overflow content
-
-
- @for (int i = 0; i < 10; i++)
- {
- Item @i
- }
-
- );
-
- // Assert - Custom template should be rendered instead of default tooltip
- var customOverflow = cut.Find(".custom-overflow");
- Assert.NotNull(customOverflow);
- Assert.Equal("Custom overflow content", customOverflow.TextContent);
-
- // Default tooltip should not be present
- var tooltips = cut.FindAll("fluent-tooltip");
- Assert.Empty(tooltips);
- }
+ private static MethodInfo GetPrivateMethod(string name, params Type[] parameterTypes) =>
+ typeof(FluentOverflow).GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic, null, parameterTypes, null)
+ ?? throw new InvalidOperationException($"Private method '{name}' not found.");
}
diff --git a/tests/Core/Components/Overflow/OverflowChangedEventArgsTests.cs b/tests/Core/Components/Overflow/OverflowChangedEventArgsTests.cs
new file mode 100644
index 0000000000..a24c54ffdd
--- /dev/null
+++ b/tests/Core/Components/Overflow/OverflowChangedEventArgsTests.cs
@@ -0,0 +1,97 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+using Xunit;
+
+namespace Microsoft.FluentUI.AspNetCore.Components.Tests.Components.Overflow;
+
+public class OverflowChangedEventArgsTests
+{
+ [Fact]
+ public void OverflowChangedEventArgs_Defaults_AreExpected()
+ {
+ // Act
+ var args = new OverflowChangedEventArgs();
+
+ // Assert
+ Assert.Null(args.Id);
+ Assert.Null(args.Items);
+ Assert.Equal(0, args.OverflowCount);
+ Assert.Equal(-1, args.FirstOverflowIndex);
+ Assert.Null(args.OrderedItemIds);
+ }
+
+ [Fact]
+ public void OverflowChangedEventArgs_Sets_AllProperties()
+ {
+ // Arrange
+ IReadOnlyList items =
+ [
+ new()
+ {
+ Id = "item-1",
+ Overflow = true,
+ Text = "Item 1",
+ Behavior = OverflowBehavior.Fixed,
+ Index = 3
+ }
+ ];
+ IReadOnlyList orderedItemIds = ["item-0", "item-1", "item-2"];
+
+ // Act
+ var args = new OverflowChangedEventArgs
+ {
+ Id = "overflow-1",
+ Items = items,
+ OverflowCount = 5,
+ FirstOverflowIndex = 3,
+ OrderedItemIds = orderedItemIds
+ };
+
+ // Assert
+ Assert.Equal("overflow-1", args.Id);
+ Assert.Same(items, args.Items);
+ Assert.Equal(5, args.OverflowCount);
+ Assert.Equal(3, args.FirstOverflowIndex);
+ Assert.Same(orderedItemIds, args.OrderedItemIds);
+ }
+}
+
+public class OverflowChangedItemTests
+{
+ [Fact]
+ public void OverflowChangedItem_Defaults_AreExpected()
+ {
+ // Act
+ var item = new OverflowChangedItem();
+
+ // Assert
+ Assert.Null(item.Id);
+ Assert.False(item.Overflow);
+ Assert.Null(item.Text);
+ Assert.Null(item.Behavior);
+ Assert.Equal(0, item.Index);
+ }
+
+ [Fact]
+ public void OverflowChangedItem_Sets_AllProperties()
+ {
+ // Act
+ var item = new OverflowChangedItem
+ {
+ Id = "item-2",
+ Overflow = true,
+ Text = "Item 2",
+ Behavior = OverflowBehavior.Ellipsis,
+ Index = 4
+ };
+
+ // Assert
+ Assert.Equal("item-2", item.Id);
+ Assert.True(item.Overflow);
+ Assert.Equal("Item 2", item.Text);
+ Assert.Equal(OverflowBehavior.Ellipsis, item.Behavior);
+ Assert.Equal(4, item.Index);
+ }
+}
diff --git a/tests/Core/Components/Overflow/OverflowItemStateTests.cs b/tests/Core/Components/Overflow/OverflowItemStateTests.cs
new file mode 100644
index 0000000000..37c07a2571
--- /dev/null
+++ b/tests/Core/Components/Overflow/OverflowItemStateTests.cs
@@ -0,0 +1,56 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+using Xunit;
+
+namespace Microsoft.FluentUI.AspNetCore.Components.Tests.Components.Overflow;
+
+public class OverflowStateTests
+{
+ [Fact]
+ public void OverflowState_Defaults_AreExpected()
+ {
+ // Act
+ var state = new OverflowState();
+
+ // Assert
+ Assert.Null(state.OverflowItems);
+ Assert.Equal(0, state.OverflowCount);
+ Assert.Equal(0, state.FirstOverflowIndex);
+ Assert.Null(state.OrderedItemIds);
+ }
+
+ [Fact]
+ public void OverflowState_Sets_AllProperties()
+ {
+ // Arrange
+ OverflowItem[] overflowItems =
+ [
+ new()
+ {
+ Id = "item-2",
+ Overflow = true,
+ Text = "Item 2",
+ Behavior = OverflowBehavior.Ellipsis,
+ Index = 3,
+ },
+ ];
+ string[] orderedItemIds = ["item-0", "item-1", "item-2"];
+
+ // Act
+ var state = new OverflowState
+ {
+ OverflowItems = overflowItems,
+ OverflowCount = 4,
+ FirstOverflowIndex = 3,
+ OrderedItemIds = orderedItemIds,
+ };
+
+ // Assert
+ Assert.Same(overflowItems, state.OverflowItems);
+ Assert.Equal(4, state.OverflowCount);
+ Assert.Equal(3, state.FirstOverflowIndex);
+ Assert.Same(orderedItemIds, state.OrderedItemIds);
+ }
+}
diff --git a/tests/Core/Components/Overflow/OverflowItemTests.cs b/tests/Core/Components/Overflow/OverflowItemTests.cs
new file mode 100644
index 0000000000..972291533e
--- /dev/null
+++ b/tests/Core/Components/Overflow/OverflowItemTests.cs
@@ -0,0 +1,45 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+using Xunit;
+
+namespace Microsoft.FluentUI.AspNetCore.Components.Tests.Components.Overflow;
+
+public class OverflowItemTests
+{
+ [Fact]
+ public void OverflowItem_Defaults_AreExpected()
+ {
+ // Act
+ var item = new OverflowItem();
+
+ // Assert
+ Assert.Null(item.Id);
+ Assert.False(item.Overflow);
+ Assert.Null(item.Text);
+ Assert.Null(item.Behavior);
+ Assert.Equal(0, item.Index);
+ }
+
+ [Fact]
+ public void OverflowItem_Sets_AllProperties()
+ {
+ // Act
+ var item = new OverflowItem
+ {
+ Id = "item-1",
+ Overflow = true,
+ Text = "Item 1",
+ Behavior = OverflowBehavior.Fixed,
+ Index = 2,
+ };
+
+ // Assert
+ Assert.Equal("item-1", item.Id);
+ Assert.True(item.Overflow);
+ Assert.Equal("Item 1", item.Text);
+ Assert.Equal(OverflowBehavior.Fixed, item.Behavior);
+ Assert.Equal(2, item.Index);
+ }
+}