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/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..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 @@ -1,13 +1,13 @@ --- 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 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 @@ -16,19 +16,19 @@ 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 start edge | +| `DataGridColumnPin.End` | Column stays anchored to the end edge | ## 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. -* Violating any of these rules throws an `ArgumentException` with a descriptive message. +* **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 the missing-width or ordering rules throws an `ArgumentException` with a descriptive message. ## Scrollable container @@ -39,10 +39,10 @@ bar appears when columns overflow the container: ```razor
- - + + - + ... @@ -63,22 +63,20 @@ 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`. +* 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. -* 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: start and 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;"` 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 d5e53fa542..3a1441c6b1 100644 --- a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs +++ b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs @@ -188,21 +188,23 @@ 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 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"). - /// Left-pinned columns must be contiguous at the start of the column list; - /// right-pinned columns must be contiguous at the end. + /// 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. /// [Parameter] public DataGridColumnPin Pin { get; set; } = DataGridColumnPin.None; /// - /// The sticky left or right CSS offset (in pixels) 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 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..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,12 +624,13 @@ private void FinishCollectingColumns() } /// - /// Validates the pinned-column configuration and computes the sticky pixel 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"). - /// 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. + /// 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. /// /// private void ValidateAndComputePinnedColumns() @@ -642,20 +643,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 in display order. + 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 in reverse display order. + 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); } } @@ -665,52 +666,46 @@ 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."); } } - // 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."); } } } /// /// 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) { @@ -1445,4 +1440,3 @@ private async Task ToggleExpandedAsync(TGridItem item) } } } - 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..f029e1290d 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.ts +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.ts @@ -564,12 +564,13 @@ 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 + * 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 @@ -592,7 +593,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 +603,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..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 left edge of the grid. - /// The column will remain visible when the user scrolls right. + /// The column is pinned to the start edge of the grid. + /// The column will remain visible when the user scrolls toward the 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 end edge of the grid. + /// The column will remain visible when the user scrolls toward the 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..301fd79076 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 a start offset of 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 start offset 0px, col2 start offset 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) → end offset 0px | col2 → end offset 60px var cut = Render>( @ - - + + ); - // col3 is index 3 — rightmost right-pinned → right: 0px + // col3 is index 3 — rightmost end-pinned → end offset 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 → end offset 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,85 +158,91 @@ Render>( @ - + ); }); } // ------------------------------------------------------------------------- - // 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") ?? ""); }); } // ------------------------------------------------------------------------- - // 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 +250,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 → start offset 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 → start offset 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 → end offset 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 +316,10 @@ var cut = Render>( @ - - + + - + );