Skip to content

[dev-v5][DataGrid] Add pinned (sticky/frozen) column support#4671

Open
Copilot wants to merge 12 commits intodev-v5from
copilot/research-fluentdatagrid-implementation
Open

[dev-v5][DataGrid] Add pinned (sticky/frozen) column support#4671
Copilot wants to merge 12 commits intodev-v5from
copilot/research-fluentdatagrid-implementation

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 2, 2026

This pull request adds support for pinned (frozen) columns to the FluentDataGrid component, allowing columns to remain visible while horizontally scrolling. It introduces a new Pin parameter for columns, enforces validation and layout rules, updates the CSS for proper appearance, and enhances the JavaScript to keep sticky offsets in sync during resizing. Documentation and demo examples are also provided.

image

Pinned Columns Feature:

  • Added a Pin parameter to ColumnBase<TGridItem> (and UI/JS support) to allow columns to be pinned to the left or right edge of the grid, with validation for explicit pixel widths and contiguous placement. Throws descriptive exceptions for invalid configurations. (src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs, src/Core/Components/DataGrid/FluentDataGrid.razor.cs, [1] [2] [3]
  • Updated CSS to style pinned columns with a solid background, visual separators, and theming support via --fluent-data-grid-pinned-background. Ensures pinned headers stack above data cells and handles sticky header row correctly. (src/Core/Components/DataGrid/FluentDataGrid.razor.css, [1] [2]

JavaScript Integration:

  • Enhanced the grid's JavaScript to recalculate sticky offsets after rendering and after every column resize, ensuring correct positioning and minimum width enforcement. (src/Core/Components/DataGrid/FluentDataGrid.razor.ts, [1] [2] [3] [4] [5] [6]

Documentation and Examples:

Other improvements:

  • Minor code and style tweaks for header menu layering and button sizing. (src/Core/Components/DataGrid/Columns/ColumnBase.razor, [1] [2]

@vnbaaij
Copy link
Copy Markdown
Collaborator

vnbaaij commented Apr 3, 2026

@copilot remove the obj\Debug folder and files from the src/Core.Scripts/ path. These are build artifacts and should not be included in the repo

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 3, 2026

@copilot remove the obj\Debug folder and files from the src/Core.Scripts/ path. These are build artifacts and should not be included in the repo

Done in e1acae8. Removed src/Core.Scripts/obj\Debug\package.g.props from tracking and added src/Core.Scripts/.gitignore with obj/ to prevent the directory from being committed again.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 3, 2026

✅ All tests passed successfully

Details on your Workflow / Core Tests page.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 3, 2026

Summary - Unit Tests Code Coverage

Summary
Generated on: 04/04/2026 - 09:27:08
Coverage date: 04/04/2026 - 09:26:40
Parser: Cobertura
Assemblies: 1
Classes: 234
Files: 321
Line coverage: 98.9% (9731 of 9836)
Covered lines: 9731
Uncovered lines: 105
Coverable lines: 9836
Total lines: 34979
Branch coverage: 92.1% (4762 of 5166)
Covered branches: 4762
Total branches: 5166
Method coverage: Feature is only available for sponsors
Tag: 5817_23976077980

Coverage

Microsoft.FluentUI.AspNetCore.Components - 98.9%
Name Line Branch
Microsoft.FluentUI.AspNetCore.Components 98.9% 92.1%
Microsoft.FluentUI.AspNetCore.Components.AccordionItemEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.AdditionalAttributesExtensions 100% 100%
Microsoft.FluentUI.AspNetCore.Components.CachedServices 100% 100%
Microsoft.FluentUI.AspNetCore.Components.Calendar.CalendarExtended 100% 100%
Microsoft.FluentUI.AspNetCore.Components.Calendar.CalendarTitles`1 100% 100%
Microsoft.FluentUI.AspNetCore.Components.Calendar.CalendarTValue 100% 100%
Microsoft.FluentUI.AspNetCore.Components.ColumnBase`1 97.3% 90.2%
Microsoft.FluentUI.AspNetCore.Components.ColumnKeyGridSort`1 94.4% 75%
Microsoft.FluentUI.AspNetCore.Components.ColumnOptionsUISettings 100%
Microsoft.FluentUI.AspNetCore.Components.ColumnResizeOptions`1 100% 100%
Microsoft.FluentUI.AspNetCore.Components.ColumnResizeUISettings 100%
Microsoft.FluentUI.AspNetCore.Components.ColumnSortUISettings 100%
Microsoft.FluentUI.AspNetCore.Components.CustomEmoji 100% 100%
Microsoft.FluentUI.AspNetCore.Components.CustomIcon 100%
Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure.Defer 100%
Microsoft.FluentUI.AspNetCore.Components.DataGrid.Infrastructure.InternalGr
idContext`1
100% 100%
Microsoft.FluentUI.AspNetCore.Components.DataGridSortEventArgs`1 100%
Microsoft.FluentUI.AspNetCore.Components.DateTimeProvider 100% 100%
Microsoft.FluentUI.AspNetCore.Components.DateTimeProviderContext 100% 92.8%
Microsoft.FluentUI.AspNetCore.Components.DefaultStyles 100%
Microsoft.FluentUI.AspNetCore.Components.Dialog.MessageBox.FluentMessageBox 100% 75%
Microsoft.FluentUI.AspNetCore.Components.DialogEventArgs 100% 92.8%
Microsoft.FluentUI.AspNetCore.Components.DialogInstance 100% 100%
Microsoft.FluentUI.AspNetCore.Components.DialogOptions 100%
Microsoft.FluentUI.AspNetCore.Components.DialogOptionsFooter 100% 100%
Microsoft.FluentUI.AspNetCore.Components.DialogOptionsFooterAction 100% 100%
Microsoft.FluentUI.AspNetCore.Components.DialogOptionsHeader 100%
Microsoft.FluentUI.AspNetCore.Components.DialogResult 100% 100%
Microsoft.FluentUI.AspNetCore.Components.DialogResult`1 100%
Microsoft.FluentUI.AspNetCore.Components.DialogService 100% 82.3%
Microsoft.FluentUI.AspNetCore.Components.DialogToggleEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.DropdownEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.Emoji 100% 100%
Microsoft.FluentUI.AspNetCore.Components.EmojiCompress 100% 100%
Microsoft.FluentUI.AspNetCore.Components.EmojiExtensions 100% 50%
Microsoft.FluentUI.AspNetCore.Components.EmojiInfo 100%
Microsoft.FluentUI.AspNetCore.Components.Extensions.DateTimeExtensions 98.4% 90.3%
Microsoft.FluentUI.AspNetCore.Components.Extensions.DisplayAttributeExtensi
ons
100% 100%
Microsoft.FluentUI.AspNetCore.Components.Extensions.EnumExtensions 100% 100%
Microsoft.FluentUI.AspNetCore.Components.Extensions.FieldSizeExtensions 100% 100%
Microsoft.FluentUI.AspNetCore.Components.Extensions.FluentInputExtensions 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FileSizeConverter 100%
Microsoft.FluentUI.AspNetCore.Components.FluentAccordion 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentAccordionItem 100% 95.8%
Microsoft.FluentUI.AspNetCore.Components.FluentAnchorButton 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentAppBar 97.4% 97.6%
Microsoft.FluentUI.AspNetCore.Components.FluentAppBarItem 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentAvatar 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentBadge 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentButton 98.4% 90.6%
Microsoft.FluentUI.AspNetCore.Components.FluentCalendar`1 97% 85%
Microsoft.FluentUI.AspNetCore.Components.FluentCalendarBase`1 100% 94.4%
Microsoft.FluentUI.AspNetCore.Components.FluentCalendarDay`1 100% 95.8%
Microsoft.FluentUI.AspNetCore.Components.FluentCalendarMonth`1 100% 85.7%
Microsoft.FluentUI.AspNetCore.Components.FluentCalendarYear`1 100% 91.6%
Microsoft.FluentUI.AspNetCore.Components.FluentCard 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentCheckbox 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentCombobox`2 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentComponentBase 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentCompoundButton 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentCounterBadge 100% 94.4%
Microsoft.FluentUI.AspNetCore.Components.FluentDataGrid`1 92.7% 88.5%
Microsoft.FluentUI.AspNetCore.Components.FluentDataGridCell`1 100% 95.3%
Microsoft.FluentUI.AspNetCore.Components.FluentDataGridRow`1 98.5% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentDatePicker`1 97.4% 85.2%
Microsoft.FluentUI.AspNetCore.Components.FluentDialog 97.5% 89.3%
Microsoft.FluentUI.AspNetCore.Components.FluentDialogBody 100% 91.6%
Microsoft.FluentUI.AspNetCore.Components.FluentDialogInstance 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentDialogProvider 100% 73.9%
Microsoft.FluentUI.AspNetCore.Components.FluentDivider 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentDragContainer`1 100%
Microsoft.FluentUI.AspNetCore.Components.FluentDragEventArgs`1 100%
Microsoft.FluentUI.AspNetCore.Components.FluentDropZone`1 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentEmoji`1 100% 95%
Microsoft.FluentUI.AspNetCore.Components.FluentErrorBoundary 95.3% 93.7%
Microsoft.FluentUI.AspNetCore.Components.FluentField 100% 96.5%
Microsoft.FluentUI.AspNetCore.Components.FluentFieldCondition 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentFieldConditionItem 100%
Microsoft.FluentUI.AspNetCore.Components.FluentFieldConditionOptions 100%
Microsoft.FluentUI.AspNetCore.Components.FluentFieldExtensions 100%
Microsoft.FluentUI.AspNetCore.Components.FluentFieldParameterSelector 100% 98.7%
Microsoft.FluentUI.AspNetCore.Components.FluentGrid 100% 90%
Microsoft.FluentUI.AspNetCore.Components.FluentGridItem 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentHighlighter 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentIcon`1 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentImage 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentInputBase`1 93.8% 50%
Microsoft.FluentUI.AspNetCore.Components.FluentInputFile 100% 93.3%
Microsoft.FluentUI.AspNetCore.Components.FluentInputFileBuffer 100%
Microsoft.FluentUI.AspNetCore.Components.FluentInputFileErrorEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.FluentInputFileEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.FluentInputImmediateBase`1 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentJSModule 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentKeyCode 100% 92.8%
Microsoft.FluentUI.AspNetCore.Components.FluentKeyCodeEventArgs 100% 75%
Microsoft.FluentUI.AspNetCore.Components.FluentKeyCodeProvider 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentKeyPressEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.FluentLabel 100%
Microsoft.FluentUI.AspNetCore.Components.FluentLayout 100% 88.8%
Microsoft.FluentUI.AspNetCore.Components.FluentLayoutHamburger 100% 96.6%
Microsoft.FluentUI.AspNetCore.Components.FluentLayoutItem 100% 91%
Microsoft.FluentUI.AspNetCore.Components.FluentLink 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentListBase`2 97.8% 90.2%
Microsoft.FluentUI.AspNetCore.Components.FluentListbox`2 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentLocalizerExtensions 100%
Microsoft.FluentUI.AspNetCore.Components.FluentLocalizerInternal 100%
Microsoft.FluentUI.AspNetCore.Components.FluentMenu 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentMenuButton 100% 71.4%
Microsoft.FluentUI.AspNetCore.Components.FluentMenuItem 100% 90.4%
Microsoft.FluentUI.AspNetCore.Components.FluentMenuList 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentMessageBar 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentMultiSplitter 100% 93.4%
Microsoft.FluentUI.AspNetCore.Components.FluentMultiSplitterEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.FluentMultiSplitterPane 100% 87.5%
Microsoft.FluentUI.AspNetCore.Components.FluentMultiSplitterResizeEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.FluentNav 100% 95.4%
Microsoft.FluentUI.AspNetCore.Components.FluentNavBase 100%
Microsoft.FluentUI.AspNetCore.Components.FluentNavCategory 97.8% 92.1%
Microsoft.FluentUI.AspNetCore.Components.FluentNavItem 100% 89.7%
Microsoft.FluentUI.AspNetCore.Components.FluentNavSectionHeader 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentOption`1 95.8% 91.6%
Microsoft.FluentUI.AspNetCore.Components.FluentOptionString 100%
Microsoft.FluentUI.AspNetCore.Components.FluentOverflow 100% 95.8%
Microsoft.FluentUI.AspNetCore.Components.FluentOverflowItem 100% 80%
Microsoft.FluentUI.AspNetCore.Components.FluentOverlay 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentPaginator 100% 94.4%
Microsoft.FluentUI.AspNetCore.Components.FluentPopover 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentPresenceBadge 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentProgress 100%
Microsoft.FluentUI.AspNetCore.Components.FluentProgressBar 100% 87.5%
Microsoft.FluentUI.AspNetCore.Components.FluentProgressRing 100%
Microsoft.FluentUI.AspNetCore.Components.FluentProviders 100%
Microsoft.FluentUI.AspNetCore.Components.FluentPullToRefresh 100% 96.7%
Microsoft.FluentUI.AspNetCore.Components.FluentRadio`1 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentRadioGroup`1 100% 88.8%
Microsoft.FluentUI.AspNetCore.Components.FluentRatingDisplay 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentSelect`2 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentServiceBase`1 100%
Microsoft.FluentUI.AspNetCore.Components.FluentServiceProviderException`1 100%
Microsoft.FluentUI.AspNetCore.Components.FluentSkeleton 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentSlider`1 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentSortableList`1 95.8% 92.3%
Microsoft.FluentUI.AspNetCore.Components.FluentSortableListEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.FluentSpacer 100%
Microsoft.FluentUI.AspNetCore.Components.FluentSpinner 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentSplitButton 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentStack 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentStatus 100%
Microsoft.FluentUI.AspNetCore.Components.FluentSwitch 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentTab 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentTabs 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentText 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentTextArea 100% 70%
Microsoft.FluentUI.AspNetCore.Components.FluentTextInput 100% 85%
Microsoft.FluentUI.AspNetCore.Components.FluentTimePicker`1 98.9% 75.3%
Microsoft.FluentUI.AspNetCore.Components.FluentToast 100% 92.8%
Microsoft.FluentUI.AspNetCore.Components.FluentToastInstance 100%
Microsoft.FluentUI.AspNetCore.Components.FluentToastProvider 100% 80%
Microsoft.FluentUI.AspNetCore.Components.FluentToggleButton 100% 91.6%
Microsoft.FluentUI.AspNetCore.Components.FluentTooltip 100% 95%
Microsoft.FluentUI.AspNetCore.Components.FluentTooltipProvider 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentTreeItem 100% 95.3%
Microsoft.FluentUI.AspNetCore.Components.FluentTreeView 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FluentValidationSummary 100% 100%
Microsoft.FluentUI.AspNetCore.Components.FreeOptionOutput 100%
Microsoft.FluentUI.AspNetCore.Components.GridItemsProviderRequest`1 100% 100%
Microsoft.FluentUI.AspNetCore.Components.GridItemsProviderResult 100%
Microsoft.FluentUI.AspNetCore.Components.GridItemsProviderResult`1 100%
Microsoft.FluentUI.AspNetCore.Components.GridSort`1 100% 100%
Microsoft.FluentUI.AspNetCore.Components.HierarchicalGridItem`2 100% 100%
Microsoft.FluentUI.AspNetCore.Components.HierarchicalGridUtilities 100% 100%
Microsoft.FluentUI.AspNetCore.Components.HierarchicalSelectColumn`1 98.8% 95.5%
Microsoft.FluentUI.AspNetCore.Components.HighlighterSplitter 100% 100%
Microsoft.FluentUI.AspNetCore.Components.Icon 100% 95%
Microsoft.FluentUI.AspNetCore.Components.IconFromImage 100%
Microsoft.FluentUI.AspNetCore.Components.IconInfo 100%
Microsoft.FluentUI.AspNetCore.Components.IconsExtensions 100% 50%
Microsoft.FluentUI.AspNetCore.Components.IFluentComponentChangeAfterKeyPres
s
100% 100%
Microsoft.FluentUI.AspNetCore.Components.IFluentLocalizer 100% 100%
Microsoft.FluentUI.AspNetCore.Components.Infrastructure.EventCallbackSubscr
ibable`1
100% 100%
Microsoft.FluentUI.AspNetCore.Components.Infrastructure.EventCallbackSubscr
iber`1
100% 87.5%
Microsoft.FluentUI.AspNetCore.Components.InputFileInstance 100% 100%
Microsoft.FluentUI.AspNetCore.Components.InputFileOptions 100%
Microsoft.FluentUI.AspNetCore.Components.InternalAppBarContext 100% 100%
Microsoft.FluentUI.AspNetCore.Components.InternalListContext`1 100%
Microsoft.FluentUI.AspNetCore.Components.KeyCodeService 100% 85.7%
Microsoft.FluentUI.AspNetCore.Components.KeyPress 100%
Microsoft.FluentUI.AspNetCore.Components.LayoutHamburgerEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.LibraryTooltipOptions 100%
Microsoft.FluentUI.AspNetCore.Components.Localization.LanguageResource 100% 100%
Microsoft.FluentUI.AspNetCore.Components.MarkupSanitizedOptions 100%
Microsoft.FluentUI.AspNetCore.Components.MenuItemEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.MessageBoxOptions 100%
Microsoft.FluentUI.AspNetCore.Components.Migration.AppearanceExtensions 100% 100%
Microsoft.FluentUI.AspNetCore.Components.Migration.FluentInputAppearanceExt
ensions
100% 100%
Microsoft.FluentUI.AspNetCore.Components.Migration.TooltipPositionExtension 100% 100%
Microsoft.FluentUI.AspNetCore.Components.PaginationState 100% 81.2%
Microsoft.FluentUI.AspNetCore.Components.ProgressFileDetails 100%
Microsoft.FluentUI.AspNetCore.Components.PropertyColumn`2 100% 81.8%
Microsoft.FluentUI.AspNetCore.Components.RadioEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.RangeOfDates 96.5% 86.1%
Microsoft.FluentUI.AspNetCore.Components.SelectAllTemplateArgs 100%
Microsoft.FluentUI.AspNetCore.Components.SelectColumn`1 92.8% 88.9%
Microsoft.FluentUI.AspNetCore.Components.ServiceProviderExtensions 100%
Microsoft.FluentUI.AspNetCore.Components.SortedProperty 100%
Microsoft.FluentUI.AspNetCore.Components.SpacingExtensions 100% 97.2%
Microsoft.FluentUI.AspNetCore.Components.TabChangeEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.TemplateColumn`1 100% 25%
Microsoft.FluentUI.AspNetCore.Components.Theme 100% 80%
Microsoft.FluentUI.AspNetCore.Components.ThemeExtensions 94.7% 87.5%
Microsoft.FluentUI.AspNetCore.Components.ThemeService 100% 88.8%
Microsoft.FluentUI.AspNetCore.Components.ThemeSettings 100%
Microsoft.FluentUI.AspNetCore.Components.ToastEventArgs 100% 92.8%
Microsoft.FluentUI.AspNetCore.Components.ToastInstance 100% 100%
Microsoft.FluentUI.AspNetCore.Components.ToastOptions 100%
Microsoft.FluentUI.AspNetCore.Components.ToastResult 100% 100%
Microsoft.FluentUI.AspNetCore.Components.ToastResult`1 100%
Microsoft.FluentUI.AspNetCore.Components.ToastService 96.5% 67.8%
Microsoft.FluentUI.AspNetCore.Components.TooltipEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.TotalItemCountChangedEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.TreeItemChangedEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.TreeViewItem 100% 100%
Microsoft.FluentUI.AspNetCore.Components.TreeViewItemExpandedEventArgs 100%
Microsoft.FluentUI.AspNetCore.Components.UploadedFileDetails 100%
Microsoft.FluentUI.AspNetCore.Components.Utilities.AddTag 100% 100%
Microsoft.FluentUI.AspNetCore.Components.Utilities.CssBuilder 100% 100%
Microsoft.FluentUI.AspNetCore.Components.Utilities.Debounce 100% 100%
Microsoft.FluentUI.AspNetCore.Components.Utilities.Identifier 100% 100%
Microsoft.FluentUI.AspNetCore.Components.Utilities.IdentifierContext 100% 75%
Microsoft.FluentUI.AspNetCore.Components.Utilities.InlineStyleBuilder 100% 91.6%
Microsoft.FluentUI.AspNetCore.Components.Utilities.MarkupStringSanitized 100% 92.5%
Microsoft.FluentUI.AspNetCore.Components.Utilities.RangeOf`1 96.7% 97.2%
Microsoft.FluentUI.AspNetCore.Components.Utilities.StyleBuilder 100% 100%
Microsoft.FluentUI.AspNetCore.Components.ZIndex 100%

@vnbaaij vnbaaij changed the title feat(DataGrid): add pinned (sticky/frozen) column support [dev-v5][DataGrid] Add pinned (sticky/frozen) column support Apr 3, 2026
@vnbaaij vnbaaij marked this pull request as ready for review April 3, 2026 14:32
@vnbaaij vnbaaij requested a review from dvoituron as a code owner April 3, 2026 14:32
Copilot AI review requested due to automatic review settings April 3, 2026 14:32
@vnbaaij vnbaaij enabled auto-merge (squash) April 3, 2026 14:33
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds pinned (sticky/frozen) column support to FluentDataGrid, enabling columns to remain visible while horizontally scrolling, with accompanying validation, styling, JS offset updates, docs, and tests.

Changes:

  • Introduces DataGridColumnPin + a new Pin parameter on columns, with grid-side validation and computed sticky offsets.
  • Updates rendering/CSS/JS to apply position: sticky and keep left/right offsets correct during resizing.
  • Adds demo documentation/examples and expands test coverage/snapshots for pinned columns and updated header z-index behavior.

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_SingleStickySelect_Rendering.verified.razor.html Updates snapshot output (removes inline header z-index styling).
tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_SingleSelect_Rendering.verified.razor.html Updates snapshot output (removes inline header z-index styling).
tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_MultiSelect_Rendering.verified.razor.html Updates snapshot output (removes inline header z-index styling).
tests/Core/Components/DataGrid/SelectColumnTests.SelectColumnTests_MultiSelect_Customized_Rendering.verified.razor.html Updates snapshot output (removes inline header z-index styling).
tests/Core/Components/DataGrid/SelectColumnTests.FluentDataGrid_ColumSelect_SingleStickySelect_Rendering.verified.razor.html Updates snapshot output (removes inline header z-index styling).
tests/Core/Components/DataGrid/SelectColumnTests.FluentDataGrid_ColumSelect_SingleSelect_Rendering.verified.razor.html Updates snapshot output (removes inline header z-index styling).
tests/Core/Components/DataGrid/SelectColumnTests.FluentDataGrid_ColumSelect_MultiSelect_Rendering.verified.razor.html Updates snapshot output (removes inline header z-index styling).
tests/Core/Components/DataGrid/SelectColumnTests.FluentDataGrid_ColumSelect_MultiSelect_Customized_Rendering.verified.razor.html Updates snapshot output (removes inline header z-index styling).
tests/Core/Components/DataGrid/HierarchicalSelectColumnTests.HierarchicalSelectColumnTests_Rendering.verified.razor.html Updates snapshot output (removes inline header z-index styling).
tests/Core/Components/DataGrid/FluentDataGridTests.razor Adds assertions ensuring header z-index moved from th to the button/menu layer.
tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_StripedRows.verified.razor.html Updates snapshot output (removes inline header z-index styling).
tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_StickyHeader.verified.razor.html Updates snapshot output (removes inline header z-index styling).
tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_IsFixed.verified.razor.html Updates snapshot output (removes inline header z-index styling).
tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_HeaderCellAsButtonWithMenu.verified.razor.html Updates snapshot output (adds z-index layering on button/menu).
tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_Default.verified.razor.html Updates snapshot output (removes inline header z-index styling).
tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnSortUISettings.verified.razor.html Updates snapshot output (removes inline header z-index styling).
tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnResizeUISettings.verified.razor.html Updates snapshot output (removes inline header z-index styling).
tests/Core/Components/DataGrid/FluentDataGridTests.FluentDataGrid_ColumnOptionsUISettings.verified.razor.html Updates snapshot output (removes inline header z-index styling).
tests/Core/Components/DataGrid/FluentDataGridPinnedColumnTests.razor Adds new unit/snapshot tests for pinned columns, offsets, and validation rules.
tests/Core/Components/DataGrid/FluentDataGridPinnedColumnTests.FluentDataGrid_PinnedColumn_Snapshot.verified.razor.html Adds snapshot for pinned column rendering (classes + sticky offsets).
tests/Core/Components/DataGrid/FluentDataGridCellTests.FluentDataGridCell_DisableCellFocus.verified.razor.html Updates snapshot output (removes inline header z-index styling).
src/Core/Enums/DataGridColumnPin.cs Adds enum defining None/Left/Right pin states.
src/Core/Components/DataGrid/FluentDataGridCell.razor.cs Applies pinned classes + sticky positioning/offset styles for pinned columns; adjusts min-width behavior and z-index usage.
src/Core/Components/DataGrid/FluentDataGrid.razor.ts Adds pinned offset recalculation and improves min-width handling during resize; introduces UpdatePinnedColumnOffsets.
src/Core/Components/DataGrid/FluentDataGrid.razor.css Adds pinned column styling (backgrounds, separators, stacking rules) and introduces --fluent-data-grid-pinned-background.
src/Core/Components/DataGrid/FluentDataGrid.razor.cs Validates pinned configuration and computes initial pin offsets from explicit pixel widths.
src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs Adds Pin parameter and internal computed PinOffsetPx for sticky offsets.
src/Core/Components/DataGrid/Columns/ColumnBase.razor Adjusts header menu/button layering to use explicit z-index style on interactive elements.
src/Core.Scripts/.gitignore Adds obj/ ignore for Core.Scripts.
examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridPinnedColumnsPage.md Adds new documentation page for pinned columns.
examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md Links the new pinned columns doc page and minor formatting tweaks.
examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridPinnedColumns.razor Adds a demo example showcasing pinned columns.
Comments suppressed due to low confidence (2)

src/Core/Components/DataGrid/FluentDataGrid.razor.ts:476

  • Pinned offsets are kept in sync during drag resize, but ResizeColumnDiscrete (keyboard/menu resize) doesn’t call UpdatePinnedColumnOffsets. Resizing a pinned (or adjacent) column via this path can leave pinned columns misaligned until a full re-init. Consider calling UpdatePinnedColumnOffsets after applying the new size / gridTemplateColumns here too.
      if (column.header === headerBeingResized) {
        const width = headerBeingResized!.offsetWidth + change;
        //const width = headerBeingResized!.getBoundingClientRect().width + change;

        if (change < 0) {
          column.size = Math.max(getMinWidthPx(column.header), width) + 'px';
        }
        else {
          column.size = width + 'px';
        }
        column.header.style.width = column.size;
      }

      if (isGrid) {
        // for grid we need to recalculate all columns that are minmax
        if (column.size.startsWith('minmax')) {
          column.size = column.header.offsetWidth + 'px';
        }
        columns.push(column.size);
      }
    });

    if (isGrid) {
      gridElement.style.gridTemplateColumns = columns.join(' ');
    }
  }

src/Core/Components/DataGrid/FluentDataGrid.razor.ts:508

  • ResizeColumnExact updates column widths but doesn’t call UpdatePinnedColumnOffsets, so pinned columns may keep stale left/right offsets after an exact resize. Consider invoking UpdatePinnedColumnOffsets before dispatching closecolumnresize (and after updating gridTemplateColumns in Grid mode).
    grids.find(grid => grid.id === gridElement.id)!.columns.forEach((column: any) => {
      if (column.header === headerBeingResized) {
        column.size = Math.max(getMinWidthPx(column.header), width) + 'px';
        column.header.style.width = column.size;
      }

      if (isGrid) {
        // for grid we need to recalculate all columns that are minmax
        if (column.size.startsWith('minmax')) {
          column.size = column.header.offsetWidth + 'px';
        }
        columns.push(column.size);
      }
    });

    if (isGrid) {
      gridElement.style.gridTemplateColumns = columns.join(' ');
    }

    gridElement.dispatchEvent(new CustomEvent('closecolumnresize', { bubbles: true }));
    gridElement.focus();
  }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +666 to +683
private void ValidatePinnedColumnConstraints()
{
// Width must be an explicit pixel value.
foreach (var col in _columns.Where(c => c.Pin != DataGridColumnPin.None))
{
if (string.IsNullOrWhiteSpace(col.Width))
{
throw new ArgumentException(
$"Column '{col.Title ?? col.Index.ToString(CultureInfo.InvariantCulture)}' has Pin set but no Width. " +
"Pinned columns require an explicit Width in pixels (e.g., '150px').");
}

if (!col.Width!.Trim().EndsWith("px", StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException(
$"Column '{col.Title ?? col.Index.ToString(CultureInfo.InvariantCulture)}' has Pin set but Width '{col.Width}' is not in pixels. " +
"Pinned columns require an explicit Width in pixels (e.g., '150px').");
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pinned-column width validation only checks that Width ends with "px", but does not verify the numeric part parses. Values like "abcpx" would pass validation, then ParsePixelWidth returns 0 and multiple pinned columns can overlap at offset 0. Consider validating that the value parses to a positive pixel number (and throw a descriptive ArgumentException when parsing fails).

Suggested change
private void ValidatePinnedColumnConstraints()
{
// Width must be an explicit pixel value.
foreach (var col in _columns.Where(c => c.Pin != DataGridColumnPin.None))
{
if (string.IsNullOrWhiteSpace(col.Width))
{
throw new ArgumentException(
$"Column '{col.Title ?? col.Index.ToString(CultureInfo.InvariantCulture)}' has Pin set but no Width. " +
"Pinned columns require an explicit Width in pixels (e.g., '150px').");
}
if (!col.Width!.Trim().EndsWith("px", StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException(
$"Column '{col.Title ?? col.Index.ToString(CultureInfo.InvariantCulture)}' has Pin set but Width '{col.Width}' is not in pixels. " +
"Pinned columns require an explicit Width in pixels (e.g., '150px').");
}
private static string GetColumnIdentifier(ColumnBase<object> column)
=> column.Title ?? column.Index.ToString(CultureInfo.InvariantCulture);
private void ValidatePinnedColumnConstraints()
{
// Width must be an explicit positive pixel value.
foreach (var col in _columns.Where(c => c.Pin != DataGridColumnPin.None))
{
if (string.IsNullOrWhiteSpace(col.Width))
{
throw new ArgumentException(
$"Column '{GetColumnIdentifier(col)}' has Pin set but no Width. " +
"Pinned columns require an explicit positive Width in pixels (e.g., '150px').");
}
var trimmedWidth = col.Width!.Trim();
if (!trimmedWidth.EndsWith("px", StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException(
$"Column '{GetColumnIdentifier(col)}' has Pin set but Width '{col.Width}' is not in pixels. " +
"Pinned columns require an explicit positive Width in pixels (e.g., '150px').");
}
var numericPortion = trimmedWidth[..^2].Trim();
if (!double.TryParse(numericPortion, NumberStyles.Float, CultureInfo.InvariantCulture, out var pixelWidth) || pixelWidth <= 0)
{
throw new ArgumentException(
$"Column '{GetColumnIdentifier(col)}' has Pin set but Width '{col.Width}' is not a valid positive pixel value. " +
"Pinned columns require an explicit positive Width in pixels (e.g., '150px').");
}

Copilot uses AI. Check for mistakes.
Comment on lines +113 to +119
/* Visual separator on the trailing edge of the last left-pinned column */
.fluent-data-grid td:nth-last-child(1 of .col-pinned-left) {
border-inline-end: var(--strokeWidthThin) solid var(--colorNeutralStroke1);
}

/* Visual separator on the leading edge of the first right-pinned column */
.fluent-data-grid td:nth-child(1 of .col-pinned-right) {
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These separator selectors use the Selectors Level 4 :nth-child(... of S) syntax, which is not supported in some browsers (notably older Safari versions). If this library targets those browsers, consider replacing with a more compatible approach (e.g., add a specific class to the last left-pinned / first right-pinned column and style that class) so separators render consistently.

Suggested change
/* Visual separator on the trailing edge of the last left-pinned column */
.fluent-data-grid td:nth-last-child(1 of .col-pinned-left) {
border-inline-end: var(--strokeWidthThin) solid var(--colorNeutralStroke1);
}
/* Visual separator on the leading edge of the first right-pinned column */
.fluent-data-grid td:nth-child(1 of .col-pinned-right) {
/* Visual separator at the boundary after the left-pinned columns.
Use adjacent-sibling selectors instead of Selectors Level 4 `:nth-child(... of S)`
for broader browser compatibility. */
.fluent-data-grid td.col-pinned-left + td:not(.col-pinned-left) {
border-inline-start: var(--strokeWidthThin) solid var(--colorNeutralStroke1);
}
/* Fallback for rows where the final cell is left-pinned. */
.fluent-data-grid td.col-pinned-left:last-child {
border-inline-end: var(--strokeWidthThin) solid var(--colorNeutralStroke1);
}
/* Visual separator at the boundary before the right-pinned columns. */
.fluent-data-grid td:not(.col-pinned-right) + td.col-pinned-right,
.fluent-data-grid td:first-child.col-pinned-right {

Copilot uses AI. Check for mistakes.
/// <see cref="FluentDataGrid{TGridItem}"/> when columns are collected.
/// Not intended for direct use by consumers.
/// </summary>
internal double PinOffsetPx { get; set; }
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why a number ? This is probably better to have a string like 10px but also for other CSS values.

/// The column is pinned to the left edge of the grid.
/// The column will remain visible when the user scrolls right.
/// </summary>
Left,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to have Left or Start: what will be the result in RTL direction?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants