[dev-v5] Redesign FluentOverflow with generic item and template support - #5268
Denis Voituron (dvoituron) merged 26 commits into
Conversation
…xRenderedItems for improved clarity and functionality
…for ellipsis divs
- Added FluentOverflowController to manage overflow behavior for elements. - Introduced FluentOverflowInterop for imperative API to attach overflow behavior. - Created FluentOverflowStyles for component styling. - Defined FluentOverflowTemplate for Shadow DOM structure. - Established FluentOverflowTypes for public types and configurations.
…tests for overflow behavior
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved moderate findings affect overflow lifecycle, layout, trigger behavior, and state handling.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Redesigns FluentOverflow as a generic, template-driven component with updated JavaScript handling and AppBar/Tabs integrations.
Changes:
- Adds typed items, templates, contexts, limits, and callbacks.
- Reworks overflow measurement, visibility, and lifecycle handling.
- Updates tests, documentation, examples, and event models.
File summaries
| File | Reviewed change |
|---|---|
tests/Integration/Components/Overflow/FluentOverflowTests.cs |
Adds integration coverage for overflow scenarios. |
tests/Integration/Components/Overflow/Examples/SchedulingOverflow.razor |
Updates the scheduling fixture for generic overflow. |
tests/Integration/Components/Overflow/Examples/ConsumersPage.razor |
Adds consumer integration scenarios. |
tests/Core/Serialization/EventArgsJsonTests.cs |
Updates overflow payload serialization expectations. |
tests/Core/Components/Tabs/FluentTabsTests.razor |
Updates Tabs interop tests. |
tests/Core/Components/Overflow/OverflowStateTests.cs |
Removes obsolete state tests. |
tests/Core/Components/Overflow/OverflowItemTests.cs |
Updates overflow item tests. |
tests/Core/Components/Overflow/OverflowChangedEventArgsTests.cs |
Tests revised event payloads. |
tests/Core/Components/Overflow/FluentOverflowTests.razor |
Adds generic component and template coverage. |
tests/Core/Components/DataGrid/FluentDataGridTests.razor |
Adjusts virtualization test conditions. |
tests/Core/Components/Base/ComponentBaseTests.cs |
Registers the generic component for testing. |
tests/Core/Components/AppBar/FluentAppBarTests.razor |
Updates AppBar overflow tests. |
src/Core/Events/OverflowChangedItem.cs |
Removes obsolete overflow metadata. |
src/Core/Events/OverflowChangedEventArgs.cs |
Simplifies overflow event arguments. |
src/Core/Components/Tabs/FluentTabs.razor.cs |
Uses the new Tabs overflow controller API. |
src/Core/Components/Overflow/OverflowState.cs |
Removes the obsolete overflow state type. |
src/Core/Components/Overflow/OverflowItem.cs |
Represents hidden overflow items. |
src/Core/Components/Overflow/OverflowContext.cs |
Adds typed template context. |
src/Core/Components/Overflow/FluentOverflow.razor.css |
Updates overflow styling. |
src/Core/Components/Overflow/FluentOverflow.razor.cs |
Implements generic items, templates, limits, and callbacks. |
src/Core/Components/Overflow/FluentOverflow.razor |
Renders typed items and overflow templates. |
src/Core/Components/AppBar/FluentAppBar.razor.css |
Updates AppBar hidden-item styling. |
src/Core/Components/AppBar/FluentAppBar.razor.cs |
Applies hidden overflow item IDs. |
src/Core/Components/AppBar/FluentAppBar.razor |
Integrates the overflow trigger. |
src/Core.Scripts/src/FluentUICustomEvents.ts |
Updates overflow event conversion. |
src/Core.Scripts/src/ExportedMethods.ts |
Exposes overflow interop methods. |
src/Core.Scripts/src/Components/Tabs/FluentTabs.ts |
Adds shared overflow controller integration. |
src/Core.Scripts/src/Components/Overflow/README.md |
Documents the JavaScript overflow API. |
src/Core.Scripts/src/Components/Overflow/FluentOverflowTypes.ts |
Defines controller and event types. |
src/Core.Scripts/src/Components/Overflow/FluentOverflowTemplate.ts |
Defines the shadow DOM template. |
src/Core.Scripts/src/Components/Overflow/FluentOverflowStyles.ts |
Defines shadow DOM styles. |
src/Core.Scripts/src/Components/Overflow/FluentOverflowInterop.ts |
Adds imperative overflow lifecycle APIs. |
src/Core.Scripts/src/Components/Overflow/FluentOverflowController.ts |
Implements overflow measurement and visibility logic. |
src/Core.Scripts/src/Components/Overflow/FluentOverflowAttachedController.ts |
Adds an attached controller implementation. |
src/Core.Scripts/src/Components/Overflow/FluentOverflow.ts |
Rebuilds the overflow custom element. |
src/Core.Scripts/src/Components/Overflow/AttachedOverflowController.ts |
Removes the obsolete attached controller. |
examples/Demo/FluentUI.Demo.Client/Documentation/GetStarted/Migration/MigrationFluentOverflow.md |
Documents migration to generic overflow. |
examples/Demo/FluentUI.Demo.Client/Documentation/Components/Overflow/FluentOverflow.md |
Documents generic overflow usage. |
examples/Demo/FluentUI.Demo.Client/Documentation/Components/Overflow/Examples/OverflowVisibleOnLoad.razor |
Updates the visible-on-load example. |
examples/Demo/FluentUI.Demo.Client/Documentation/Components/Overflow/Examples/OverflowSelectorExample.razor |
Updates the selector example. |
examples/Demo/FluentUI.Demo.Client/Documentation/Components/Overflow/Examples/OverflowMultipleFixedItemsExample.razor |
Updates overflow behavior examples. |
examples/Demo/FluentUI.Demo.Client/Documentation/Components/Overflow/Examples/OverflowMaxRenderedItemsExample.razor |
Removes the obsolete payload-limit example. |
examples/Demo/FluentUI.Demo.Client/Documentation/Components/Overflow/Examples/OverflowItemsExample.razor |
Adds a typed-items example. |
examples/Demo/FluentUI.Demo.Client/Documentation/Components/Overflow/Examples/OverflowEllipsis.razor |
Removes the obsolete ellipsis example. |
examples/Demo/FluentUI.Demo.Client/Documentation/Components/Overflow/Examples/OverflowDefault.razor |
Updates the default overflow example. |
examples/Demo/FluentUI.Demo.Client/Documentation/Components/Overflow/Examples/OverflowCustomPopupExample.razor |
Adds a custom popup example. |
examples/Demo/FluentUI.Demo.Client/Documentation/Components/Overflow/Examples/OverflowCustomExample.razor |
Updates the custom overflow example. |
Review details
Suppressed comments (5)
src/Core.Scripts/src/Components/Overflow/FluentOverflowController.ts:450
- When an item is hidden here it is unobserved, but its cached
getOuterSizevalue is retained. If the item changes size while hidden (for example through responsive styling, a font load, or content changes that do not mutate the item), the next layout reuses that stale measurement and can hide the wrong items. InvalidatemeasuredSizeswhen unobserving hidden items, or continue observing them so their size cache stays current.
for (const item of this.observedItems) {
if (!nextItems.has(item) || hidden.has(item)) {
this.resizeObserver.unobserve(item);
this.observedItems.delete(item);
src/Core.Scripts/src/Components/Overflow/FluentOverflowController.ts:500
- An external
hiddenmutation on an item that this controller previously hid does not clear itshiddenByControllerownership. The next refresh therefore excludes it fromconsumerHidden, unhides it in the loop above, and may make it visible again even though the consumer requested it to stay hidden. Clear controller ownership when an unexpected hidden mutation is detected so the documented consumer-hidden state is preserved.
return true;
src/Core.Scripts/src/Components/Overflow/FluentOverflowInterop.ts:50
disposeOverflowcallsOverflow.dispose(), which clears the custom element's controller, but this custom-element branch ofinitializeOverflowonly changes attributes and callsrefresh(). Therefore aninitializeOverflowcall afterdisposeOverflowleaves the still-connected<fluent-overflow>permanently unmanaged becauserefresh()is a no-op without a controller. Add a reconnect/initialize method onOverflowand invoke it here, or otherwise recreate the controller before refreshing.
if (element instanceof Overflow) {
element.selector = querySelector;
element.threshold = threshold;
element.maxOverflowItems = maxRenderedItems <= 0
? Number.POSITIVE_INFINITY
: maxRenderedItems;
element.refresh();
src/Core/Components/AppBar/FluentAppBar.razor:21
- This AppBar uses the raw custom element, so the
FluentOverflow.razor.csshost rule that sets the spacing variable is not applied. The new shadow controller reads--fluent-overflow-gap, whileOverflowStyleValuestill supplies onlygap: 2px(a non-inherited property), so AppBar layout is measured with the controller's 4px default instead of its configured gap. Pass--fluent-overflow-gap: 2pxin the AppBar overflow style, including the vertical case.
<div id="@($"appbar-more-{Id}")" class="fluent-appbar-more-item" style="min-width: var(--appbar-item-size);" tabindex="0" slot="trigger" title="@Localizer[Localization.LanguageResource.AppBar_MoreItems]" @onclick="@TogglePopoverAsync">
src/Core/Components/AppBar/FluentAppBar.razor:21
FluentOverflow.razoralways renders its ownslot="trigger"element, so adding this second slotted element gives the AppBar two assigned triggers. Both are laid out when overflow is active, the default+Nbadge appears beside the AppBar's ellipsis trigger, and selectors such as the integration test's[slot='trigger']become ambiguous. Keep a single trigger (or add a way to suppress the wrapper's default trigger) and anchor the popover to that trigger.
<div id="@($"appbar-more-{Id}")" class="fluent-appbar-more-item" style="min-width: var(--appbar-item-size);" tabindex="0" slot="trigger" title="@Localizer[Localization.LanguageResource.AppBar_MoreItems]" @onclick="@TogglePopoverAsync">
- Files reviewed: 47/47 changed files
- Comments generated: 5
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
✅ All tests passed successfully Details on your Workflow / Core Tests page. |
Summary - Unit Tests Code CoverageSummary
CoverageMicrosoft.FluentUI.AspNetCore.Components - 98.5%
Microsoft.FluentUI.AspNetCore.Components.Charts - 100%
|
…content rerendering
|
James Newton-King (@JamesNK) What do you think? |
James Newton-King (JamesNK)
left a comment
There was a problem hiding this comment.
I found three issues that should be addressed before merge:
- constrained children are measured by intrinsic scroll size instead of their rendered layout size, which can hide items that actually fit;
- the migration guide omits removed public event/item members while saying existing callbacks can remain unchanged;
- Overflow and AppBar triggers are visible with misleading counts during prerender/no-JS before the custom element upgrades.
I verified the current Overflow revision in an isolated worktree and browser server. The eight new Overflow integration tests pass, and direct browser checks covered resize scheduling, selectors/fixed/ellipsis behavior, bounded rendering, Tabs keyboard/disposal, and horizontal/vertical AppBar behavior. Details and reproduction evidence are inline.
I don’t agree; it’s the developer’s responsibility to set a min-width for their container if they want to have enough space to display the fixed elements. If there isn’t enough space, it behaves just like any other HTML element.
I agree. I’ll have a look at that. |
|
Comments are from AI. Feel free to ignore. |
…ior in FluentOverflow component
Fixed in next commit peek_1.mp4
Fixed in next commit peek_2.mp4 |


[dev-v5] Redesign FluentOverflow with generic item and template support
Summary
This PR redesigns
FluentOverflowas a generic, collection-driven component.It adds support for strongly typed items and templates, custom overflow and "more" content, configurable rendered-item limits, item text conversion, and callbacks when the "more" button is clicked.
The Overflow JavaScript controller and interop logic have also been updated, along with the AppBar and Tabs integrations, documentation, examples, and tests.
Example
peek_3.mp4
Changes
Added
FluentOverflow<TItem>IEnumerable<TItem>? ItemsRenderFragment<TItem>? ItemTemplateFunc<TItem, string>? ItemTextEventCallback<MouseEventArgs> OnMoreClickint MaxRenderedItemsRenderFragment<OverflowContext<TItem>>? OverflowTemplateRenderFragment<OverflowContext<TItem>>? MoreTemplateIReadOnlyList<OverflowItem> ItemsOverflowint OverflowCountA new
OverflowContext<TItem>type has been added with the following properties:IReadOnlyList<TItem> ItemsIReadOnlyList<OverflowItem> ItemsOverflowint OverflowCountstring IdMoreButtonRemoved or replaced
FluentOverflowcomponent has been replaced byFluentOverflow<TItem>.StoreOverflowInMemoryhas been removed.MaxOverflowItemshas been removed and replaced byMaxRenderedItems.RenderFragment<FluentOverflow>? OverflowTemplatehas been replaced byRenderFragment<OverflowContext<TItem>>?.RenderFragment<FluentOverflow>? MoreTemplatehas been replaced byRenderFragment<OverflowContext<TItem>>?.ItemsOverflowreturn type has changed fromIEnumerable<OverflowItem>toIReadOnlyList<OverflowItem>.Breaking changes
Consumers must now provide the item type through
FluentOverflow<TItem>and update overflow or "more" templates to useOverflowContext<TItem>.Validation
Unit Tests