Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/BootstrapBlazor/BootstrapBlazor.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<Version>10.5.1-beta05</Version>
<Version>10.5.1-beta06</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/BootstrapBlazor/Components/Table/Table.razor
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<CascadingValue Value="this" IsFixed="true">
@TableColumns?.Invoke(CreateTItem())
</CascadingValue>
@if (FirstRender)
@if (_firstRender)
{
if (ShowSkeleton)
{
Expand Down
118 changes: 67 additions & 51 deletions src/BootstrapBlazor/Components/Table/Table.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1091,11 +1091,7 @@ private void OnInitParameters()
SearchModel ??= CreateSearchModel();
}

/// <summary>
/// <para lang="zh">获得/设置 是否为第一次 Render</para>
/// <para lang="en">Gets or sets Whether it is the first Render</para>
/// </summary>
protected bool FirstRender { get; set; } = true;
private bool _firstRender = true;

/// <summary>
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

The protected FirstRender property was removed and replaced with a private field. Since Table is a public component, removing a protected member is a breaking change for anyone deriving from Table and using FirstRender in subclasses. Consider keeping the protected property (possibly forwarding to _firstRender and marking it [Obsolete]) to preserve backward compatibility.

Suggested change
/// <summary>
/// <summary>
/// <para lang="zh">获得/设置 首次渲染标志。已弃用,请勿在派生类中继续使用。</para>
/// <para lang="en">Gets or sets the first-render flag. Obsolete; do not continue using this in derived classes.</para>
/// </summary>
[Obsolete("已弃用,请勿继续使用 FirstRender。Deprecated, please do not continue using FirstRender.")]
protected bool FirstRender
{
get => _firstRender;
set => _firstRender = value;
}
/// <summary>

Copilot uses AI. Check for mistakes.
/// <para lang="zh">获得/设置 自动刷新 CancellationTokenSource 实例</para>
Expand Down Expand Up @@ -1140,7 +1136,7 @@ protected override void OnParametersSet()
IsTree = false;
}

if (!FirstRender)
if (!_firstRender)
{
// 动态列模式
ResetDynamicContext();
Expand All @@ -1153,6 +1149,21 @@ protected override void OnParametersSet()
_searchItems = null;
}

/// <summary>
/// <inheritdoc/>
/// </summary>
/// <returns></returns>
protected override async Task OnParametersSetAsync()
{
await base.OnParametersSetAsync();

if (!_firstRender)
{
// 重新读取浏览器设置
await OnTableColumnReset();
}
Comment on lines +1156 to +1164
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

OnParametersSetAsync calls base.OnParametersSetAsync(), and ComponentBase's base implementation invokes OnParametersSet(). Because this component already overrides OnParametersSet(), this will cause OnParametersSet() to run twice per parameter update, duplicating work (and potentially triggering side effects) after the first render. Remove the base.OnParametersSetAsync() call (or move all logic into a single override) so OnParametersSet executes only once per update.

Copilot uses AI. Check for mistakes.
}

/// <summary>
/// <inheritdoc/>
/// </summary>
Expand Down Expand Up @@ -1237,6 +1248,54 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
}
}

private async Task OnTableColumnReset()
{
// 动态列模式
var cols = new List<ITableColumn>();
if (DynamicContext != null && typeof(TItem).IsAssignableTo(typeof(IDynamicObject)))
{
cols.AddRange(DynamicContext.GetColumns());
}
else if (AutoGenerateColumns)
{
cols.AddRange(Utility.GetTableColumns<TItem>(Columns));
}
else
{
cols.AddRange(Columns);
}

if (ColumnOrderCallback != null)
{
cols = [.. ColumnOrderCallback(cols)];
}

await ReloadColumnOrdersFromBrowserAsync(cols);

// 查看是否开启列宽序列化
await ReloadColumnWidthFromBrowserAsync(cols);

if (OnColumnCreating != null)
{
await OnColumnCreating(cols);
}

InternalResetVisibleColumns(cols);

await ReloadColumnVisibleFromBrowserAsync();

Columns.Clear();
Columns.AddRange(cols.OrderFunc());

// set default sortName
var col = Columns.Find(i => i is { Sortable: true, DefaultSort: true });
if (col != null)
{
SortName = col.GetFieldName();
SortOrder = col.DefaultSortOrder;
Comment on lines +1290 to +1295
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

OnTableColumnReset resets SortName/SortOrder to the default sort column every time it runs. Since this method is now also invoked from OnParametersSetAsync on subsequent parameter updates, it can unintentionally override user-driven sorting state. Only apply the default sort when sorting is currently unset (e.g., SortName is null/empty and SortOrder is Unset) or restrict this logic to first render.

Suggested change
// set default sortName
var col = Columns.Find(i => i is { Sortable: true, DefaultSort: true });
if (col != null)
{
SortName = col.GetFieldName();
SortOrder = col.DefaultSortOrder;
// set default sortName only when sort is not already initialized
if (string.IsNullOrEmpty(SortName) && SortOrder == SortOrder.Unset)
{
var col = Columns.Find(i => i is { Sortable: true, DefaultSort: true });
if (col != null)
{
SortName = col.GetFieldName();
SortOrder = col.DefaultSortOrder;
}

Copilot uses AI. Check for mistakes.
}
}

private async Task OnTableRenderAsync(bool firstRender)
{
if (firstRender)
Expand Down Expand Up @@ -1372,52 +1431,9 @@ private async Task ProcessFirstRender()
IsLoading = true;

// 设置渲染完毕
FirstRender = false;

// 动态列模式
var cols = new List<ITableColumn>();
if (DynamicContext != null && typeof(TItem).IsAssignableTo(typeof(IDynamicObject)))
{
cols.AddRange(DynamicContext.GetColumns());
}
else if (AutoGenerateColumns)
{
cols.AddRange(Utility.GetTableColumns<TItem>(Columns));
}
else
{
cols.AddRange(Columns);
}
_firstRender = false;

if (ColumnOrderCallback != null)
{
cols = [.. ColumnOrderCallback(cols)];
}

await ReloadColumnOrdersFromBrowserAsync(cols);

// 查看是否开启列宽序列化
await ReloadColumnWidthFromBrowserAsync(cols);

if (OnColumnCreating != null)
{
await OnColumnCreating(cols);
}

InternalResetVisibleColumns(cols);

await ReloadColumnVisibleFromBrowserAsync();

Columns.Clear();
Columns.AddRange(cols.OrderFunc());

// set default sortName
var col = Columns.Find(i => i is { Sortable: true, DefaultSort: true });
if (col != null)
{
SortName = col.GetFieldName();
SortOrder = col.DefaultSortOrder;
}
await OnTableColumnReset();

// 获取是否自动查询参数值
_autoQuery = IsAutoQueryFirstRender;
Expand Down
65 changes: 15 additions & 50 deletions src/BootstrapBlazor/Components/Table/Table.razor.js
Original file line number Diff line number Diff line change
Expand Up @@ -536,41 +536,24 @@ const setExcelKeyboardListener = table => {
}

const resetTableWidth = table => {
const { options: { scrollWidth } } = table;
table.tables.forEach(t => {
const group = [...t.children].find(i => i.nodeName === 'COLGROUP')
if (group) {
const colgroup = getLastColgroup(t, group);
if (colgroup) {
colgroup.style = null;
}
const width = getTableWidthByColumnGroup(t, 100);
if (width >= t.parentElement.offsetWidth + scrollWidth) {
t.style.width = `${width}px`;
}
else {
t.style.width = null;
}
let width = 0;
[...group.children].forEach(col => {
let colWidth = parseInt(col.style.width);
if (isNaN(colWidth)) {
colWidth = 100;
}
width += colWidth;
})
t.style.width = `${width}px`;

saveColumnWidth(table);
Comment on lines 538 to 552
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

resetTableWidth now always forces each table's width to the sum of colgroup widths and no longer accounts for fixed-header mode (header vs body width needing scrollWidth compensation) or the previous conditional behavior that cleared width when not needed. This can cause header/body misalignment and unnecessary horizontal scrolling. Align this logic with setTableDefaultWidth / resize-drag behavior (e.g., subtract scrollWidth for the fixed-body table and only set explicit width when required).

Copilot uses AI. Check for mistakes.
}
})
}

const getLastColgroup = (table, group) => {
const p = table.parentElement;
if (p) {
const length = group.children.length;
if (p.classList.contains("table-fixed-header")) {
return group.children[length - 2];
}
else {
return group.children[length - 1];
}
}
return null;
}

const setResizeListener = table => {
if (table.options.showColumnWidthTooltip) {
table.handlers.setResizeHandler = e => {
Expand Down Expand Up @@ -1064,33 +1047,15 @@ const saveColumnWidth = table => {
}));
}

const getTableWidthByColumnGroup = (table, defaultWidth) => {
return [...table.querySelectorAll('colgroup col')]
.map((col, index) => getColumnRenderWidth(table, col, index, defaultWidth))
.reduce((accumulator, val) => accumulator + val, 0);
}

const getColumnRenderWidth = (table, col, index, defaultWidth) => {
let width = parseFloat(col.style.width);
if (!isNaN(width) && width > 0) {
return width;
}

const header = table.querySelectorAll('thead > tr:last-child > th').item(index);
width = header?.offsetWidth ?? 0;
if (width > 0) {
return width;
}

const row = [...table.querySelectorAll('tbody > tr')].find(i => !i.classList.contains('is-detail'));
width = row?.children.item(index)?.offsetWidth ?? 0;
return width > 0 ? width : defaultWidth;
}

const setTableDefaultWidth = table => {
if (table.tables.length > 0 && isVisible(table.tables[0])) {
const { scrollWidth, columnMinWidth } = table.options;
const tableWidth = getTableWidthByColumnGroup(table.tables[0], columnMinWidth);
const tableWidth = [...table.tables[0].querySelectorAll('col')]
.map(i => {
const colWidth = parseFloat(i.style.width);
return isNaN(colWidth) ? columnMinWidth : colWidth;
})
.reduce((accumulator, val) => accumulator + val, 0);
Comment on lines 1051 to +1058
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

setTableDefaultWidth now calculates tableWidth purely from col.style.width with a columnMinWidth fallback. For columns without an explicit width (style is empty), this can significantly overestimate the real rendered width and force an unnecessary fixed width + horizontal scroll. The previous implementation measured actual header/body cell widths when style width was absent. Consider restoring that measurement-based fallback (offsetWidth) rather than defaulting to columnMinWidth for unset widths.

Copilot uses AI. Check for mistakes.

if (tableWidth > table.el.offsetWidth) {
table.tables[0].style.setProperty('width', `${tableWidth}px`);
Expand Down
Loading