diff --git a/src/AtomUI.Controls.DataGrid/AtomUI.Controls.DataGrid.csproj b/src/AtomUI.Controls.DataGrid/AtomUI.Controls.DataGrid.csproj
index 065528396..c26890ae9 100644
--- a/src/AtomUI.Controls.DataGrid/AtomUI.Controls.DataGrid.csproj
+++ b/src/AtomUI.Controls.DataGrid/AtomUI.Controls.DataGrid.csproj
@@ -18,5 +18,11 @@
+
+
+
+
+
+
diff --git a/src/AtomUI.Controls.DataGrid/DataGridEnumerations.cs b/src/AtomUI.Controls.DataGrid/DataGridEnumerations.cs
new file mode 100644
index 000000000..3012e2222
--- /dev/null
+++ b/src/AtomUI.Controls.DataGrid/DataGridEnumerations.cs
@@ -0,0 +1,98 @@
+namespace AtomUI.Controls;
+
+///
+/// Used to specify action to take out of edit mode.
+///
+public enum DataGridEditAction
+{
+ ///
+ /// Cancel the changes.
+ ///
+ Cancel,
+
+ ///
+ /// Commit edited value.
+ ///
+ Commit
+}
+
+// Determines the location and visibility of the editing row.
+internal enum DataGridEditingRowLocation
+{
+ Bottom = 0, // The editing row is collapsed below the displayed rows
+ Inline = 1, // The editing row is visible and displayed
+ Top = 2 // The editing row is collapsed above the displayed rows
+}
+
+///
+/// Determines whether the inner cells' vertical/horizontal gridlines are shown or not.
+///
+[Flags]
+public enum DataGridGridLinesVisibility
+{
+ None = 0,
+ Horizontal = 1,
+ Vertical = 2,
+ All = 3,
+}
+
+public enum DataGridEditingUnit
+{
+ Cell = 0,
+ Row = 1,
+}
+
+///
+/// Determines whether the row/column headers are shown or not.
+///
+[Flags]
+public enum DataGridHeadersVisibility
+{
+ ///
+ /// Show Row, Column, and Corner Headers
+ ///
+ All = Row | Column,
+
+ ///
+ /// Show only Column Headers with top-right corner Header
+ ///
+ Column = 0x01,
+
+ ///
+ /// Show only Row Headers with bottom-left corner
+ ///
+ Row = 0x02,
+
+ ///
+ /// Don’t show any Headers
+ ///
+ None = 0x00
+}
+
+public enum DataGridRowDetailsVisibilityMode
+{
+ Collapsed = 2, // Show no details. Developer is in charge of toggling visibility.
+ Visible = 1, // Show the details section for all rows.
+ VisibleWhenSelected = 0 // Show the details section only for the selected row(s).
+}
+
+///
+/// Determines the type of action to take when selecting items
+///
+internal enum DataGridSelectionAction
+{
+ AddCurrentToSelection,
+ None,
+ RemoveCurrentFromSelection,
+ SelectCurrent,
+ SelectFromAnchorToCurrent
+}
+
+///
+/// Determines the selection model
+///
+public enum DataGridSelectionMode
+{
+ Extended = 0,
+ Single = 1
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls.DataGrid/DataGridError.cs b/src/AtomUI.Controls.DataGrid/DataGridError.cs
new file mode 100644
index 000000000..0f0ad6850
--- /dev/null
+++ b/src/AtomUI.Controls.DataGrid/DataGridError.cs
@@ -0,0 +1,201 @@
+// (c) Copyright Microsoft Corporation.
+// This source is subject to the Microsoft Public License (Ms-PL).
+// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
+// All other rights reserved.
+
+using System.Globalization;
+
+namespace AtomUI.Controls;
+
+internal static class DataGridError
+{
+ public static class DataGrid
+ {
+ public static InvalidOperationException CannotChangeItemsWhenLoadingRows()
+ {
+ return new InvalidOperationException(
+ "Items cannot be added, removed or reset while rows are loading or unloading.");
+ }
+
+ public static InvalidOperationException CannotChangeColumnCollectionWhileAdjustingDisplayIndexes()
+ {
+ return new InvalidOperationException(
+ "Column collection cannot be changed while adjusting display indexes.");
+ }
+
+ public static InvalidOperationException ColumnCannotBeCollapsed()
+ {
+ return new InvalidOperationException("Column cannot be collapsed.");
+ }
+
+ public static InvalidOperationException ColumnCannotBeReassignedToDifferentDataGrid()
+ {
+ return new InvalidOperationException(
+ "Column already belongs to a DataGrid instance and cannot be reassigned.");
+ }
+
+ public static ArgumentException ColumnNotInThisDataGrid()
+ {
+ return new ArgumentException("Provided column does not belong to this DataGrid.");
+ }
+
+ public static ArgumentException ItemIsNotContainedInTheItemsSource(string paramName)
+ {
+ return new ArgumentException("The item is not contained in the ItemsSource.", paramName);
+ }
+
+ public static InvalidOperationException NoCurrentRow()
+ {
+ return new InvalidOperationException("There is no current row. Operation cannot be completed.");
+ }
+
+ public static InvalidOperationException NoOwningGrid(Type type)
+ {
+ return new InvalidOperationException(Format(
+ "There is no instance of DataGrid assigned to this {0}. Operation cannot be completed.",
+ type.FullName ?? type.Name));
+ }
+
+ public static InvalidOperationException UnderlyingPropertyIsReadOnly(string paramName)
+ {
+ return new InvalidOperationException(
+ Format("{0} cannot be set because the underlying property is read only.", paramName));
+ }
+
+ public static ArgumentException ValueCannotBeSetToInfinity(string paramName)
+ {
+ return new ArgumentException(Format("{0} cannot be set to infinity.", paramName));
+ }
+
+ public static ArgumentException ValueCannotBeSetToNAN(string paramName)
+ {
+ return new ArgumentException(Format("{0} cannot be set to double.NAN.", paramName));
+ }
+
+ public static ArgumentNullException ValueCannotBeSetToNull(string paramName, string valueName)
+ {
+ return new ArgumentNullException(paramName, Format("{0} cannot be set to a null value.", valueName));
+ }
+
+ public static ArgumentException ValueIsNotAnInstanceOf(string paramName, Type type)
+ {
+ return new ArgumentException(paramName, Format("The value is not an instance of {0}.", type.FullName ?? type.Name));
+ }
+
+ public static ArgumentException ValueIsNotAnInstanceOfEitherOr(string paramName, Type type1, Type type2)
+ {
+ return new ArgumentException(paramName,
+ Format("The value is not an instance of {0} or {1}.", type1.FullName ?? type1.Name, type2.FullName ?? type2.Name));
+ }
+
+ public static ArgumentOutOfRangeException ValueMustBeBetween(string paramName, string valueName,
+ object lowValue, bool lowInclusive,
+ object highValue, bool highInclusive)
+ {
+ string? message = null;
+
+ if (lowInclusive && highInclusive)
+ {
+ message = "{0} must be greater than or equal to {1} and less than or equal to {2}.";
+ }
+ else if (lowInclusive && !highInclusive)
+ {
+ message = "{0} must be greater than or equal to {1} and less than {2}.";
+ }
+ else if (!lowInclusive && highInclusive)
+ {
+ message = "{0} must be greater than {1} and less than or equal to {2}.";
+ }
+ else
+ {
+ message = "{0} must be greater than {1} and less than {2}.";
+ }
+
+ return new ArgumentOutOfRangeException(paramName, Format(message, valueName, lowValue, highValue));
+ }
+
+ public static ArgumentOutOfRangeException ValueMustBeGreaterThanOrEqualTo(
+ string paramName, string valueName, object value)
+ {
+ return new ArgumentOutOfRangeException(paramName,
+ Format("{0} must be greater than or equal to {1}.", valueName, value));
+ }
+
+ public static ArgumentOutOfRangeException ValueMustBeLessThanOrEqualTo(
+ string paramName, string valueName, object value)
+ {
+ return new ArgumentOutOfRangeException(paramName,
+ Format("{0} must be less than or equal to {1}.", valueName, value));
+ }
+
+ public static ArgumentOutOfRangeException ValueMustBeLessThan(string paramName, string valueName, object value)
+ {
+ return new ArgumentOutOfRangeException(paramName, Format("{0} must be less than {1}.", valueName, value));
+ }
+ }
+
+ public static class DataGridColumnHeader
+ {
+ public static NotSupportedException ContentDoesNotSupportUIElements()
+ {
+ return new NotSupportedException("Content does not support Controls; use ContentTemplate instead.");
+ }
+ }
+
+ public static class DataGridLength
+ {
+ public static ArgumentException InvalidUnitType(string paramName)
+ {
+ return new ArgumentException(Format("{0} is not a valid DataGridLengthUnitType.", paramName), paramName);
+ }
+ }
+
+ public static class DataGridLengthConverter
+ {
+ public static NotSupportedException CannotConvertFrom(string paramName)
+ {
+ return new NotSupportedException(Format("DataGridLengthConverter cannot convert from {0}.", paramName));
+ }
+
+ public static NotSupportedException CannotConvertTo(string paramName)
+ {
+ return new NotSupportedException(Format("Cannot convert from DataGridLength to {0}.", paramName));
+ }
+
+ public static NotSupportedException InvalidDataGridLength(string paramName)
+ {
+ return new NotSupportedException(Format("Invalid DataGridLength.", paramName));
+ }
+ }
+
+ public static class DataGridRow
+ {
+ public static InvalidOperationException InvalidRowIndexCannotCompleteOperation()
+ {
+ return new InvalidOperationException("Invalid row index. Operation cannot be completed.");
+ }
+ }
+
+ public static class DataGridSelectedItemsCollection
+ {
+ public static InvalidOperationException CannotChangeSelectedItemsCollectionInSingleMode()
+ {
+ return new InvalidOperationException(
+ "Can only change SelectedItems collection in Extended selection mode. Use SelectedItem property in Single selection mode.");
+ }
+ }
+
+ public static class DataGridTemplateColumn
+ {
+ public static TypeInitializationException MissingTemplateForType(Type type)
+ {
+ return new TypeInitializationException(Format("Missing template. Cannot initialize {0}.", type.FullName ?? type.Name),
+ null);
+ }
+ }
+
+ private static string Format(string formatString, params object[] args)
+ {
+ return String.Format(CultureInfo.CurrentCulture, formatString, args);
+ }
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls.DataGrid/DataGridLength.cs b/src/AtomUI.Controls.DataGrid/DataGridLength.cs
new file mode 100644
index 000000000..81c514509
--- /dev/null
+++ b/src/AtomUI.Controls.DataGrid/DataGridLength.cs
@@ -0,0 +1,15 @@
+namespace AtomUI.Controls;
+
+public enum DataGridLengthUnitType
+{
+ Auto = 0,
+ Pixel = 1,
+ SizeToCells = 2,
+ SizeToHeader = 3,
+ Star = 4
+}
+
+public class DataGridLength
+{
+
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls.DataGrid/DataGridLengthConverter.cs b/src/AtomUI.Controls.DataGrid/DataGridLengthConverter.cs
new file mode 100644
index 000000000..9f5536f34
--- /dev/null
+++ b/src/AtomUI.Controls.DataGrid/DataGridLengthConverter.cs
@@ -0,0 +1,6 @@
+namespace AtomUI.Controls;
+
+public class DataGridLengthConverter
+{
+
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs b/src/AtomUI.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs
new file mode 100644
index 000000000..c6c2ad5b0
--- /dev/null
+++ b/src/AtomUI.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs
@@ -0,0 +1,344 @@
+// using Avalonia.Controls;
+// using Avalonia.LogicalTree;
+//
+// namespace AtomUI.Controls.Primitives;
+//
+// public sealed class DataGridCellsPresenter: Panel, IChildIndexProvider
+// {
+// private double _fillerLeftEdge;
+// private EventHandler _childIndexChanged;
+//
+//
+// // The desired height needs to be cached due to column virtualization; otherwise, the cells
+// // would grow and shrink as the DataGrid scrolls horizontally
+// private double DesiredHeight
+// {
+// get;
+// set;
+// }
+//
+// private DataGrid OwningGrid
+// {
+// get
+// {
+// return OwningRow?.OwningGrid;
+// }
+// }
+//
+// internal DataGridRow OwningRow
+// {
+// get;
+// set;
+// }
+//
+// event EventHandler IChildIndexProvider.ChildIndexChanged
+// {
+// add => _childIndexChanged += value;
+// remove => _childIndexChanged -= value;
+// }
+//
+// int IChildIndexProvider.GetChildIndex(ILogical child)
+// {
+// return child is DataGridCell cell
+// ? cell.OwningColumn?.DisplayIndex ?? -1
+// : throw new InvalidOperationException("Invalid cell type");
+// }
+//
+// bool IChildIndexProvider.TryGetTotalCount(out int count)
+// {
+// count = Children.Count - 1; // Adjust for filler column
+// return true;
+// }
+//
+// ///
+// /// Arranges the content of the .
+// ///
+// ///
+// /// The actual size used by the .
+// ///
+// ///
+// /// The final area within the parent that this element should use to arrange itself and its children.
+// ///
+// protected override Size ArrangeOverride(Size finalSize)
+// {
+// if (OwningGrid == null)
+// {
+// return base.ArrangeOverride(finalSize);
+// }
+//
+// if (OwningGrid.AutoSizingColumns)
+// {
+// // When we initially load an auto-column, we have to wait for all the rows to be measured
+// // before we know its final desired size. We need to trigger a new round of measures now
+// // that the final sizes have been calculated.
+// OwningGrid.AutoSizingColumns = false;
+// return base.ArrangeOverride(finalSize);
+// }
+//
+// double frozenLeftEdge = 0;
+// double scrollingLeftEdge = -OwningGrid.HorizontalOffset;
+//
+// double cellLeftEdge;
+// foreach (DataGridColumn column in OwningGrid.ColumnsInternal.GetVisibleColumns())
+// {
+// DataGridCell cell = OwningRow.Cells[column.Index];
+// Debug.Assert(cell.OwningColumn == column);
+// Debug.Assert(column.IsVisible);
+//
+// if (column.IsFrozen)
+// {
+// cellLeftEdge = frozenLeftEdge;
+// // This can happen before or after clipping because frozen cells aren't clipped
+// frozenLeftEdge += column.ActualWidth;
+// }
+// else
+// {
+// cellLeftEdge = scrollingLeftEdge;
+// }
+// if (cell.IsVisible)
+// {
+// cell.Arrange(new Rect(cellLeftEdge, 0, column.LayoutRoundedWidth, finalSize.Height));
+// EnsureCellClip(cell, column.ActualWidth, finalSize.Height, frozenLeftEdge, scrollingLeftEdge);
+// }
+// scrollingLeftEdge += column.ActualWidth;
+// column.IsInitialDesiredWidthDetermined = true;
+// }
+//
+// _fillerLeftEdge = scrollingLeftEdge;
+//
+// OwningRow.FillerCell.Arrange(new Rect(_fillerLeftEdge, 0, OwningGrid.ColumnsInternal.FillerColumn.FillerWidth, finalSize.Height));
+//
+// return finalSize;
+// }
+//
+// private static void EnsureCellClip(DataGridCell cell, double width, double height, double frozenLeftEdge, double cellLeftEdge)
+// {
+// // Clip the cell only if it's scrolled under frozen columns. Unfortunately, we need to clip in this case
+// // because cells could be transparent
+// if (!cell.OwningColumn.IsFrozen && frozenLeftEdge > cellLeftEdge)
+// {
+// RectangleGeometry rg = new RectangleGeometry();
+// double xClip = Math.Round(Math.Min(width, frozenLeftEdge - cellLeftEdge));
+// rg.Rect = new Rect(xClip, 0, Math.Max(0, width - xClip), height);
+// cell.Clip = rg;
+// }
+// else
+// {
+// cell.Clip = null;
+// }
+// }
+//
+// protected override void ChildrenChanged(object sender, NotifyCollectionChangedEventArgs e)
+// {
+// base.ChildrenChanged(sender, e);
+//
+// InvalidateChildIndex();
+// }
+//
+// private static void EnsureCellDisplay(DataGridCell cell, bool displayColumn)
+// {
+// if (cell.IsCurrent)
+// {
+// if (displayColumn)
+// {
+// cell.IsVisible = true;
+// cell.Clip = null;
+// }
+// else
+// {
+// // Clip
+// RectangleGeometry rg = new RectangleGeometry();
+// rg.Rect = default;
+// cell.Clip = rg;
+// }
+// }
+// else
+// {
+// cell.IsVisible = displayColumn;
+// }
+// }
+//
+// internal void EnsureFillerVisibility()
+// {
+// DataGridFillerColumn fillerColumn = OwningGrid.ColumnsInternal.FillerColumn;
+// bool newVisibility = fillerColumn.IsActive;
+// if (OwningRow.FillerCell.IsVisible != newVisibility)
+// {
+// OwningRow.FillerCell.IsVisible = newVisibility;
+// if (newVisibility)
+// {
+// OwningRow.FillerCell.Arrange(new Rect(_fillerLeftEdge, 0, fillerColumn.FillerWidth, Bounds.Height));
+// }
+// }
+//
+// // This must be done after the Filler visibility is determined. This also must be done
+// // regardless of whether or not the filler visibility actually changed values because
+// // we could scroll in a cell that didn't have EnsureGridLine called yet
+// DataGridColumn lastVisibleColumn = OwningGrid.ColumnsInternal.LastVisibleColumn;
+// if (lastVisibleColumn != null)
+// {
+// DataGridCell cell = OwningRow.Cells[lastVisibleColumn.Index];
+// cell.EnsureGridLine(lastVisibleColumn);
+// }
+// }
+//
+// ///
+// /// Measures the children of a to
+// /// prepare for arranging them during the pass.
+// ///
+// ///
+// /// The available size that this element can give to child elements. Indicates an upper limit that child elements should not exceed.
+// ///
+// ///
+// /// The size that the determines it needs during layout, based on its calculations of child object allocated sizes.
+// ///
+// protected override Size MeasureOverride(Size availableSize)
+// {
+// if (OwningGrid == null)
+// {
+// return base.MeasureOverride(availableSize);
+// }
+//
+// bool autoSizeHeight;
+// double measureHeight;
+// if (double.IsNaN(OwningGrid.RowHeight))
+// {
+// // No explicit height values were set so we can autosize
+// autoSizeHeight = true;
+// // We need to invalidate desired height in order to grow or shrink as needed
+// InvalidateDesiredHeight();
+// measureHeight = double.PositiveInfinity;
+// }
+// else
+// {
+// DesiredHeight = OwningGrid.RowHeight;
+// measureHeight = DesiredHeight;
+// autoSizeHeight = false;
+// }
+//
+// double frozenLeftEdge = 0;
+// double totalDisplayWidth = 0;
+// double scrollingLeftEdge = -OwningGrid.HorizontalOffset;
+// OwningGrid.ColumnsInternal.EnsureVisibleEdgedColumnsWidth();
+// DataGridColumn lastVisibleColumn = OwningGrid.ColumnsInternal.LastVisibleColumn;
+// foreach (DataGridColumn column in OwningGrid.ColumnsInternal.GetVisibleColumns())
+// {
+// DataGridCell cell = OwningRow.Cells[column.Index];
+// // Measure the entire first row to make the horizontal scrollbar more accurate
+// bool shouldDisplayCell = ShouldDisplayCell(column, frozenLeftEdge, scrollingLeftEdge) || OwningRow.Index == 0;
+// EnsureCellDisplay(cell, shouldDisplayCell);
+// if (shouldDisplayCell)
+// {
+// DataGridLength columnWidth = column.Width;
+// bool autoGrowWidth = columnWidth.IsSizeToCells || columnWidth.IsAuto;
+// if (column != lastVisibleColumn)
+// {
+// cell.EnsureGridLine(lastVisibleColumn);
+// }
+//
+// // If we're not using star sizing or the current column can't be resized,
+// // then just set the display width according to the column's desired width
+// if (!OwningGrid.UsesStarSizing || (!column.ActualCanUserResize && !column.Width.IsStar))
+// {
+// // In the edge-case where we're given infinite width and we have star columns, the
+// // star columns grow to their predefined limit of 10,000 (or their MaxWidth)
+// double newDisplayWidth = column.Width.IsStar ?
+// Math.Min(column.ActualMaxWidth, DataGrid.DATAGRID_maximumStarColumnWidth) :
+// Math.Max(column.ActualMinWidth, Math.Min(column.ActualMaxWidth, column.Width.DesiredValue));
+// column.SetWidthDisplayValue(newDisplayWidth);
+// }
+//
+// // If we're auto-growing the column based on the cell content, we want to measure it at its maximum value
+// if (autoGrowWidth)
+// {
+// cell.Measure(new Size(column.ActualMaxWidth, measureHeight));
+// OwningGrid.AutoSizeColumn(column, cell.DesiredSize.Width);
+// column.ComputeLayoutRoundedWidth(totalDisplayWidth);
+// }
+// else if (!OwningGrid.UsesStarSizing)
+// {
+// column.ComputeLayoutRoundedWidth(scrollingLeftEdge);
+// cell.Measure(new Size(column.LayoutRoundedWidth, measureHeight));
+// }
+//
+// // We need to track the largest height in order to auto-size
+// if (autoSizeHeight)
+// {
+// DesiredHeight = Math.Max(DesiredHeight, cell.DesiredSize.Height);
+// }
+// }
+//
+// if (column.IsFrozen)
+// {
+// frozenLeftEdge += column.ActualWidth;
+// }
+// scrollingLeftEdge += column.ActualWidth;
+// totalDisplayWidth += column.ActualWidth;
+// }
+//
+// // If we're using star sizing (and we're not waiting for an auto-column to finish growing)
+// // then we will resize all the columns to fit the available space.
+// if (OwningGrid.UsesStarSizing && !OwningGrid.AutoSizingColumns)
+// {
+// double adjustment = OwningGrid.CellsWidth - totalDisplayWidth;
+// totalDisplayWidth += adjustment - OwningGrid.AdjustColumnWidths(0, adjustment, false);
+//
+// // Since we didn't know the final widths of the columns until we resized,
+// // we waited until now to measure each cell
+// double leftEdge = 0;
+// if (autoSizeHeight)
+// DesiredHeight = 0;
+//
+// foreach (DataGridColumn column in OwningGrid.ColumnsInternal.GetVisibleColumns())
+// {
+// DataGridCell cell = OwningRow.Cells[column.Index];
+// column.ComputeLayoutRoundedWidth(leftEdge);
+// cell.Measure(new Size(column.LayoutRoundedWidth, measureHeight));
+// if (autoSizeHeight)
+// {
+// DesiredHeight = Math.Max(DesiredHeight, cell.DesiredSize.Height);
+// }
+// leftEdge += column.ActualWidth;
+// }
+// }
+//
+// // Measure FillerCell, we're doing it unconditionally here because we don't know if we'll need the filler
+// // column and we don't want to cause another Measure if we do
+// OwningRow.FillerCell.Measure(new Size(double.PositiveInfinity, DesiredHeight));
+//
+// OwningGrid.ColumnsInternal.EnsureVisibleEdgedColumnsWidth();
+// return new Size(OwningGrid.ColumnsInternal.VisibleEdgedColumnsWidth, DesiredHeight);
+// }
+//
+// internal void Recycle()
+// {
+// // Clear out the cached desired height so it is not reused for other rows
+// DesiredHeight = 0;
+// }
+//
+// internal void InvalidateDesiredHeight()
+// {
+// DesiredHeight = 0;
+// }
+//
+// internal void InvalidateChildIndex()
+// {
+// _childIndexChanged?.Invoke(this, ChildIndexChangedEventArgs.ChildIndexesReset);
+// }
+//
+// private bool ShouldDisplayCell(DataGridColumn column, double frozenLeftEdge, double scrollingLeftEdge)
+// {
+// if (!column.IsVisible)
+// {
+// return false;
+// }
+//
+// scrollingLeftEdge += OwningGrid.HorizontalAdjustment;
+// double leftEdge = column.IsFrozen ? frozenLeftEdge : scrollingLeftEdge;
+// double rightEdge = leftEdge + column.ActualWidth;
+// return
+// MathUtilities.GreaterThan(rightEdge, 0) &&
+// MathUtilities.LessThanOrClose(leftEdge, OwningGrid.CellsWidth) &&
+// MathUtilities.GreaterThan(rightEdge, frozenLeftEdge); // scrolling column covered up by frozen column(s)
+// }
+// }
\ No newline at end of file