From 274b393efbb56d83eb5cb6406fb8a1a42e8feb2f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 09:38:01 +0000 Subject: [PATCH 01/10] feat: rename DataGridColumnPin Left/Right to Start/End and PinOffsetPx to PinOffset (string) with logical CSS properties Agent-Logs-Url: https://github.com/microsoft/fluentui-blazor/sessions/705a5b7c-1768-42a0-ba5f-faa99daf78d4 Co-authored-by: vnbaaij <1761079+vnbaaij@users.noreply.github.com> --- .../Examples/DataGridPinnedColumns.razor | 6 +- .../Pages/DataGridPinnedColumnsPage.md | 41 +++--- .../obj\\Debug/\\package.g.props" | 29 ++++ .../DataGrid/Columns/ColumnBase.razor.cs | 10 +- .../DataGrid/FluentDataGrid.razor.cs | 46 +++---- .../DataGrid/FluentDataGrid.razor.css | 24 ++-- .../DataGrid/FluentDataGrid.razor.ts | 34 ++--- .../DataGrid/FluentDataGridCell.razor.cs | 8 +- src/Core/Enums/DataGridColumnPin.cs | 12 +- ..._PinnedColumn_Snapshot.verified.razor.html | 24 ++-- .../FluentDataGridPinnedColumnTests.razor | 126 +++++++++--------- 11 files changed, 193 insertions(+), 167 deletions(-) create mode 100644 "src/Core.Scripts/obj\\Debug/\\package.g.props" diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridPinnedColumns.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridPinnedColumns.razor index 4ea452ab5b..5c607812a1 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridPinnedColumns.razor +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridPinnedColumns.razor @@ -4,13 +4,13 @@ StripedRows="true" ResizableColumns="true" DisplayMode="DataGridDisplayMode.Grid"> - - + + - + diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridPinnedColumnsPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridPinnedColumnsPage.md index 4c41db5309..49f9e7c9cc 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridPinnedColumnsPage.md +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridPinnedColumnsPage.md @@ -1,13 +1,9 @@ ---- -title: Pinned columns -order: 0095 -route: /DataGrid/PinnedColumns ---- - # Pinned columns -Columns can be pinned (frozen) to the left or right edge of the grid so that they remain visible -while the user scrolls horizontally through wider datasets. +Columns can be pinned (frozen) to the inline-start or inline-end edge of the grid so that they remain visible +while the user scrolls horizontally through wider datasets. Using logical-property directions (`Start`/`End`) +instead of physical ones (`Left`/`Right`) means pinned columns automatically work correctly in both +LTR and RTL layouts. ## Parameters @@ -16,18 +12,18 @@ Set the `Pin` parameter on any `PropertyColumn` or `TemplateColumn`: | Value | Behavior | |---|---| | `DataGridColumnPin.None` | Default — column scrolls normally | -| `DataGridColumnPin.Left` | Column stays anchored to the left edge | -| `DataGridColumnPin.Right` | Column stays anchored to the right edge | +| `DataGridColumnPin.Start` | Column stays anchored to the inline-start edge (left in LTR, right in RTL) | +| `DataGridColumnPin.End` | Column stays anchored to the inline-end edge (right in LTR, left in RTL) | ## Rules * **Explicit pixel width required.** Every pinned column must declare a `Width` in pixels (e.g. `Width="150px"`). Relative units (`fr`, `%`) are not supported because the browser cannot determine a fixed sticky offset from them at render time. -* **Left-pinned columns must be contiguous at the start.** Each left-pinned column must - immediately follow another left-pinned column, or be the very first column. -* **Right-pinned columns must be contiguous at the end.** Each right-pinned column must - immediately precede another right-pinned column, or be the very last column. +* **Start-pinned columns must be contiguous at the start.** Each start-pinned column must + immediately follow another start-pinned column, or be the very first column. +* **End-pinned columns must be contiguous at the end.** Each end-pinned column must + immediately precede another end-pinned column, or be the very last column. * Violating any of these rules throws an `ArgumentException` with a descriptive message. ## Scrollable container @@ -39,10 +35,10 @@ bar appears when columns overflow the container: ```razor
- - + + - + ... @@ -64,16 +60,17 @@ property `--fluent-data-grid-pinned-background`: ## Notes * Column resizing interacts correctly with sticky offsets — the JavaScript in - `FluentDataGrid.razor.ts` recalculates `left` / `right` values after every resize step via - `UpdatePinnedColumnOffsets`. + `FluentDataGrid.razor.ts` recalculates `inset-inline-start` / `inset-inline-end` values after + every resize step via `UpdatePinnedColumnOffsets`. * Virtualization and paging are fully compatible because each rendered row's cells carry the same `position: sticky` styling regardless of which page or scroll position is active. -* In RTL layouts the browser interprets `left` / `right` according to the document direction, so - pinned columns behave correctly without additional configuration. +* RTL layouts are fully supported: the CSS logical properties `inset-inline-start` and + `inset-inline-end` automatically map to the correct physical direction based on the document's + writing mode. ## Example -Demonstrates pinned (frozen) columns using `Pin="DataGridColumnPin.Left"` and `Pin="DataGridColumnPin.Right"`. +Demonstrates pinned (frozen) columns using `Pin="DataGridColumnPin.Start"` and `Pin="DataGridColumnPin.End"`. The two leftmost columns and the Actions column remain visible while the rest scroll horizontally. Wrap the grid in a `
` container and give the grid a `Style="min-width: max-content;"` diff --git "a/src/Core.Scripts/obj\\Debug/\\package.g.props" "b/src/Core.Scripts/obj\\Debug/\\package.g.props" new file mode 100644 index 0000000000..4b7b3b41a5 --- /dev/null +++ "b/src/Core.Scripts/obj\\Debug/\\package.g.props" @@ -0,0 +1,29 @@ + + + + true + microsoft.fluentui.aspnetcore.components.assets + src/index.ts + dist/Microsoft.FluentUI.AspNetCore.Components.lib.module.js + ../Core/Components/**/*.css + dist/Microsoft.FluentUI.AspNetCore.Components.bundle.scp.css + node ./esbuild.config.mjs + rimraf ./dist + [] + + ISC + module + ^1.15.9 + 8.57.1 + 8.57.1 + 0.27.4 + 0.0.1 + 10.0.3 + ^13.0.6 + ^7.6.1 + 6.1.3 + 6.4.0 + 5.9.3 + ^3.0.0-rc.9 + + \ No newline at end of file diff --git a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs index d5e53fa542..4f9f1ad86f 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs @@ -188,21 +188,21 @@ public abstract partial class ColumnBase public string? Width { get; set; } /// - /// Gets or sets whether this column is pinned (frozen) to the left or right edge of the grid, + /// Gets or sets whether this column is pinned (frozen) to the inline-start or inline-end edge of the grid, /// so it remains visible when the user scrolls horizontally. /// Pinned columns require an explicit in pixels (e.g., "150px"). - /// Left-pinned columns must be contiguous at the start of the column list; - /// right-pinned columns must be contiguous at the end. + /// Start-pinned columns must be contiguous at the start of the column list; + /// end-pinned columns must be contiguous at the end. /// [Parameter] public DataGridColumnPin Pin { get; set; } = DataGridColumnPin.None; /// - /// The sticky left or right CSS offset (in pixels) computed by + /// The sticky inset-inline-start or inset-inline-end CSS offset computed by /// when columns are collected. /// Not intended for direct use by consumers. /// - internal double PinOffsetPx { get; set; } + internal string PinOffset { get; set; } = "0px"; /// /// Gets or sets the minimal width of the column. diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index e1fa32b112..f5cac8f869 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -624,12 +624,12 @@ private void FinishCollectingColumns() } /// - /// Validates the pinned-column configuration and computes the sticky pixel offsets for each + /// Validates the pinned-column configuration and computes the sticky offsets for each /// pinned column. Rules enforced: /// /// Pinned columns must specify an explicit pixel Width (e.g., "150px"). - /// Left-pinned columns must be contiguous at the beginning of the column list. - /// Right-pinned columns must be contiguous at the end of the column list. + /// Start-pinned columns must be contiguous at the beginning of the column list. + /// End-pinned columns must be contiguous at the end of the column list. /// /// private void ValidateAndComputePinnedColumns() @@ -642,20 +642,20 @@ private void ValidateAndComputePinnedColumns() ValidatePinnedColumnConstraints(); - // Compute left-pin sticky offsets (cumulative left-to-right). - var leftOffset = 0.0; - foreach (var col in _columns.Where(c => c.Pin == DataGridColumnPin.Left)) + // Compute start-pin sticky offsets (cumulative inline-start to inline-end). + var startOffset = 0.0; + foreach (var col in _columns.Where(c => c.Pin == DataGridColumnPin.Start)) { - col.PinOffsetPx = leftOffset; - leftOffset += ParsePixelWidth(col.Width); + col.PinOffset = $"{startOffset.ToString(CultureInfo.InvariantCulture)}px"; + startOffset += ParsePixelWidth(col.Width); } - // Compute right-pin sticky offsets (cumulative right-to-left). - var rightOffset = 0.0; - foreach (var col in _columns.Where(c => c.Pin == DataGridColumnPin.Right).Reverse()) + // Compute end-pin sticky offsets (cumulative inline-end to inline-start). + var endOffset = 0.0; + foreach (var col in _columns.Where(c => c.Pin == DataGridColumnPin.End).Reverse()) { - col.PinOffsetPx = rightOffset; - rightOffset += ParsePixelWidth(col.Width); + col.PinOffset = $"{endOffset.ToString(CultureInfo.InvariantCulture)}px"; + endOffset += ParsePixelWidth(col.Width); } } @@ -683,27 +683,27 @@ private void ValidatePinnedColumnConstraints() } } - // Left-pinned columns must be contiguous at the start: each one must be preceded by - // another left-pinned column (or be the very first column). + // Start-pinned columns must be contiguous at the start: each one must be preceded by + // another start-pinned column (or be the very first column). for (var i = 0; i < _columns.Count; i++) { - if (_columns[i].Pin == DataGridColumnPin.Left && i > 0 && _columns[i - 1].Pin != DataGridColumnPin.Left) + if (_columns[i].Pin == DataGridColumnPin.Start && i > 0 && _columns[i - 1].Pin != DataGridColumnPin.Start) { throw new ArgumentException( - $"Column '{_columns[i].Title ?? _columns[i].Index.ToString(CultureInfo.InvariantCulture)}' is left-pinned but the preceding column is not. " + - "Left-pinned columns must be contiguous at the start of the column list."); + $"Column '{_columns[i].Title ?? _columns[i].Index.ToString(CultureInfo.InvariantCulture)}' is start-pinned but the preceding column is not. " + + "Start-pinned columns must be contiguous at the start of the column list."); } } - // Right-pinned columns must be contiguous at the end: each one must be followed by - // another right-pinned column (or be the very last column). + // End-pinned columns must be contiguous at the end: each one must be followed by + // another end-pinned column (or be the very last column). for (var i = 0; i < _columns.Count; i++) { - if (_columns[i].Pin == DataGridColumnPin.Right && i < _columns.Count - 1 && _columns[i + 1].Pin != DataGridColumnPin.Right) + if (_columns[i].Pin == DataGridColumnPin.End && i < _columns.Count - 1 && _columns[i + 1].Pin != DataGridColumnPin.End) { throw new ArgumentException( - $"Column '{_columns[i].Title ?? _columns[i].Index.ToString(CultureInfo.InvariantCulture)}' is right-pinned but the following column is not. " + - "Right-pinned columns must be contiguous at the end of the column list."); + $"Column '{_columns[i].Title ?? _columns[i].Index.ToString(CultureInfo.InvariantCulture)}' is end-pinned but the following column is not. " + + "End-pinned columns must be contiguous at the end of the column list."); } } } diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.css b/src/Core/Components/DataGrid/FluentDataGrid.razor.css index c86445a2bf..d03de008ea 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.css +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.css @@ -100,23 +100,23 @@ /* ---- Pinned (sticky) columns ---- */ /* Background: keeps content from showing through when scrolling. Override --fluent-data-grid-pinned-background to theme. */ -.fluent-data-grid th.col-pinned-left, -.fluent-data-grid th.col-pinned-right { +.fluent-data-grid th.col-pinned-start, +.fluent-data-grid th.col-pinned-end { background-color: var(--colorNeutralBackground1); } -.fluent-data-grid td.col-pinned-left, -.fluent-data-grid td.col-pinned-right { +.fluent-data-grid td.col-pinned-start, +.fluent-data-grid td.col-pinned-end { background-color: var(--fluent-data-grid-pinned-background); } -/* Visual separator on the trailing edge of the last left-pinned column */ -.fluent-data-grid td:nth-last-child(1 of .col-pinned-left) { +/* Visual separator on the trailing edge of the last start-pinned column */ +.fluent-data-grid td:nth-last-child(1 of .col-pinned-start) { 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 on the leading edge of the first end-pinned column */ +.fluent-data-grid td:nth-child(1 of .col-pinned-end) { border-inline-start: var(--strokeWidthThin) solid var(--colorNeutralStroke1); } @@ -124,13 +124,13 @@ For UseMenuService=false the inline z-index: 5 is absent, so we provide a floor here. The sticky-header row needs the highest value to beat both the sticky-header row z-index (2) and the pinned data cell z-index (1). */ -.fluent-data-grid th.col-pinned-left, -.fluent-data-grid th.col-pinned-right { +.fluent-data-grid th.col-pinned-start, +.fluent-data-grid th.col-pinned-end { z-index: 2; } -.fluent-data-grid tr[row-type='sticky-header'] > th.col-pinned-left, -.fluent-data-grid tr[row-type='sticky-header'] > th.col-pinned-right { +.fluent-data-grid tr[row-type='sticky-header'] > th.col-pinned-start, +.fluent-data-grid tr[row-type='sticky-header'] > th.col-pinned-end { background-color: var(--colorNeutralBackground4); z-index: 4; } diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.ts b/src/Core/Components/DataGrid/FluentDataGrid.razor.ts index 2cb37b0ca9..395f90313f 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.ts +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.ts @@ -564,10 +564,10 @@ export namespace Microsoft.FluentUI.Blazor.DataGrid { * pinned column so that columns stack correctly against the grid edge after the initial * render or after a column is resized. * - * Left-pinned columns are processed left-to-right; each column's offset is the sum of - * the widths of all left-pinned columns to its left. - * Right-pinned columns are processed right-to-left; each column's offset is the sum of - * the widths of all right-pinned columns to its right. + * Start-pinned columns are processed in DOM order; each column's offset is the sum of + * the widths of all start-pinned columns before it. + * End-pinned columns are processed in reverse DOM order; each column's offset is the sum of + * the widths of all end-pinned columns after it. * * The function reads the actual rendered header-cell width so it handles both Grid mode * (CSS grid layout) and Table mode (standard table layout). Grid mode uses `offsetWidth` @@ -592,7 +592,7 @@ export namespace Microsoft.FluentUI.Blazor.DataGrid { * Applies a cumulative sticky offset to all cells in a column and returns the new * running total to be used by the next column in the sequence. */ - function applyOffset(header: HTMLElement, offset: number, side: 'left' | 'right'): number { + function applyOffset(header: HTMLElement, offset: number, side: 'insetInlineStart' | 'insetInlineEnd'): number { const colIndex = header.getAttribute('col-index'); if (!colIndex) { return offset; } @@ -602,24 +602,24 @@ export namespace Microsoft.FluentUI.Blazor.DataGrid { return offset + headerWidth(header); } - // Left-pinned columns: process in DOM (left-to-right) order. - const leftPinnedHeaders = Array.from( - gridElement.querySelectorAll('th.col-pinned-left') + // Start-pinned columns: process in DOM order. + const startPinnedHeaders = Array.from( + gridElement.querySelectorAll('th.col-pinned-start') ) as HTMLElement[]; - let leftOffset = 0; - for (const header of leftPinnedHeaders) { - leftOffset = applyOffset(header, leftOffset, 'left'); + let startOffset = 0; + for (const header of startPinnedHeaders) { + startOffset = applyOffset(header, startOffset, 'insetInlineStart'); } - // Right-pinned columns: process in reverse DOM (right-to-left) order. - const rightPinnedHeaders = Array.from( - gridElement.querySelectorAll('th.col-pinned-right') + // End-pinned columns: process in reverse DOM order. + const endPinnedHeaders = Array.from( + gridElement.querySelectorAll('th.col-pinned-end') ) as HTMLElement[]; - let rightOffset = 0; - for (let i = rightPinnedHeaders.length - 1; i >= 0; i--) { - rightOffset = applyOffset(rightPinnedHeaders[i], rightOffset, 'right'); + let endOffset = 0; + for (let i = endPinnedHeaders.length - 1; i >= 0; i--) { + endOffset = applyOffset(endPinnedHeaders[i], endOffset, 'insetInlineEnd'); } } } diff --git a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs index 22b1173456..14dc98280a 100644 --- a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs @@ -31,8 +31,8 @@ public FluentDataGridCell(LibraryConfiguration configuration) : base(configurati .AddClass("column-header", when: CellType == DataGridCellType.ColumnHeader) .AddClass("select-all", when: CellType == DataGridCellType.ColumnHeader && Column is SelectColumn) .AddClass("multiline-text", when: Grid.MultiLine && (Grid.Items is not null || Grid.ItemsProvider is not null) && CellType != DataGridCellType.ColumnHeader) - .AddClass("col-pinned-left", Column?.Pin == DataGridColumnPin.Left) - .AddClass("col-pinned-right", Column?.Pin == DataGridColumnPin.Right) + .AddClass("col-pinned-start", Column?.Pin == DataGridColumnPin.Start) + .AddClass("col-pinned-end", Column?.Pin == DataGridColumnPin.End) .AddClass(Owner.Class) .Build(); @@ -50,8 +50,8 @@ public FluentDataGridCell(LibraryConfiguration configuration) : base(configurati .AddStyle("height", "100%", Grid.MultiLine) .AddStyle("min-height", "40px", Owner.RowType != DataGridRowType.Default) .AddStyle("position", "sticky", Column != null && Column.Pin != DataGridColumnPin.None) - .AddStyle("left", $"{(Column?.PinOffsetPx ?? 0).ToString(CultureInfo.InvariantCulture)}px", Column?.Pin == DataGridColumnPin.Left) - .AddStyle("right", $"{(Column?.PinOffsetPx ?? 0).ToString(CultureInfo.InvariantCulture)}px", Column?.Pin == DataGridColumnPin.Right) + .AddStyle("inset-inline-start", Column?.PinOffset ?? "0px", Column?.Pin == DataGridColumnPin.Start) + .AddStyle("inset-inline-end", Column?.PinOffset ?? "0px", Column?.Pin == DataGridColumnPin.End) .AddStyle("z-index", "1", Column != null && Column.Pin != DataGridColumnPin.None && CellType == DataGridCellType.Default) .AddStyle(Owner.Style) .Build(); diff --git a/src/Core/Enums/DataGridColumnPin.cs b/src/Core/Enums/DataGridColumnPin.cs index 4aa90cf098..7862eb9779 100644 --- a/src/Core/Enums/DataGridColumnPin.cs +++ b/src/Core/Enums/DataGridColumnPin.cs @@ -16,14 +16,14 @@ public enum DataGridColumnPin None, /// - /// The column is pinned to the left edge of the grid. - /// The column will remain visible when the user scrolls right. + /// The column is pinned to the inline-start edge of the grid (left in LTR, right in RTL). + /// The column will remain visible when the user scrolls toward the inline-end. /// - Left, + Start, /// - /// The column is pinned to the right edge of the grid. - /// The column will remain visible when the user scrolls left. + /// The column is pinned to the inline-end edge of the grid (right in LTR, left in RTL). + /// The column will remain visible when the user scrolls toward the inline-start. /// - Right, + End, } diff --git a/tests/Core/Components/DataGrid/FluentDataGridPinnedColumnTests.FluentDataGrid_PinnedColumn_Snapshot.verified.razor.html b/tests/Core/Components/DataGrid/FluentDataGridPinnedColumnTests.FluentDataGrid_PinnedColumn_Snapshot.verified.razor.html index 90d91a2865..656413b651 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridPinnedColumnTests.FluentDataGrid_PinnedColumn_Snapshot.verified.razor.html +++ b/tests/Core/Components/DataGrid/FluentDataGridPinnedColumnTests.FluentDataGrid_PinnedColumn_Snapshot.verified.razor.html @@ -4,14 +4,14 @@ - - - - - + + - + - - + + - + - - + + - +
+
Id
+
Name
@@ -25,7 +25,7 @@
+
Action
@@ -36,22 +36,22 @@
1Denis Voituron1Denis Voituron BrusselsEditEdit
2Vincent Baaij2Vincent Baaij AmsterdamEditEdit
3Bill Gates3Bill Gates MedinaEditEdit
diff --git a/tests/Core/Components/DataGrid/FluentDataGridPinnedColumnTests.razor b/tests/Core/Components/DataGrid/FluentDataGridPinnedColumnTests.razor index 0435aeaaf5..e893f6174e 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridPinnedColumnTests.razor +++ b/tests/Core/Components/DataGrid/FluentDataGridPinnedColumnTests.razor @@ -31,43 +31,43 @@ } // ------------------------------------------------------------------------- - // Single pinned-left column: correct CSS class and sticky style + // Single pinned-start column: correct CSS class and sticky style // ------------------------------------------------------------------------- [Fact] - public void FluentDataGrid_PinnedColumn_Left_Has_Sticky_Class_And_Style() + public void FluentDataGrid_PinnedColumn_Start_Has_Sticky_Class_And_Style() { // Arrange && Act var cut = Render>( @ - + ); - // Assert — every cell in the column carries col-pinned-left and position:sticky;left:0px + // Assert — every cell in the column carries col-pinned-start and position:sticky;inset-inline-start:0px var cells = cut.FindAll("[col-index='1']"); Assert.NotEmpty(cells); Assert.All(cells, cell => { - Assert.Contains("col-pinned-left", cell.ClassName); + Assert.Contains("col-pinned-start", cell.ClassName); Assert.Contains("position: sticky", cell.GetAttribute("style") ?? ""); - Assert.Contains("left: 0px", cell.GetAttribute("style") ?? ""); + Assert.Contains("inset-inline-start: 0px", cell.GetAttribute("style") ?? ""); }); } // ------------------------------------------------------------------------- - // Single pinned-right column: correct CSS class and sticky style + // Single pinned-end column: correct CSS class and sticky style // ------------------------------------------------------------------------- [Fact] - public void FluentDataGrid_PinnedColumn_Right_Has_Sticky_Class_And_Style() + public void FluentDataGrid_PinnedColumn_End_Has_Sticky_Class_And_Style() { // Arrange && Act var cut = Render>( @ - + ); @@ -75,9 +75,9 @@ Assert.NotEmpty(cells); Assert.All(cells, cell => { - Assert.Contains("col-pinned-right", cell.ClassName); + Assert.Contains("col-pinned-end", cell.ClassName); Assert.Contains("position: sticky", cell.GetAttribute("style") ?? ""); - Assert.Contains("right: 0px", cell.GetAttribute("style") ?? ""); + Assert.Contains("inset-inline-end: 0px", cell.GetAttribute("style") ?? ""); }); } @@ -87,7 +87,7 @@ var cut = Render>( @ - + ); @@ -97,53 +97,53 @@ } // ------------------------------------------------------------------------- - // Two left-pinned columns: cumulative offsets + // Two start-pinned columns: cumulative offsets // ------------------------------------------------------------------------- [Fact] - public void FluentDataGrid_PinnedColumn_Left_Multiple_Cumulative_Offsets() + public void FluentDataGrid_PinnedColumn_Start_Multiple_Cumulative_Offsets() { - // Arrange && Act — col1: 100px, col2: 120px → col1 left:0px, col2 left:100px + // Arrange && Act — col1: 100px, col2: 120px → col1 inset-inline-start:0px, col2 inset-inline-start:100px var cut = Render>( @ - - + + ); Assert.All(cut.FindAll("[col-index='1']"), cell => - Assert.Contains("left: 0px", cell.GetAttribute("style") ?? "")); + Assert.Contains("inset-inline-start: 0px", cell.GetAttribute("style") ?? "")); Assert.All(cut.FindAll("[col-index='2']"), cell => - Assert.Contains("left: 100px", cell.GetAttribute("style") ?? "")); + Assert.Contains("inset-inline-start: 100px", cell.GetAttribute("style") ?? "")); } // ------------------------------------------------------------------------- - // Two right-pinned columns: cumulative offsets + // Two end-pinned columns: cumulative offsets // ------------------------------------------------------------------------- [Fact] - public void FluentDataGrid_PinnedColumn_Right_Multiple_Cumulative_Offsets() + public void FluentDataGrid_PinnedColumn_End_Multiple_Cumulative_Offsets() { - // col1 (normal 1fr) | col2: 80px right-pinned | col3: 60px right-pinned - // col3 (rightmost) → right:0px | col2 → right:60px + // col1 (normal 1fr) | col2: 80px end-pinned | col3: 60px end-pinned + // col3 (rightmost) → inset-inline-end:0px | col2 → inset-inline-end:60px var cut = Render>( @ - - + + ); - // col3 is index 3 — rightmost right-pinned → right: 0px + // col3 is index 3 — rightmost end-pinned → inset-inline-end: 0px Assert.All(cut.FindAll("[col-index='3']"), cell => - Assert.Contains("right: 0px", cell.GetAttribute("style") ?? "")); + Assert.Contains("inset-inline-end: 0px", cell.GetAttribute("style") ?? "")); - // col2 is index 2 — next-to-last right-pinned → right: 60px (width of col3) + // col2 is index 2 — next-to-last end-pinned → inset-inline-end: 60px (width of col3) Assert.All(cut.FindAll("[col-index='2']"), cell => - Assert.Contains("right: 60px", cell.GetAttribute("style") ?? "")); + Assert.Contains("inset-inline-end: 60px", cell.GetAttribute("style") ?? "")); } // ------------------------------------------------------------------------- @@ -158,7 +158,7 @@ Render>( @ - + ); }); @@ -176,67 +176,67 @@ Render>( @ - + ); }); } // ------------------------------------------------------------------------- - // Ordering validation: isolated left-pinned column (not at the start) throws + // Ordering validation: isolated start-pinned column (not at the start) throws // ------------------------------------------------------------------------- [Fact] - public void FluentDataGrid_PinnedColumn_Left_Not_At_Start_Throws() + public void FluentDataGrid_PinnedColumn_Start_Not_At_Start_Throws() { - // col1 is unpinned, col2 is left-pinned → invalid + // col1 is unpinned, col2 is start-pinned → invalid Assert.Throws(() => { Render>( @ - + ); }); } // ------------------------------------------------------------------------- - // Ordering validation: left-pinned columns split by a normal column throws + // Ordering validation: start-pinned columns split by a normal column throws // ------------------------------------------------------------------------- [Fact] - public void FluentDataGrid_PinnedColumn_Left_NonContiguous_Throws() + public void FluentDataGrid_PinnedColumn_Start_NonContiguous_Throws() { - // col1 left-pinned, col2 normal, col3 left-pinned → not contiguous + // col1 start-pinned, col2 normal, col3 start-pinned → not contiguous Assert.Throws(() => { Render>( @ - + - + ); }); } // ------------------------------------------------------------------------- - // Ordering validation: isolated right-pinned column (not at the end) throws + // Ordering validation: isolated end-pinned column (not at the end) throws // ------------------------------------------------------------------------- [Fact] - public void FluentDataGrid_PinnedColumn_Right_Not_At_End_Throws() + public void FluentDataGrid_PinnedColumn_End_Not_At_End_Throws() { - // col1 right-pinned, col2 normal → invalid + // col1 end-pinned, col2 normal → invalid Assert.Throws(() => { Render>( @ - + ); @@ -244,60 +244,60 @@ } // ------------------------------------------------------------------------- - // Ordering validation: right-pinned columns split by a normal column throws + // Ordering validation: end-pinned columns split by a normal column throws // ------------------------------------------------------------------------- [Fact] - public void FluentDataGrid_PinnedColumn_Right_NonContiguous_Throws() + public void FluentDataGrid_PinnedColumn_End_NonContiguous_Throws() { - // col1 normal, col2 right-pinned, col3 normal, col4 right-pinned → not contiguous + // col1 normal, col2 end-pinned, col3 normal, col4 end-pinned → not contiguous Assert.Throws(() => { Render>( @ - + - + ); }); } // ------------------------------------------------------------------------- - // Valid mixed layout: left-pinned + normal + right-pinned + // Valid mixed layout: start-pinned + normal + end-pinned // ------------------------------------------------------------------------- [Fact] - public void FluentDataGrid_PinnedColumn_LeftAndRight_Valid_Layout() + public void FluentDataGrid_PinnedColumn_StartAndEnd_Valid_Layout() { // Should not throw; verify offsets are correct var cut = Render>( @ - - + + - + ); - // col1 (Id) left-pinned → left: 0px + // col1 (Id) start-pinned → inset-inline-start: 0px Assert.All(cut.FindAll("[col-index='1']"), c => - Assert.Contains("left: 0px", c.GetAttribute("style") ?? "")); + Assert.Contains("inset-inline-start: 0px", c.GetAttribute("style") ?? "")); - // col2 (Name) left-pinned → left: 50px + // col2 (Name) start-pinned → inset-inline-start: 50px Assert.All(cut.FindAll("[col-index='2']"), c => - Assert.Contains("left: 50px", c.GetAttribute("style") ?? "")); + Assert.Contains("inset-inline-start: 50px", c.GetAttribute("style") ?? "")); // col3 (City) not pinned → no sticky style Assert.All(cut.FindAll("[col-index='3']"), c => Assert.DoesNotContain("position: sticky", c.GetAttribute("style") ?? "")); - // col4 (Action) right-pinned → right: 0px + // col4 (Action) end-pinned → inset-inline-end: 0px Assert.All(cut.FindAll("[col-index='4']"), c => - Assert.Contains("right: 0px", c.GetAttribute("style") ?? "")); + Assert.Contains("inset-inline-end: 0px", c.GetAttribute("style") ?? "")); } // ------------------------------------------------------------------------- @@ -310,10 +310,10 @@ var cut = Render>( @ - - + + - + ); From 23b3c4f96fed575c8d2910005a294871ee9454a7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 09:43:05 +0000 Subject: [PATCH 02/10] chore: remove accidentally committed build artifact and add obj/ to .gitignore Agent-Logs-Url: https://github.com/microsoft/fluentui-blazor/sessions/705a5b7c-1768-42a0-ba5f-faa99daf78d4 Co-authored-by: vnbaaij <1761079+vnbaaij@users.noreply.github.com> --- .gitignore | 1 + .../obj\\Debug/\\package.g.props" | 29 ------------------- 2 files changed, 1 insertion(+), 29 deletions(-) delete mode 100644 "src/Core.Scripts/obj\\Debug/\\package.g.props" diff --git a/.gitignore b/.gitignore index 4be8a1c616..61f81fb7a7 100644 --- a/.gitignore +++ b/.gitignore @@ -424,3 +424,4 @@ all-icons.json all-emojis.json /global.json /src/Core.Scripts/src/BuildConstants.ts +/src/Core.Scripts/obj/ diff --git "a/src/Core.Scripts/obj\\Debug/\\package.g.props" "b/src/Core.Scripts/obj\\Debug/\\package.g.props" deleted file mode 100644 index 4b7b3b41a5..0000000000 --- "a/src/Core.Scripts/obj\\Debug/\\package.g.props" +++ /dev/null @@ -1,29 +0,0 @@ - - - - true - microsoft.fluentui.aspnetcore.components.assets - src/index.ts - dist/Microsoft.FluentUI.AspNetCore.Components.lib.module.js - ../Core/Components/**/*.css - dist/Microsoft.FluentUI.AspNetCore.Components.bundle.scp.css - node ./esbuild.config.mjs - rimraf ./dist - [] - - ISC - module - ^1.15.9 - 8.57.1 - 8.57.1 - 0.27.4 - 0.0.1 - 10.0.3 - ^13.0.6 - ^7.6.1 - 6.1.3 - 6.4.0 - 5.9.3 - ^3.0.0-rc.9 - - \ No newline at end of file From c76e2cb39963e3e4482e348d6abfd4ec1edbb32c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 15:29:42 +0000 Subject: [PATCH 03/10] chore: plan wording updates for start/end docs and comments Agent-Logs-Url: https://github.com/microsoft/fluentui-blazor/sessions/e324508b-d7f0-40e2-bfec-a2707d42806c Co-authored-by: vnbaaij <1761079+vnbaaij@users.noreply.github.com> --- .../obj\\Debug/\\package.g.props" | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 "src/Core.Scripts/obj\\Debug/\\package.g.props" diff --git "a/src/Core.Scripts/obj\\Debug/\\package.g.props" "b/src/Core.Scripts/obj\\Debug/\\package.g.props" new file mode 100644 index 0000000000..4b7b3b41a5 --- /dev/null +++ "b/src/Core.Scripts/obj\\Debug/\\package.g.props" @@ -0,0 +1,29 @@ + + + + true + microsoft.fluentui.aspnetcore.components.assets + src/index.ts + dist/Microsoft.FluentUI.AspNetCore.Components.lib.module.js + ../Core/Components/**/*.css + dist/Microsoft.FluentUI.AspNetCore.Components.bundle.scp.css + node ./esbuild.config.mjs + rimraf ./dist + [] + + ISC + module + ^1.15.9 + 8.57.1 + 8.57.1 + 0.27.4 + 0.0.1 + 10.0.3 + ^13.0.6 + ^7.6.1 + 6.1.3 + 6.4.0 + 5.9.3 + ^3.0.0-rc.9 + + \ No newline at end of file From 1b4c295af697bd6cffafb7e6728f5dc2f25cf114 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 15:30:43 +0000 Subject: [PATCH 04/10] docs: simplify DataGrid start/end wording Agent-Logs-Url: https://github.com/microsoft/fluentui-blazor/sessions/e324508b-d7f0-40e2-bfec-a2707d42806c Co-authored-by: vnbaaij <1761079+vnbaaij@users.noreply.github.com> --- .../Pages/DataGridPinnedColumnsPage.md | 20 +++++++++---------- .../DataGrid/Columns/ColumnBase.razor.cs | 4 ++-- .../DataGrid/FluentDataGrid.razor.cs | 5 ++--- src/Core/Enums/DataGridColumnPin.cs | 8 ++++---- .../FluentDataGridPinnedColumnTests.razor | 16 +++++++-------- 5 files changed, 25 insertions(+), 28 deletions(-) diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridPinnedColumnsPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridPinnedColumnsPage.md index 49f9e7c9cc..d82c480ab4 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridPinnedColumnsPage.md +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridPinnedColumnsPage.md @@ -1,9 +1,8 @@ # Pinned columns -Columns can be pinned (frozen) to the inline-start or inline-end edge of the grid so that they remain visible -while the user scrolls horizontally through wider datasets. Using logical-property directions (`Start`/`End`) -instead of physical ones (`Left`/`Right`) means pinned columns automatically work correctly in both -LTR and RTL layouts. +Columns can be pinned (frozen) to the start or end edge of the grid so that they remain visible +while the user scrolls horizontally through wider datasets. Using `Start`/`End` instead of +`Left`/`Right` means pinned columns automatically work correctly in both LTR and RTL layouts. ## Parameters @@ -12,8 +11,8 @@ Set the `Pin` parameter on any `PropertyColumn` or `TemplateColumn`: | Value | Behavior | |---|---| | `DataGridColumnPin.None` | Default — column scrolls normally | -| `DataGridColumnPin.Start` | Column stays anchored to the inline-start edge (left in LTR, right in RTL) | -| `DataGridColumnPin.End` | Column stays anchored to the inline-end edge (right in LTR, left in RTL) | +| `DataGridColumnPin.Start` | Column stays anchored to the start edge (left in LTR, right in RTL) | +| `DataGridColumnPin.End` | Column stays anchored to the end edge (right in LTR, left in RTL) | ## Rules @@ -60,13 +59,12 @@ property `--fluent-data-grid-pinned-background`: ## Notes * Column resizing interacts correctly with sticky offsets — the JavaScript in - `FluentDataGrid.razor.ts` recalculates `inset-inline-start` / `inset-inline-end` values after - every resize step via `UpdatePinnedColumnOffsets`. + `FluentDataGrid.razor.ts` recalculates start and end offset values after every resize step via + `UpdatePinnedColumnOffsets`. * Virtualization and paging are fully compatible because each rendered row's cells carry the same `position: sticky` styling regardless of which page or scroll position is active. -* RTL layouts are fully supported: the CSS logical properties `inset-inline-start` and - `inset-inline-end` automatically map to the correct physical direction based on the document's - writing mode. +* RTL layouts are fully supported: start and end automatically map to the correct physical + direction based on the document's writing mode. ## Example diff --git a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs index 4f9f1ad86f..7c8adff08f 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs @@ -188,7 +188,7 @@ public abstract partial class ColumnBase public string? Width { get; set; } /// - /// Gets or sets whether this column is pinned (frozen) to the inline-start or inline-end edge of the grid, + /// Gets or sets whether this column is pinned (frozen) to the start or end edge of the grid, /// so it remains visible when the user scrolls horizontally. /// Pinned columns require an explicit in pixels (e.g., "150px"). /// Start-pinned columns must be contiguous at the start of the column list; @@ -198,7 +198,7 @@ public abstract partial class ColumnBase public DataGridColumnPin Pin { get; set; } = DataGridColumnPin.None; /// - /// The sticky inset-inline-start or inset-inline-end CSS offset computed by + /// The sticky start or end CSS offset computed by /// when columns are collected. /// Not intended for direct use by consumers. /// diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index f5cac8f869..c96c70e82f 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -642,7 +642,7 @@ private void ValidateAndComputePinnedColumns() ValidatePinnedColumnConstraints(); - // Compute start-pin sticky offsets (cumulative inline-start to inline-end). + // Compute start-pin sticky offsets in display order. var startOffset = 0.0; foreach (var col in _columns.Where(c => c.Pin == DataGridColumnPin.Start)) { @@ -650,7 +650,7 @@ private void ValidateAndComputePinnedColumns() startOffset += ParsePixelWidth(col.Width); } - // Compute end-pin sticky offsets (cumulative inline-end to inline-start). + // Compute end-pin sticky offsets in reverse display order. var endOffset = 0.0; foreach (var col in _columns.Where(c => c.Pin == DataGridColumnPin.End).Reverse()) { @@ -1445,4 +1445,3 @@ private async Task ToggleExpandedAsync(TGridItem item) } } } - diff --git a/src/Core/Enums/DataGridColumnPin.cs b/src/Core/Enums/DataGridColumnPin.cs index 7862eb9779..f666060c24 100644 --- a/src/Core/Enums/DataGridColumnPin.cs +++ b/src/Core/Enums/DataGridColumnPin.cs @@ -16,14 +16,14 @@ public enum DataGridColumnPin None, /// - /// The column is pinned to the inline-start edge of the grid (left in LTR, right in RTL). - /// The column will remain visible when the user scrolls toward the inline-end. + /// The column is pinned to the start edge of the grid. + /// The column will remain visible when the user scrolls toward the end. /// Start, /// - /// The column is pinned to the inline-end edge of the grid (right in LTR, left in RTL). - /// The column will remain visible when the user scrolls toward the inline-start. + /// The column is pinned to the end edge of the grid. + /// The column will remain visible when the user scrolls toward the start. /// End, } diff --git a/tests/Core/Components/DataGrid/FluentDataGridPinnedColumnTests.razor b/tests/Core/Components/DataGrid/FluentDataGridPinnedColumnTests.razor index e893f6174e..c4ad7669ad 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridPinnedColumnTests.razor +++ b/tests/Core/Components/DataGrid/FluentDataGridPinnedColumnTests.razor @@ -45,7 +45,7 @@ ); - // Assert — every cell in the column carries col-pinned-start and position:sticky;inset-inline-start:0px + // Assert — every cell in the column carries col-pinned-start and a start offset of 0px var cells = cut.FindAll("[col-index='1']"); Assert.NotEmpty(cells); Assert.All(cells, cell => @@ -103,7 +103,7 @@ [Fact] public void FluentDataGrid_PinnedColumn_Start_Multiple_Cumulative_Offsets() { - // Arrange && Act — col1: 100px, col2: 120px → col1 inset-inline-start:0px, col2 inset-inline-start:100px + // Arrange && Act — col1: 100px, col2: 120px → col1 start offset 0px, col2 start offset 100px var cut = Render>( @ @@ -127,7 +127,7 @@ public void FluentDataGrid_PinnedColumn_End_Multiple_Cumulative_Offsets() { // col1 (normal 1fr) | col2: 80px end-pinned | col3: 60px end-pinned - // col3 (rightmost) → inset-inline-end:0px | col2 → inset-inline-end:60px + // col3 (rightmost) → end offset 0px | col2 → end offset 60px var cut = Render>( @ @@ -137,11 +137,11 @@ ); - // col3 is index 3 — rightmost end-pinned → inset-inline-end: 0px + // col3 is index 3 — rightmost end-pinned → end offset 0px Assert.All(cut.FindAll("[col-index='3']"), cell => Assert.Contains("inset-inline-end: 0px", cell.GetAttribute("style") ?? "")); - // col2 is index 2 — next-to-last end-pinned → inset-inline-end: 60px (width of col3) + // col2 is index 2 — next-to-last end-pinned → end offset 60px (width of col3) Assert.All(cut.FindAll("[col-index='2']"), cell => Assert.Contains("inset-inline-end: 60px", cell.GetAttribute("style") ?? "")); } @@ -283,11 +283,11 @@ ); - // col1 (Id) start-pinned → inset-inline-start: 0px + // col1 (Id) start-pinned → start offset 0px Assert.All(cut.FindAll("[col-index='1']"), c => Assert.Contains("inset-inline-start: 0px", c.GetAttribute("style") ?? "")); - // col2 (Name) start-pinned → inset-inline-start: 50px + // col2 (Name) start-pinned → start offset 50px Assert.All(cut.FindAll("[col-index='2']"), c => Assert.Contains("inset-inline-start: 50px", c.GetAttribute("style") ?? "")); @@ -295,7 +295,7 @@ Assert.All(cut.FindAll("[col-index='3']"), c => Assert.DoesNotContain("position: sticky", c.GetAttribute("style") ?? "")); - // col4 (Action) end-pinned → inset-inline-end: 0px + // col4 (Action) end-pinned → end offset 0px Assert.All(cut.FindAll("[col-index='4']"), c => Assert.Contains("inset-inline-end: 0px", c.GetAttribute("style") ?? "")); } From 6e4243da07acc1bfaa332f6b77d880444251fe08 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 15:34:50 +0000 Subject: [PATCH 05/10] chore: remove tracked generated props file Agent-Logs-Url: https://github.com/microsoft/fluentui-blazor/sessions/9d4563a7-5f41-4b62-ab85-0d78d50060b1 Co-authored-by: vnbaaij <1761079+vnbaaij@users.noreply.github.com> --- .../obj\\Debug/\\package.g.props" | 29 ------------------- 1 file changed, 29 deletions(-) delete mode 100644 "src/Core.Scripts/obj\\Debug/\\package.g.props" diff --git "a/src/Core.Scripts/obj\\Debug/\\package.g.props" "b/src/Core.Scripts/obj\\Debug/\\package.g.props" deleted file mode 100644 index 4b7b3b41a5..0000000000 --- "a/src/Core.Scripts/obj\\Debug/\\package.g.props" +++ /dev/null @@ -1,29 +0,0 @@ - - - - true - microsoft.fluentui.aspnetcore.components.assets - src/index.ts - dist/Microsoft.FluentUI.AspNetCore.Components.lib.module.js - ../Core/Components/**/*.css - dist/Microsoft.FluentUI.AspNetCore.Components.bundle.scp.css - node ./esbuild.config.mjs - rimraf ./dist - [] - - ISC - module - ^1.15.9 - 8.57.1 - 8.57.1 - 0.27.4 - 0.0.1 - 10.0.3 - ^13.0.6 - ^7.6.1 - 6.1.3 - 6.4.0 - 5.9.3 - ^3.0.0-rc.9 - - \ No newline at end of file From a0f46eb2f61ed12c721d9e143f909e08a30bb15a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 15:53:21 +0000 Subject: [PATCH 06/10] docs: restore pinned columns page metadata Agent-Logs-Url: https://github.com/microsoft/fluentui-blazor/sessions/bbc46172-3499-4123-a119-cc10c435de7f Co-authored-by: vnbaaij <1761079+vnbaaij@users.noreply.github.com> --- .../Pages/DataGridPinnedColumnsPage.md | 9 ++++-- .../obj\\Debug/\\package.g.props" | 29 +++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 "src/Core.Scripts/obj\\Debug/\\package.g.props" diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridPinnedColumnsPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridPinnedColumnsPage.md index d82c480ab4..b97c848fce 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridPinnedColumnsPage.md +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridPinnedColumnsPage.md @@ -1,3 +1,8 @@ +--- +title: Pinned columns +route: /DataGrid/PinnedColumns +--- + # Pinned columns Columns can be pinned (frozen) to the start or end edge of the grid so that they remain visible @@ -58,9 +63,7 @@ property `--fluent-data-grid-pinned-background`: ## Notes -* Column resizing interacts correctly with sticky offsets — the JavaScript in - `FluentDataGrid.razor.ts` recalculates start and end offset values after every resize step via - `UpdatePinnedColumnOffsets`. +* Column resizing keeps pinned columns aligned as widths change. * Virtualization and paging are fully compatible because each rendered row's cells carry the same `position: sticky` styling regardless of which page or scroll position is active. * RTL layouts are fully supported: start and end automatically map to the correct physical diff --git "a/src/Core.Scripts/obj\\Debug/\\package.g.props" "b/src/Core.Scripts/obj\\Debug/\\package.g.props" new file mode 100644 index 0000000000..4b7b3b41a5 --- /dev/null +++ "b/src/Core.Scripts/obj\\Debug/\\package.g.props" @@ -0,0 +1,29 @@ + + + + true + microsoft.fluentui.aspnetcore.components.assets + src/index.ts + dist/Microsoft.FluentUI.AspNetCore.Components.lib.module.js + ../Core/Components/**/*.css + dist/Microsoft.FluentUI.AspNetCore.Components.bundle.scp.css + node ./esbuild.config.mjs + rimraf ./dist + [] + + ISC + module + ^1.15.9 + 8.57.1 + 8.57.1 + 0.27.4 + 0.0.1 + 10.0.3 + ^13.0.6 + ^7.6.1 + 6.1.3 + 6.4.0 + 5.9.3 + ^3.0.0-rc.9 + + \ No newline at end of file From b2175fe1acadd9289778ffce73deb70a461dffee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 15:53:37 +0000 Subject: [PATCH 07/10] chore: remove regenerated props file Agent-Logs-Url: https://github.com/microsoft/fluentui-blazor/sessions/bbc46172-3499-4123-a119-cc10c435de7f Co-authored-by: vnbaaij <1761079+vnbaaij@users.noreply.github.com> --- .../obj\\Debug/\\package.g.props" | 29 ------------------- 1 file changed, 29 deletions(-) delete mode 100644 "src/Core.Scripts/obj\\Debug/\\package.g.props" diff --git "a/src/Core.Scripts/obj\\Debug/\\package.g.props" "b/src/Core.Scripts/obj\\Debug/\\package.g.props" deleted file mode 100644 index 4b7b3b41a5..0000000000 --- "a/src/Core.Scripts/obj\\Debug/\\package.g.props" +++ /dev/null @@ -1,29 +0,0 @@ - - - - true - microsoft.fluentui.aspnetcore.components.assets - src/index.ts - dist/Microsoft.FluentUI.AspNetCore.Components.lib.module.js - ../Core/Components/**/*.css - dist/Microsoft.FluentUI.AspNetCore.Components.bundle.scp.css - node ./esbuild.config.mjs - rimraf ./dist - [] - - ISC - module - ^1.15.9 - 8.57.1 - 8.57.1 - 0.27.4 - 0.0.1 - 10.0.3 - ^13.0.6 - ^7.6.1 - 6.1.3 - 6.4.0 - 5.9.3 - ^3.0.0-rc.9 - - \ No newline at end of file From 2403bfd902f218dc0c81f4f01945464e221a32ad Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Sat, 4 Apr 2026 17:58:10 +0200 Subject: [PATCH 08/10] Fix formatting in DataGridPinnedColumnsPage.md --- .../Components/DataGrid/Pages/DataGridPinnedColumnsPage.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridPinnedColumnsPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridPinnedColumnsPage.md index b97c848fce..12dec08f05 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridPinnedColumnsPage.md +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridPinnedColumnsPage.md @@ -16,8 +16,8 @@ Set the `Pin` parameter on any `PropertyColumn` or `TemplateColumn`: | Value | Behavior | |---|---| | `DataGridColumnPin.None` | Default — column scrolls normally | -| `DataGridColumnPin.Start` | Column stays anchored to the start edge (left in LTR, right in RTL) | -| `DataGridColumnPin.End` | Column stays anchored to the end edge (right in LTR, left in RTL) | +| `DataGridColumnPin.Start` | Column stays anchored to the start edge | +| `DataGridColumnPin.End` | Column stays anchored to the end edge | ## Rules From c0b87b30d363dc63bfe00ca83395ce287a77b2f4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 08:53:08 +0000 Subject: [PATCH 09/10] fix: allow non-pixel pinned column widths Agent-Logs-Url: https://github.com/microsoft/fluentui-blazor/sessions/4c32bc45-2c71-462f-8fad-b3b8abd4a55a Co-authored-by: vnbaaij <1761079+vnbaaij@users.noreply.github.com> --- .../Pages/DataGridPinnedColumnsPage.md | 10 ++++---- .../DataGrid/Columns/ColumnBase.razor.cs | 8 ++++--- .../DataGrid/FluentDataGrid.razor.cs | 23 +++++++----------- .../DataGrid/FluentDataGrid.razor.ts | 2 ++ .../FluentDataGridPinnedColumnTests.razor | 24 ++++++++++++------- 5 files changed, 36 insertions(+), 31 deletions(-) diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridPinnedColumnsPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridPinnedColumnsPage.md index 12dec08f05..caa36cffb2 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridPinnedColumnsPage.md +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridPinnedColumnsPage.md @@ -21,14 +21,14 @@ Set the `Pin` parameter on any `PropertyColumn` or `TemplateColumn`: ## Rules -* **Explicit pixel width required.** Every pinned column must declare a `Width` in pixels - (e.g. `Width="150px"`). Relative units (`fr`, `%`) are not supported because the browser cannot - determine a fixed sticky offset from them at render time. +* **Explicit width required.** Every pinned column must declare a `Width`. + Pixel and non-pixel CSS units are supported. After the grid renders, sticky offsets are + recomputed from the rendered header widths so pinned columns stay aligned. * **Start-pinned columns must be contiguous at the start.** Each start-pinned column must immediately follow another start-pinned column, or be the very first column. * **End-pinned columns must be contiguous at the end.** Each end-pinned column must immediately precede another end-pinned column, or be the very last column. -* Violating any of these rules throws an `ArgumentException` with a descriptive message. +* Violating the missing-width or ordering rules throws an `ArgumentException` with a descriptive message. ## Scrollable container @@ -77,6 +77,6 @@ The two leftmost columns and the Actions column remain visible while the rest sc Wrap the grid in a `
` container and give the grid a `Style="min-width: max-content;"` so that the horizontal scroll bar appears. -Pinned columns require an explicit pixel `Width`. +Pinned columns require an explicit `Width`. {{ DataGridPinnedColumns }} diff --git a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs index 7c8adff08f..3a1441c6b1 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs @@ -190,7 +190,8 @@ public abstract partial class ColumnBase /// /// Gets or sets whether this column is pinned (frozen) to the start or end edge of the grid, /// so it remains visible when the user scrolls horizontally. - /// Pinned columns require an explicit in pixels (e.g., "150px"). + /// Pinned columns require an explicit . + /// Sticky offsets are recomputed from rendered header widths after the grid is rendered. /// Start-pinned columns must be contiguous at the start of the column list; /// end-pinned columns must be contiguous at the end. /// @@ -198,8 +199,9 @@ public abstract partial class ColumnBase public DataGridColumnPin Pin { get; set; } = DataGridColumnPin.None; /// - /// The sticky start or end CSS offset computed by - /// when columns are collected. + /// The sticky start or end CSS offset seeded by + /// when columns are collected and later updated from + /// rendered widths by JavaScript. /// Not intended for direct use by consumers. /// internal string PinOffset { get; set; } = "0px"; diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index c96c70e82f..bc8b596b84 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -600,7 +600,7 @@ private void FinishCollectingColumns() throw new ArgumentException("The 'HierarchicalToggle' parameter can only be set on the first column of the grid."); } - // Validate and compute offsets for pinned columns. + // Validate pinned columns and seed their initial sticky offsets. ValidateAndComputePinnedColumns(); // Always re-evaluate after collecting columns when using displaymode grid. A column might be added or hidden and the _internalGridTemplateColumns needs to reflect that. @@ -624,10 +624,11 @@ private void FinishCollectingColumns() } /// - /// Validates the pinned-column configuration and computes the sticky offsets for each - /// pinned column. Rules enforced: + /// Validates the pinned-column configuration and seeds initial sticky offsets for each + /// pinned column before JavaScript recomputes them from rendered widths after first render. + /// Rules enforced: /// - /// Pinned columns must specify an explicit pixel Width (e.g., "150px"). + /// Pinned columns must specify an explicit Width. /// Start-pinned columns must be contiguous at the beginning of the column list. /// End-pinned columns must be contiguous at the end of the column list. /// @@ -665,21 +666,14 @@ private void ValidateAndComputePinnedColumns() /// private void ValidatePinnedColumnConstraints() { - // Width must be an explicit pixel value. + // Width must be explicitly provided for pinned columns. 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')."); + "Pinned columns require an explicit Width."); } } @@ -710,7 +704,8 @@ private void ValidatePinnedColumnConstraints() /// /// Parses a CSS pixel value string such as "150px" and returns the numeric value. - /// Returns 0 if the string is null, empty, or not a valid pixel value. + /// Returns 0 if the string is null, empty, or not a valid pixel value so JavaScript + /// can recompute the final sticky offsets from rendered widths after first render. /// private static double ParsePixelWidth(string? width) { diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.ts b/src/Core/Components/DataGrid/FluentDataGrid.razor.ts index 395f90313f..ce16cc692c 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.ts +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.ts @@ -570,6 +570,8 @@ export namespace Microsoft.FluentUI.Blazor.DataGrid { * the widths of all end-pinned columns after it. * * The function reads the actual rendered header-cell width so it handles both Grid mode + * and pinned columns whose configured widths use non-pixel CSS units. + * It also handles both Grid mode * (CSS grid layout) and Table mode (standard table layout). Grid mode uses `offsetWidth` * (includes borders, matches the grid-track width) while Table mode uses `clientWidth` * (excludes borders, matches the CSS column width), consistent with how existing resize diff --git a/tests/Core/Components/DataGrid/FluentDataGridPinnedColumnTests.razor b/tests/Core/Components/DataGrid/FluentDataGridPinnedColumnTests.razor index c4ad7669ad..301fd79076 100644 --- a/tests/Core/Components/DataGrid/FluentDataGridPinnedColumnTests.razor +++ b/tests/Core/Components/DataGrid/FluentDataGridPinnedColumnTests.razor @@ -165,20 +165,26 @@ } // ------------------------------------------------------------------------- - // Pinned column with non-px Width throws + // Pinned column with non-px Width renders // ------------------------------------------------------------------------- [Fact] - public void FluentDataGrid_PinnedColumn_With_NonPixel_Width_Throws() + public void FluentDataGrid_PinnedColumn_With_NonPixel_Width_Renders() { - Assert.Throws(() => + var cut = Render>( + @ + + + + ); + + var cells = cut.FindAll("[col-index='1']"); + Assert.NotEmpty(cells); + Assert.All(cells, cell => { - Render>( - @ - - - - ); + Assert.Contains("col-pinned-start", cell.ClassName); + Assert.Contains("position: sticky", cell.GetAttribute("style") ?? ""); + Assert.Contains("inset-inline-start: 0px", cell.GetAttribute("style") ?? ""); }); } From 41021db49ab463067ce8c8b95e0877ad80c63040 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 08:55:48 +0000 Subject: [PATCH 10/10] docs: clarify pinned offset recomputation comments Agent-Logs-Url: https://github.com/microsoft/fluentui-blazor/sessions/4c32bc45-2c71-462f-8fad-b3b8abd4a55a Co-authored-by: vnbaaij <1761079+vnbaaij@users.noreply.github.com> --- src/Core/Components/DataGrid/FluentDataGrid.razor.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.ts b/src/Core/Components/DataGrid/FluentDataGrid.razor.ts index ce16cc692c..f029e1290d 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.ts +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.ts @@ -569,9 +569,8 @@ export namespace Microsoft.FluentUI.Blazor.DataGrid { * End-pinned columns are processed in reverse DOM order; each column's offset is the sum of * the widths of all end-pinned columns after it. * - * The function reads the actual rendered header-cell width so it handles both Grid mode - * and pinned columns whose configured widths use non-pixel CSS units. - * It also handles both Grid mode + * The function reads the actual rendered header-cell width so it handles pinned columns whose + * configured widths use non-pixel CSS units as well as both Grid mode * (CSS grid layout) and Table mode (standard table layout). Grid mode uses `offsetWidth` * (includes borders, matches the grid-track width) while Table mode uses `clientWidth` * (excludes borders, matches the CSS column width), consistent with how existing resize