From 72ffffabc2cd18f87264e3aec1fd45ec1b66b752 Mon Sep 17 00:00:00 2001 From: Tony996 <6914529@qq.com> Date: Fri, 12 Jun 2026 16:19:40 +0800 Subject: [PATCH 01/30] =?UTF-8?q?Table=E7=BB=84=E4=BB=B6=E8=83=BD=E5=90=A6?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8A=A8=E6=80=81=E8=AE=BE=E7=BD=AE=E5=9B=BA?= =?UTF-8?q?=E5=AE=9A=E5=88=97=E7=9A=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor: 可以动态设置固定列,同时固定列支持表格持久化 --- .../Components/Table/Table.razor.cs | 26 +++++++++- .../Components/Table/Table.razor.js | 20 ++++++-- .../Components/Table/TableColumnState.cs | 6 +++ test/UnitTest/Components/TableTest.cs | 49 +++++++++++++++++++ 4 files changed, 97 insertions(+), 4 deletions(-) diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.cs b/src/BootstrapBlazor/Components/Table/Table.razor.cs index 8b71d314d81..328f6ee3b35 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.cs +++ b/src/BootstrapBlazor/Components/Table/Table.razor.cs @@ -1435,6 +1435,7 @@ private void ResetTableColumns() { var item = _tableColumnStates[index]; var col = columnMap[item.Name]; + col.Fixed = !string.IsNullOrEmpty(item.Name) && stateMap.TryGetValue(item.Name, out var state) ? stateMap[item.Name].Fixed : false; if (item.Visible) { // 增加到可见列缓存集合 @@ -1449,7 +1450,8 @@ private void ResetTableColumns() DisplayName = col.GetDisplayName(), Name = col.GetFieldName(), Width = col.Fixed && !col.Width.HasValue ? DefaultFixedColumnWidth : col.Width, - Visible = col.GetVisible(_screenSize) + Visible = col.GetVisible(_screenSize), + Fixed = col.Fixed }; private async Task OnTableRenderAsync(bool firstRender) @@ -1940,6 +1942,28 @@ public async Task FitAllColumnWidth() await InvokeVoidAsync("fitAllColumnWidth", Id); } + /// + /// 更新表格列客户端状态方法 + /// Update Table Column Client Status Method + /// + /// + public async Task UpdateTableColumnClientStatus() + { + if (Columns.Count != 0) + { + // 用户在外面变更了列状态后,为避免用户变更状态丢失,须将变更后的状态同步到缓存中 + foreach (var item in Columns) + { + _tableColumnStates.Find(x => x.Name == item.GetFieldName())!.Fixed = item.Fixed; + } + StateHasChanged(); + } + // 如果启用了 ClientTableName 则更新浏览器持久化列状态 + await InvokeVoidAsync("updateColumnStates", Id); + + return _tableColumnStateCache; + } + /// /// 清除表格列客户端状态实例方法 /// clear table column client status instance method diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.js b/src/BootstrapBlazor/Components/Table/Table.razor.js index 94804c8322e..48f02cfef78 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.js +++ b/src/BootstrapBlazor/Components/Table/Table.razor.js @@ -586,7 +586,7 @@ const autoFitColumnWidth = async (table, col) => { const index = indexOfCol(col); let rows = null; let maxWidth = getColumnMaxCellWidth(table, index); - + if (table.options.fitColumnWidthIncludeHeader) { const th = getColumnHeader(col); maxWidth = Math.max(maxWidth, getCellWidth(th)); @@ -936,6 +936,18 @@ const removeColumnWidthState = tableName => { localStorage.removeItem(columnWidthKey); } +export function updateColumnStates(id) { + const el = document.getElementById(id) + if (el === null) { + return + } + let table = Data.get(id) + table.el = el; + Data.set(id, table) + const state = getColumnStateObject(table); + saveColumnStateToLocalstorage(table, state); + +} export function clearColumnStates(tableName) { const columnStateKey = `bb-table-${tableName}`; localStorage.removeItem(columnStateKey); @@ -976,7 +988,8 @@ const getColumnStateObject = table => { return { name: col.name, width: getColumnWidth(col, table.columns), - visible: col.visible + visible: col.visible, + fixed: col.fixed } }), table: getTableWidth(table.tables[0]) @@ -988,7 +1001,8 @@ const getColumnStateObject = table => { return { name: getColumnName(col), width: getResizableColumnWidth(col), - visible: true + visible: true, + fixed: getColumnHeader(col).classList.contains('fixed') } }), table: getTableWidth(table.tables[0]) diff --git a/src/BootstrapBlazor/Components/Table/TableColumnState.cs b/src/BootstrapBlazor/Components/Table/TableColumnState.cs index 1aad8e442d8..3a2e181395a 100644 --- a/src/BootstrapBlazor/Components/Table/TableColumnState.cs +++ b/src/BootstrapBlazor/Components/Table/TableColumnState.cs @@ -38,4 +38,10 @@ public class TableColumnState /// Gets or sets column width /// public int? Width { get; set; } + + /// + /// 获得/设置 列固定 + /// Gets or sets column fixed + /// + public bool Fixed { get; set; } } diff --git a/test/UnitTest/Components/TableTest.cs b/test/UnitTest/Components/TableTest.cs index 3f48b29f279..41329364b4e 100644 --- a/test/UnitTest/Components/TableTest.cs +++ b/test/UnitTest/Components/TableTest.cs @@ -9018,6 +9018,55 @@ public async Task OnTableColumnClientStatusChanged_ResizeColumn_Ok() Assert.NotNull(clientState); } + [Fact] + public async Task UpdateTableColumnClientStatus_Ok() + { + var state = new TableColumnClientStatus(); + state.TableWidth = 220; + state.Columns.Add(new TableColumnState() { Name = nameof(Foo.Name), Visible = true, Width = 100, Fixed = true }); + state.Columns.Add(new TableColumnState() { Name = nameof(Foo.Address), Visible = true, Width = 120, Fixed = false }); + + Context.JSInterop.Setup("getColumnStates", "test_update").SetResult(state); + var invoker = Context.JSInterop.SetupVoid("clearColumnStates", "test_update"); + invoker.SetVoidResult(); + + var localizer = Context.Services.GetRequiredService>(); + var cut = Context.Render(pb => + { + pb.AddChildContent>(pb => + { + pb.Add(a => a.ClientTableName, "test_update"); + pb.Add(a => a.RenderMode, TableRenderMode.Table); + pb.Add(a => a.AllowResizing, true); + pb.Add(a => a.OnQueryAsync, OnQueryAsync(localizer)); + pb.Add(a => a.TableColumns, foo => builder => + { + builder.OpenComponent>(0); + builder.AddAttribute(1, "Field", "Name"); + builder.AddAttribute(2, "FieldExpression", Utility.GenerateValueExpression(foo, "Name", typeof(string))); + builder.AddAttribute(3, "Width", 80); + builder.CloseComponent(); + + builder.OpenComponent>(0); + builder.AddAttribute(3, "Field", "Address"); + builder.AddAttribute(4, "FieldExpression", Utility.GenerateValueExpression(foo, "Address", typeof(string))); + builder.CloseComponent(); + }); + }); + }); + + // 由于启用了客户端持久化 Name 列宽使用 100 而非 80 + var table = cut.FindComponent>(); + var colGroup = table.Find("colgroup"); + Assert.Contains("style=\"width: 100px;\"", colGroup.ToMarkup()); + Assert.Contains("style=\"width: 120px;\"", colGroup.ToMarkup()); + + + var status = await cut.InvokeAsync(() => table.Instance.UpdateTableColumnClientStatus()); + Assert.Equal(true, status.Columns[0].Fixed); + Assert.Equal(state.Columns.Count, status.Columns.Count); + } + [Fact] public async Task ClearTableColumnClientStatus_Ok() { From 74435e64a1451bd800b3a94dc9e47d15d209a49a Mon Sep 17 00:00:00 2001 From: Tony996 <6914529@qq.com> Date: Fri, 12 Jun 2026 16:50:03 +0800 Subject: [PATCH 02/30] =?UTF-8?q?=E6=B8=85=E9=99=A4=E7=A9=BA=E8=A1=8C?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=8A=A0=E7=BC=93=E5=AD=98=E5=88=97=E5=8F=AF?= =?UTF-8?q?=E8=83=BD=E4=B8=BA=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor:清除空行,增加缓存列可能为判断 --- src/BootstrapBlazor/Components/Table/Table.razor.cs | 6 +++++- src/BootstrapBlazor/Components/Table/Table.razor.js | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.cs b/src/BootstrapBlazor/Components/Table/Table.razor.cs index 328f6ee3b35..4f7045596f3 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.cs +++ b/src/BootstrapBlazor/Components/Table/Table.razor.cs @@ -1954,7 +1954,11 @@ public async Task UpdateTableColumnClientStatus() // 用户在外面变更了列状态后,为避免用户变更状态丢失,须将变更后的状态同步到缓存中 foreach (var item in Columns) { - _tableColumnStates.Find(x => x.Name == item.GetFieldName())!.Fixed = item.Fixed; + var columnState = _tableColumnStates.Find(x => x.Name == item.GetFieldName()); + if (columnState != null) + { + columnState.Fixed = item.Fixed; + } } StateHasChanged(); } diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.js b/src/BootstrapBlazor/Components/Table/Table.razor.js index 48f02cfef78..0de8d1c639b 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.js +++ b/src/BootstrapBlazor/Components/Table/Table.razor.js @@ -946,7 +946,6 @@ export function updateColumnStates(id) { Data.set(id, table) const state = getColumnStateObject(table); saveColumnStateToLocalstorage(table, state); - } export function clearColumnStates(tableName) { const columnStateKey = `bb-table-${tableName}`; From d2b777569f73370f2b43489ee1ea52cca450b05a Mon Sep 17 00:00:00 2001 From: Tony996 <6914529@qq.com> Date: Fri, 12 Jun 2026 20:41:34 +0800 Subject: [PATCH 03/30] =?UTF-8?q?refactor:=20=E8=A1=A5=E5=85=85=E5=8A=A8?= =?UTF-8?q?=E6=80=81=E5=9B=BA=E5=AE=9A=E5=88=97=E6=97=B6=EF=BC=8C=E6=B2=A1?= =?UTF-8?q?=E6=9C=89=E5=88=97=E5=AE=BD=E7=BB=99=E9=BB=98=E8=AE=A4=E5=88=97?= =?UTF-8?q?=E5=AE=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor: 补充动态固定列时,没有列宽给默认列宽,以防止无列宽时固定后,横向滑动出现错位 --- src/BootstrapBlazor/Components/Table/Table.razor.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.cs b/src/BootstrapBlazor/Components/Table/Table.razor.cs index 4f7045596f3..95695da3ec0 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.cs +++ b/src/BootstrapBlazor/Components/Table/Table.razor.cs @@ -1958,6 +1958,7 @@ public async Task UpdateTableColumnClientStatus() if (columnState != null) { columnState.Fixed = item.Fixed; + columnState.Width = item.Width.HasValue ? item.Width : (item.Fixed ? DefaultFixedColumnWidth : null); } } StateHasChanged(); From 02a3face8abcb188b16342b0f57cd35a2938941b Mon Sep 17 00:00:00 2001 From: Tony996 <6914529@qq.com> Date: Fri, 12 Jun 2026 23:16:50 +0800 Subject: [PATCH 04/30] =?UTF-8?q?=E8=A1=A5=E5=85=85=E5=8D=95=E5=85=83?= =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor: 调整指定固定列时,列宽必须有值 test:补充单元测试 --- .../Components/Table/Table.razor.cs | 2 +- test/UnitTest/Components/TableTest.cs | 20 ++++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.cs b/src/BootstrapBlazor/Components/Table/Table.razor.cs index 95695da3ec0..65cce099d97 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.cs +++ b/src/BootstrapBlazor/Components/Table/Table.razor.cs @@ -1958,7 +1958,7 @@ public async Task UpdateTableColumnClientStatus() if (columnState != null) { columnState.Fixed = item.Fixed; - columnState.Width = item.Width.HasValue ? item.Width : (item.Fixed ? DefaultFixedColumnWidth : null); + columnState.Width = item.Fixed && !item.Width.HasValue ? DefaultFixedColumnWidth : item.Width; } } StateHasChanged(); diff --git a/test/UnitTest/Components/TableTest.cs b/test/UnitTest/Components/TableTest.cs index 41329364b4e..e610b54aae2 100644 --- a/test/UnitTest/Components/TableTest.cs +++ b/test/UnitTest/Components/TableTest.cs @@ -9023,11 +9023,11 @@ public async Task UpdateTableColumnClientStatus_Ok() { var state = new TableColumnClientStatus(); state.TableWidth = 220; - state.Columns.Add(new TableColumnState() { Name = nameof(Foo.Name), Visible = true, Width = 100, Fixed = true }); + state.Columns.Add(new TableColumnState() { Name = nameof(Foo.Name), Visible = true, Fixed = true }); state.Columns.Add(new TableColumnState() { Name = nameof(Foo.Address), Visible = true, Width = 120, Fixed = false }); Context.JSInterop.Setup("getColumnStates", "test_update").SetResult(state); - var invoker = Context.JSInterop.SetupVoid("clearColumnStates", "test_update"); + var invoker = Context.JSInterop.SetupVoid("updateColumnStates", "test_update"); invoker.SetVoidResult(); var localizer = Context.Services.GetRequiredService>(); @@ -9044,7 +9044,7 @@ public async Task UpdateTableColumnClientStatus_Ok() builder.OpenComponent>(0); builder.AddAttribute(1, "Field", "Name"); builder.AddAttribute(2, "FieldExpression", Utility.GenerateValueExpression(foo, "Name", typeof(string))); - builder.AddAttribute(3, "Width", 80); + builder.AddAttribute(3, "Fixed", true); builder.CloseComponent(); builder.OpenComponent>(0); @@ -9058,13 +9058,23 @@ public async Task UpdateTableColumnClientStatus_Ok() // 由于启用了客户端持久化 Name 列宽使用 100 而非 80 var table = cut.FindComponent>(); var colGroup = table.Find("colgroup"); - Assert.Contains("style=\"width: 100px;\"", colGroup.ToMarkup()); Assert.Contains("style=\"width: 120px;\"", colGroup.ToMarkup()); - var status = await cut.InvokeAsync(() => table.Instance.UpdateTableColumnClientStatus()); Assert.Equal(true, status.Columns[0].Fixed); Assert.Equal(state.Columns.Count, status.Columns.Count); + + table = cut.FindComponent>(); + var columns = cut.FindAll("th"); + colGroup = table.Find("colgroup"); + Assert.Contains("style=\"width: 200px;\"", colGroup.ToMarkup()); + if (columns[0].ClassName.Contains("fixed")) + { + var fixedWidth = cut.FindAll("col")[0].OuterHtml.Contains("width: 200px"); + Assert.Equal("fixedWidth:True", $"fixedWidth:{fixedWidth}"); + } + + } [Fact] From 33988986414bc92fba998f00fd5e4ee679427e6b Mon Sep 17 00:00:00 2001 From: Tony996 <6914529@qq.com> Date: Fri, 12 Jun 2026 23:20:20 +0800 Subject: [PATCH 05/30] =?UTF-8?q?=E6=B8=85=E9=99=A4=E5=A4=9A=E4=BD=99?= =?UTF-8?q?=E7=A9=BA=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test:清除多余空行 --- test/UnitTest/Components/TableTest.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/UnitTest/Components/TableTest.cs b/test/UnitTest/Components/TableTest.cs index e610b54aae2..e88e00e0eaf 100644 --- a/test/UnitTest/Components/TableTest.cs +++ b/test/UnitTest/Components/TableTest.cs @@ -9055,7 +9055,6 @@ public async Task UpdateTableColumnClientStatus_Ok() }); }); - // 由于启用了客户端持久化 Name 列宽使用 100 而非 80 var table = cut.FindComponent>(); var colGroup = table.Find("colgroup"); Assert.Contains("style=\"width: 120px;\"", colGroup.ToMarkup()); @@ -9073,8 +9072,6 @@ public async Task UpdateTableColumnClientStatus_Ok() var fixedWidth = cut.FindAll("col")[0].OuterHtml.Contains("width: 200px"); Assert.Equal("fixedWidth:True", $"fixedWidth:{fixedWidth}"); } - - } [Fact] From 09582044d773781135e69bd3db374dee42a5563c Mon Sep 17 00:00:00 2001 From: Tony <6914529@qq.com> Date: Tue, 7 Jul 2026 17:53:41 +0800 Subject: [PATCH 06/30] =?UTF-8?q?=E8=A1=A5=E5=85=85=E5=9B=BA=E5=AE=9A?= =?UTF-8?q?=E5=88=97=E6=8C=81=E4=B9=85=E5=8C=96=E5=9B=9E=E6=9D=A5=E6=97=B6?= =?UTF-8?q?style=E4=B8=8A=E7=9A=84=E5=80=BC=E4=B8=8D=E6=AD=A3=E7=A1=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor:将持久化回来的列宽给到_visibleColumnsCache,以避免style运算出来的值会以默认列宽或200的数值参与运算 --- src/BootstrapBlazor/Components/Table/Table.razor.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.cs b/src/BootstrapBlazor/Components/Table/Table.razor.cs index 65cce099d97..0ec7891d254 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.cs +++ b/src/BootstrapBlazor/Components/Table/Table.razor.cs @@ -1435,7 +1435,10 @@ private void ResetTableColumns() { var item = _tableColumnStates[index]; var col = columnMap[item.Name]; - col.Fixed = !string.IsNullOrEmpty(item.Name) && stateMap.TryGetValue(item.Name, out var state) ? stateMap[item.Name].Fixed : false; + col.Fixed = !string.IsNullOrEmpty(item.Name) && stateMap.TryGetValue(item.Name, out var stateF) ? stateMap[item.Name].Fixed : false; + // 这里一定要使用 stateMap[item.Name].Width 而不是 item.Width 因为 item.Width 可能是 null; + // 以解决持久化回来时,列宽为_visibleColumnsCache里面列宽为null 的问题导致固定列上style时值不正确的问题 + col.Width = !string.IsNullOrEmpty(item.Name) && stateMap.TryGetValue(item.Name, out var stateW) ? stateMap[item.Name].Width: col.Width; if (item.Visible) { // 增加到可见列缓存集合 From 2dd7992506ba5d6e57f75445dfb9ccbdb13d3720 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 12 Jun 2026 20:26:45 +0800 Subject: [PATCH 07/30] doc(Localization): add SearchFormLocalizerOptions localization (#8112) --- localization/de.json | 10 +++++++++- localization/es.json | 10 +++++++++- localization/it.json | 10 +++++++++- localization/km-KH.json | 12 ++++++++++-- localization/pt-BR.json | 10 +++++++++- localization/pt.json | 10 +++++++++- localization/ru-RU.json | 10 +++++++++- localization/th-TH.json | 10 +++++++++- localization/uk-UA.json | 10 +++++++++- localization/zh-TW.json | 8 ++++++++ 10 files changed, 90 insertions(+), 10 deletions(-) diff --git a/localization/de.json b/localization/de.json index bf6cd28c639..6c2c77dddaf 100644 --- a/localization/de.json +++ b/localization/de.json @@ -397,7 +397,7 @@ "DefaultRequiredErrorMessage": "{0} ist erforderlich." }, "BootstrapBlazor.Components.NetworkMonitorIndicator": { - "NTitle": "Netzwerk", + "Title": "Netzwerk", "NetworkType": "Netzwerktyp", "Downlink": "Download", "RTT": "RTT" @@ -407,5 +407,13 @@ }, "BootstrapBlazor.Components.CardUpload": { "DeleteConfirmContent": "Sind Sie sicher, dass Sie die aktuellen Daten löschen möchten?" + }, + "BootstrapBlazor.Components.SearchFormLocalizerOptions": { + "SelectAllText": "Alle", + "BooleanAllText": "Alle", + "BooleanTrueText": "Ja", + "BooleanFalseText": "Nein", + "NumberStartValueLabelText": "Startwert", + "NumberEndValueLabelText": "Endwert" } } diff --git a/localization/es.json b/localization/es.json index fc28ecadcba..47fbc140ca9 100644 --- a/localization/es.json +++ b/localization/es.json @@ -397,7 +397,7 @@ "DefaultRequiredErrorMessage": "{0} es requerido." }, "BootstrapBlazor.Components.NetworkMonitorIndicator": { - "NTitle": "Red", + "Title": "Red", "NetworkType": "Tipo de red", "Downlink": "Descarga", "RTT": "RTT" @@ -407,5 +407,13 @@ }, "BootstrapBlazor.Components.CardUpload": { "DeleteConfirmContent": "¿Está seguro de que desea eliminar los datos actuales?" + }, + "BootstrapBlazor.Components.SearchFormLocalizerOptions": { + "SelectAllText": "Todos", + "BooleanAllText": "Todos", + "BooleanTrueText": "Sí", + "BooleanFalseText": "No", + "NumberStartValueLabelText": "Inicio", + "NumberEndValueLabelText": "Fin" } } diff --git a/localization/it.json b/localization/it.json index b7490284c34..c94983047d9 100644 --- a/localization/it.json +++ b/localization/it.json @@ -397,7 +397,7 @@ "DefaultRequiredErrorMessage": "{0} è obbligatorio." }, "BootstrapBlazor.Components.NetworkMonitorIndicator": { - "NTitle": "Rete", + "Title": "Rete", "NetworkType": "Tipo di rete", "Downlink": "Download", "RTT": "RTT" @@ -407,5 +407,13 @@ }, "BootstrapBlazor.Components.CardUpload": { "DeleteConfirmContent": "Sei sicuro di voler eliminare i dati correnti?" + }, + "BootstrapBlazor.Components.SearchFormLocalizerOptions": { + "SelectAllText": "Tutti", + "BooleanAllText": "Tutti", + "BooleanTrueText": "Sì", + "BooleanFalseText": "No", + "NumberStartValueLabelText": "Inizio", + "NumberEndValueLabelText": "Fine" } } diff --git a/localization/km-KH.json b/localization/km-KH.json index 03e661f4cf4..92c92e067d2 100644 --- a/localization/km-KH.json +++ b/localization/km-KH.json @@ -99,7 +99,7 @@ "BootstrapBlazor.Components.EqualToValidator": { "ErrorMessage": "សូម​បញ្ចូល​តម្លៃ​ដូច​គ្នា​ម្តង​ទៀត" }, - "BootstrapBlazor.Components.ErrorLogger": { + "BootstrapBlazor.Components.BootstrapBlazorErrorBoundary": { "ToastTitle": "កំហុស​កម្មវិធី" }, "BootstrapBlazor.Components.GoTop": { @@ -397,7 +397,7 @@ "DefaultRequiredErrorMessage": "{0} គឺ​ចាំបាច់។" }, "BootstrapBlazor.Components.NetworkMonitorIndicator": { - "NTitle": "បណ្តាញ", + "Title": "បណ្តាញ", "NetworkType": "ប្រភេទ​បណ្តាញ", "Downlink": "Downlink", "RTT": "RTT" @@ -407,5 +407,13 @@ }, "BootstrapBlazor.Components.CardUpload": { "DeleteConfirmContent": "តើ​អ្នក​ប្រាកដ​ទេ​ថា​ចង់​លុប​ទិន្នន័យ​បច្ចុប្បន្ន?" + }, + "BootstrapBlazor.Components.SearchFormLocalizerOptions": { + "SelectAllText": "ទាំងអស់", + "BooleanAllText": "ទាំងអស់", + "BooleanTrueText": "ពិត", + "BooleanFalseText": "មិនពិត", + "NumberStartValueLabelText": "ចាប់ផ្តើម", + "NumberEndValueLabelText": "បញ្ចប់" } } diff --git a/localization/pt-BR.json b/localization/pt-BR.json index ed84eaa1924..1691acc82c6 100644 --- a/localization/pt-BR.json +++ b/localization/pt-BR.json @@ -397,7 +397,7 @@ "DefaultRequiredErrorMessage": "{0} é obrigatório." }, "BootstrapBlazor.Components.NetworkMonitorIndicator": { - "NTitle": "Rede", + "Title": "Rede", "NetworkType": "Tipo de rede", "Downlink": "Downlink", "RTT": "RTT" @@ -407,5 +407,13 @@ }, "BootstrapBlazor.Components.CardUpload": { "DeleteConfirmContent": "Tem certeza de que deseja excluir os dados atuais?" + }, + "BootstrapBlazor.Components.SearchFormLocalizerOptions": { + "SelectAllText": "Todos", + "BooleanAllText": "Todos", + "BooleanTrueText": "Sim", + "BooleanFalseText": "Não", + "NumberStartValueLabelText": "Início", + "NumberEndValueLabelText": "Fim" } } diff --git a/localization/pt.json b/localization/pt.json index 3f1e6fa3ab2..f9deaab3207 100644 --- a/localization/pt.json +++ b/localization/pt.json @@ -397,7 +397,7 @@ "DefaultRequiredErrorMessage": "{0} é obrigatório." }, "BootstrapBlazor.Components.NetworkMonitorIndicator": { - "NTitle": "Rede", + "Title": "Rede", "NetworkType": "Tipo de rede", "Downlink": "Downlink", "RTT": "RTT" @@ -407,5 +407,13 @@ }, "BootstrapBlazor.Components.CardUpload": { "DeleteConfirmContent": "Tem certeza de que deseja excluir os dados atuais?" + }, + "BootstrapBlazor.Components.SearchFormLocalizerOptions": { + "SelectAllText": "Todos", + "BooleanAllText": "Todos", + "BooleanTrueText": "Sim", + "BooleanFalseText": "Não", + "NumberStartValueLabelText": "Início", + "NumberEndValueLabelText": "Fim" } } diff --git a/localization/ru-RU.json b/localization/ru-RU.json index 661d4b10566..c84135954c6 100644 --- a/localization/ru-RU.json +++ b/localization/ru-RU.json @@ -397,7 +397,7 @@ "DefaultRequiredErrorMessage": "{0} обязательно для заполнения." }, "BootstrapBlazor.Components.NetworkMonitorIndicator": { - "NTitle": "Сеть", + "Title": "Сеть", "NetworkType": "Тип сети", "Downlink": "Скорость загрузки", "RTT": "RTT" @@ -407,5 +407,13 @@ }, "BootstrapBlazor.Components.CardUpload": { "DeleteConfirmContent": "Вы уверены, что хотите удалить текущие данные?" + }, + "BootstrapBlazor.Components.SearchFormLocalizerOptions": { + "SelectAllText": "Все", + "BooleanAllText": "Все", + "BooleanTrueText": "Да", + "BooleanFalseText": "Нет", + "NumberStartValueLabelText": "Начало", + "NumberEndValueLabelText": "Конец" } } diff --git a/localization/th-TH.json b/localization/th-TH.json index a6185f5826f..8492e394c2a 100644 --- a/localization/th-TH.json +++ b/localization/th-TH.json @@ -397,7 +397,7 @@ "DefaultRequiredErrorMessage": "{0} จำเป็นต้องกรอก" }, "BootstrapBlazor.Components.NetworkMonitorIndicator": { - "NTitle": "เครือข่าย", + "Title": "เครือข่าย", "NetworkType": "ประเภทเครือข่าย", "Downlink": "ดาวน์โหลด", "RTT": "RTT" @@ -407,5 +407,13 @@ }, "BootstrapBlazor.Components.CardUpload": { "DeleteConfirmContent": "คุณแน่ใจหรือไม่ว่าต้องการลบข้อมูลปัจจุบัน?" + }, + "BootstrapBlazor.Components.SearchFormLocalizerOptions": { + "SelectAllText": "ทั้งหมด", + "BooleanAllText": "ทั้งหมด", + "BooleanTrueText": "ใช่", + "BooleanFalseText": "ไม่ใช่", + "NumberStartValueLabelText": "เริ่มต้น", + "NumberEndValueLabelText": "สิ้นสุด" } } diff --git a/localization/uk-UA.json b/localization/uk-UA.json index 83d2c200d08..25d2f8488ce 100644 --- a/localization/uk-UA.json +++ b/localization/uk-UA.json @@ -397,7 +397,7 @@ "DefaultRequiredErrorMessage": "{0} є обов'язковим." }, "BootstrapBlazor.Components.NetworkMonitorIndicator": { - "NTitle": "Мережа", + "Title": "Мережа", "NetworkType": "Тип мережі", "Downlink": "Швидкість завантаження", "RTT": "RTT" @@ -407,5 +407,13 @@ }, "BootstrapBlazor.Components.CardUpload": { "DeleteConfirmContent": "Ви впевнені, що хочете видалити поточні дані?" + }, + "BootstrapBlazor.Components.SearchFormLocalizerOptions": { + "SelectAllText": "Усі", + "BooleanAllText": "Усі", + "BooleanTrueText": "Так", + "BooleanFalseText": "Ні", + "NumberStartValueLabelText": "Початок", + "NumberEndValueLabelText": "Кінець" } } diff --git a/localization/zh-TW.json b/localization/zh-TW.json index 44f25ae7384..2473b581046 100644 --- a/localization/zh-TW.json +++ b/localization/zh-TW.json @@ -407,5 +407,13 @@ }, "BootstrapBlazor.Components.CardUpload": { "DeleteConfirmContent": "您確定要刪除當前資料嗎?" + }, + "BootstrapBlazor.Components.SearchFormLocalizerOptions": { + "SelectAllText": "全部", + "BooleanAllText": "全部", + "BooleanTrueText": "是", + "BooleanFalseText": "否", + "NumberStartValueLabelText": "開始數量", + "NumberEndValueLabelText": "截止數量" } } From b0997d6ff2a365d777a8d9f487b4d3f39a235743 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sat, 13 Jun 2026 08:18:36 +0800 Subject: [PATCH 08/30] fix(Table): use auto table layout when AllowResizing is enabled (#8113) * fix(Table): use auto table layout when AllowResizing is enabled (#8095) Revert table-layout-fixed condition to IsFixedHeader only so that tables with AllowResizing keep content-based initial column widths as before v10.6. Co-Authored-By: Claude Fable 5 * chore(Version): bump version to 10.7.2-beta03 Co-Authored-By: Claude Fable 5 --------- Co-authored-by: Claude Fable 5 --- src/BootstrapBlazor/BootstrapBlazor.csproj | 2 +- src/BootstrapBlazor/Components/Table/Table.razor.cs | 2 +- test/UnitTest/Components/TableTest.cs | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index 7614800c8cc..ae7fefb6236 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@  - 10.7.2-beta02 + 10.7.2-beta03 diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.cs b/src/BootstrapBlazor/Components/Table/Table.razor.cs index 0ec7891d254..71df5dbe23e 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.cs +++ b/src/BootstrapBlazor/Components/Table/Table.razor.cs @@ -90,7 +90,7 @@ public partial class Table : ITable, IModelEqualityComparer where .AddClass("table-excel", IsExcel) .AddClass("table-bordered", IsBordered) .AddClass("table-striped table-hover", IsStriped) - .AddClass("table-layout-fixed", IsFixedHeader || AllowResizing) + .AddClass("table-layout-fixed", IsFixedHeader) .AddClass("table-draggable", AllowDragColumn) .Build(); diff --git a/test/UnitTest/Components/TableTest.cs b/test/UnitTest/Components/TableTest.cs index e88e00e0eaf..b835c5361b8 100644 --- a/test/UnitTest/Components/TableTest.cs +++ b/test/UnitTest/Components/TableTest.cs @@ -1609,6 +1609,7 @@ public void IsFixedHeader_Ok() }); cut.Contains("table-fixed-header"); cut.Contains("height: 200px;"); + cut.Contains("table-layout-fixed"); } [Theory] @@ -1745,6 +1746,9 @@ public void AllowResizing_Ok(bool resizing) { cut.DoesNotContain("is-resizable"); } + + // 开启列宽调整时仍使用 auto 布局保持初始列宽与未开启时一致 + cut.DoesNotContain("table-layout-fixed"); } [Theory] From 781102a24fc5ab5a584fa25e2959f544e5d25651 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sun, 14 Jun 2026 09:21:20 +0800 Subject: [PATCH 09/30] feat(Utility): improve addLink method (#8116) --- src/BootstrapBlazor/wwwroot/modules/utility.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/BootstrapBlazor/wwwroot/modules/utility.js b/src/BootstrapBlazor/wwwroot/modules/utility.js index acbc216d330..7e8d4dd17b4 100644 --- a/src/BootstrapBlazor/wwwroot/modules/utility.js +++ b/src/BootstrapBlazor/wwwroot/modules/utility.js @@ -187,14 +187,22 @@ const addScript = content => { script.setAttribute('src', content) document.body.appendChild(script) script.onload = () => { - script.setAttribute('loaded', true) + script.setAttribute('loaded', 'true') + } + script.onerror = () => { + script.setAttribute('loaded', 'error') } } - return new Promise((resolve, reject) => { + return new Promise(resolve => { const handler = setInterval(() => { - const done = link[0].getAttribute('loaded') === 'true' - if (done) { + const state = link[0].getAttribute('loaded') + if (state === 'true') { + clearInterval(handler) + resolve() + } + else if (state === 'error') { clearInterval(handler) + console.error(`Failed to load script: ${content}`) resolve() } }, 20) From d651f63284b6897a0f9860e93be890eb71fb76c0 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Mon, 15 Jun 2026 09:39:08 +0800 Subject: [PATCH 10/30] feat(Utility): improve addLink method (#8118) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(Utility): improve addLink method * chore: bump version to 10.7.2-beta04 Co-Authored-By: Claude Opus 4.8 (1M context) * refactor: 增加超时策略逻辑 --------- Co-authored-by: Claude Opus 4.8 (1M context) --- src/BootstrapBlazor/BootstrapBlazor.csproj | 2 +- .../wwwroot/modules/utility.js | 33 ++++++++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index ae7fefb6236..cf3174b47e5 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@  - 10.7.2-beta03 + 10.7.2-beta04 diff --git a/src/BootstrapBlazor/wwwroot/modules/utility.js b/src/BootstrapBlazor/wwwroot/modules/utility.js index 7e8d4dd17b4..8f6da49166d 100644 --- a/src/BootstrapBlazor/wwwroot/modules/utility.js +++ b/src/BootstrapBlazor/wwwroot/modules/utility.js @@ -181,7 +181,9 @@ const addScript = content => { let link = scripts.filter(function(link) { return link.src.indexOf(url) > -1 }) + let created = false if (link.length === 0) { + created = true const script = document.createElement('script') link.push(script) script.setAttribute('src', content) @@ -194,6 +196,7 @@ const addScript = content => { } } return new Promise(resolve => { + let count = 0 const handler = setInterval(() => { const state = link[0].getAttribute('loaded') if (state === 'true') { @@ -205,6 +208,11 @@ const addScript = content => { console.error(`Failed to load script: ${content}`) resolve() } + else if (!created && ++count > 250) { + clearInterval(handler) + console.warn(`Timeout while loading script: ${content}`) + resolve() + } }, 20) }) } @@ -246,21 +254,38 @@ const addLink = (href, rel = "stylesheet") => { let link = links.filter(function(link) { return link.href.indexOf(url) > -1 }) + let created = false if (link.length === 0) { + created = true const css = document.createElement('link') link.push(css) css.setAttribute("rel", rel) css.setAttribute('href', href) document.getElementsByTagName("head")[0].appendChild(css) css.onload = () => { - css.setAttribute('loaded', true) + css.setAttribute('loaded', 'true') + } + css.onerror = () => { + css.setAttribute('loaded', 'error') } } - return new Promise((resolve, reject) => { + return new Promise(resolve => { + let count = 0 const handler = setInterval(() => { - const done = link[0].getAttribute('loaded') === 'true' - if (done) { + const el = link[0] + const state = el.getAttribute('loaded') + if (state === 'true' || el.sheet) { + clearInterval(handler) + resolve() + } + else if (state === 'error') { + clearInterval(handler) + console.error(`Failed to load stylesheet: ${href}`) + resolve() + } + else if (!created && ++count > 250) { clearInterval(handler) + console.warn(`Timeout while loading stylesheet: ${href}`) resolve() } }, 20) From ee73d095181834a00bfb32a4f158d44e855b0543 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Mon, 15 Jun 2026 13:55:49 +0800 Subject: [PATCH 11/30] feat(DockView): bump version 10.0.15 (#8120) --- src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj b/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj index a762b99012c..0118f30c752 100644 --- a/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj +++ b/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj @@ -34,7 +34,7 @@ - + From 5ed381eda371bc353175305a02b4cc04cf7c1227 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Tue, 16 Jun 2026 11:33:45 +0800 Subject: [PATCH 12/30] feat(DockView): bump version 10.0.16 (#8122) --- src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj b/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj index 0118f30c752..b1e3f7a5d0b 100644 --- a/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj +++ b/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj @@ -34,7 +34,7 @@ - + From 892e07093ba346e14499130826fd4a342525e5d4 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Tue, 16 Jun 2026 11:38:34 +0800 Subject: [PATCH 13/30] feat(Html2Pdf): bump version 10.0.9 (#8124) --- src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj b/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj index b1e3f7a5d0b..5e806b072e7 100644 --- a/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj +++ b/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj @@ -46,7 +46,7 @@ - + From f172166c8ef121ce6690836216ad96eab8b935a8 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 17 Jun 2026 01:40:11 +0800 Subject: [PATCH 14/30] feat(Table): add auto width function (#8126) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 增加客户端未设置列宽列自适应功能 * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Argo Zhang * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Argo Zhang * chore: bump version 10.7.2 --------- Signed-off-by: Argo Zhang Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/BootstrapBlazor/BootstrapBlazor.csproj | 2 +- .../Components/Table/Table.razor.Edit.cs | 6 ++ .../Components/Table/Table.razor.cs | 5 + .../Components/Table/Table.razor.js | 96 +++++++++++++++++-- 4 files changed, 98 insertions(+), 11 deletions(-) diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index cf3174b47e5..78417ff06e1 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@  - 10.7.2-beta04 + 10.7.2 diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.Edit.cs b/src/BootstrapBlazor/Components/Table/Table.razor.Edit.cs index 33a9ee11886..6d916cfa45d 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.Edit.cs +++ b/src/BootstrapBlazor/Components/Table/Table.razor.Edit.cs @@ -516,6 +516,12 @@ private async Task QueryAsync(bool shouldRender, int? pageIndex = null, bool tri if (shouldRender) { + // 数据渲染后触发脚本测量列内容最小宽度 用于固定表头(table-layout:fixed)窄容器下横向滚动兜底 + if (IsFixedHeader) + { + _measureColumnMinWidth = true; + _invoke = true; + } StateHasChanged(); } } diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.cs b/src/BootstrapBlazor/Components/Table/Table.razor.cs index 71df5dbe23e..a7e6d6dfd04 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.cs +++ b/src/BootstrapBlazor/Components/Table/Table.razor.cs @@ -522,6 +522,7 @@ private string GetSortTooltip(ITableColumn col) => SortName != col.GetFieldName( private bool _resetTable; private bool _resetColumnListPopover; private bool _resetColumns; + private bool _measureColumnMinWidth; private bool _updateSortTooltip; private bool _shouldScrollTop; private bool _invoke; @@ -1151,6 +1152,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) var resetColumnListPopover = _resetColumnListPopover; var resetTable = _resetTable; var resetColumns = _resetColumns; + var measureColumnMinWidth = _measureColumnMinWidth; var updateSortTooltip = _updateSortTooltip; var scrollToTop = _shouldScrollTop; @@ -1158,6 +1160,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) _resetColumnListPopover = false; _resetTable = false; _resetColumns = false; + _measureColumnMinWidth = false; _updateSortTooltip = false; _shouldScrollTop = false; @@ -1167,6 +1170,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) ResetColumnListPopover = resetColumnListPopover, ResetTable = resetTable, ResetColumns = resetColumns, + MeasureColumnMinWidth = measureColumnMinWidth, ColumnStates = _tableColumnStates, AllowDragColumn, UpdateSortTooltip = updateSortTooltip, @@ -1468,6 +1472,7 @@ private async Task OnTableRenderAsync(bool firstRender) FitColumnWidthIncludeHeader, ResizeColumnCallback = nameof(ResizeColumnCallback), ColumnMinWidth = ColumnMinWidth ?? Options.CurrentValue.TableSettings.ColumnMinWidth, + ColumnStates = _tableColumnStates, ScrollWidth = ActualScrollWidth, ShowColumnWidthTooltip, ColumnWidthTooltipPrefix, diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.js b/src/BootstrapBlazor/Components/Table/Table.razor.js index 0de8d1c639b..93d0c87c25c 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.js +++ b/src/BootstrapBlazor/Components/Table/Table.razor.js @@ -26,6 +26,16 @@ export async function reset(id) { return; } + if (table.minWidthObserver) { + table.minWidthObserver.disconnect(); + table.minWidthObserver = null; + } + if (table.minWidthRaf) { + cancelAnimationFrame(table.minWidthRaf); + table.minWidthRaf = null; + } + table.autoColumns = []; + table.columns = []; table.tables = []; table.dragColumns = []; @@ -51,6 +61,19 @@ export async function reset(id) { const left = table.body.scrollLeft table.thead.scrollTo(left, 0) }); + + if (window.ResizeObserver) { + table.minWidthObserver = new ResizeObserver(() => { + if (table.minWidthRaf) { + cancelAnimationFrame(table.minWidthRaf); + } + table.minWidthRaf = requestAnimationFrame(() => { + table.minWidthRaf = null; + applyColumnMinWidth(table); + }); + }); + table.minWidthObserver.observe(table.body); + } } else { table.isExcel = shim.firstElementChild.classList.contains('table-excel') @@ -91,6 +114,10 @@ export async function reset(id) { table.loopCheckHeightHandler = requestAnimationFrame(() => check(table)); return; } + + if (table.thead) { + setColSize(table, table.options); + } } export async function switchCardView(id) { @@ -113,6 +140,13 @@ const destroyTable = table => { if (table.loopCheckHeightHandler) { cancelAnimationFrame(table.loopCheckHeightHandler); } + if (table.minWidthObserver) { + table.minWidthObserver.disconnect(); + table.minWidthObserver = null; + } + if (table.minWidthRaf) { + cancelAnimationFrame(table.minWidthRaf); + } if (table.thead) { EventHandler.off(table.body, 'scroll') } @@ -1075,6 +1109,9 @@ export async function updateTableState(id, options) { if (options.resetColumns) { resetColumns(table, options); } + else if (options.measureColumnMinWidth) { + setColSize(table, options); + } if (options.resetColumnListPopover) { resetColumnListPopover(table); @@ -1134,18 +1171,57 @@ const resetColumns = (table, options) => { } const setColSize = (table, options) => { - var zeroWidthColumns = options.columnStates.filter(i => i.width === null && i.visible === true); - zeroWidthColumns.forEach(col => { - const headerCollection = [...table.tables[0].querySelectorAll('thead > tr > th')]; + if (!table.tables || table.tables.length === 0) { + return; + } + const headerCollection = [...table.tables[0].querySelectorAll('thead > tr > th')]; + const columnMinWidth = table.options.columnMinWidth || 0; + const autoColumns = []; + options.columnStates.forEach(col => { + if (col.width != null || col.visible === false) { + return; + } const th = headerCollection.find(i => i.getAttribute('data-bb-field') === col.name); - if (th.offsetWidth === 0) { - const width = getCellWidth(th); - const colIndex = headerCollection.indexOf(th); - col.width = Math.max(width, getColumnMaxCellWidth(table, colIndex)); - table.tables.forEach(table => { - table.querySelectorAll('colgroup col')[colIndex].style.width = `${col.width}px`; - }); + if (th === void 0) { + return; } + const colIndex = headerCollection.indexOf(th); + const minWidth = Math.max(getCellWidth(th) + getHeaderIconsWidth(th), getColumnMaxCellWidth(table, colIndex), columnMinWidth) | 0; + autoColumns.push({ colIndex, minWidth }); + }); + table.autoColumns = autoColumns; + applyColumnMinWidth(table); +} + +const getHeaderIconsWidth = th => { + let width = 0; + th.querySelectorAll('.sort-icon, .filter-icon, .toolbox-icon, .col-copy').forEach(icon => { + width += getWidth(icon); + }); + return width; +} + +const applyColumnMinWidth = table => { + if (!table.thead || !table.body || !table.autoColumns || table.autoColumns.length === 0) { + return; + } + setAutoColWidths(table, true); + const compact = table.body.scrollWidth > table.body.clientWidth + 1; + if (!compact) { + setAutoColWidths(table, false); + } +} + +const setAutoColWidths = (table, apply) => { + const colgroups = table.tables.map(t => t.querySelectorAll('colgroup col')); + table.autoColumns.forEach(({ colIndex, minWidth }) => { + const width = apply ? `${minWidth}px` : ''; + colgroups.forEach(cols => { + const colEl = cols[colIndex]; + if (colEl) { + colEl.style.width = width; + } + }); }); } From db8e375b3ef0a0c8755e83ed8a79413f8e8f5bd4 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 17 Jun 2026 19:36:42 +0800 Subject: [PATCH 15/30] feat(DockView): bump version 10.0.17 (#8128) --- src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj b/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj index 5e806b072e7..4adfad808fc 100644 --- a/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj +++ b/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj @@ -34,7 +34,7 @@ - + From 67f07be019b144d98f1782b0069c4e6c2e03cd99 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 18 Jun 2026 21:20:55 +0800 Subject: [PATCH 16/30] chore(LLMs): remove LLMs auto docs (#8130) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 移除 llms 工具 * chore: 删除 llms 相关脚本 * refactor: 重构自动化脚本 --- dotnet-tools.json | 7 ------ .../Directory.Build.targets | 22 +++++++------------ 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/dotnet-tools.json b/dotnet-tools.json index e56323b0952..8cc47019440 100644 --- a/dotnet-tools.json +++ b/dotnet-tools.json @@ -8,13 +8,6 @@ "css-bundler" ], "rollForward": false - }, - "bootstrapblazor.llmsdocsgenerator": { - "version": "10.0.2", - "commands": [ - "llms-docs" - ], - "rollForward": false } } } \ No newline at end of file diff --git a/src/BootstrapBlazor.Server/Directory.Build.targets b/src/BootstrapBlazor.Server/Directory.Build.targets index 124d0433260..38faf0eb713 100644 --- a/src/BootstrapBlazor.Server/Directory.Build.targets +++ b/src/BootstrapBlazor.Server/Directory.Build.targets @@ -1,25 +1,19 @@ - - - - - - - + - - - - - - + - + + + + + + From 4c525aefa89ac8a0d692326bdbccfd430b1a7980 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 18 Jun 2026 21:32:07 +0800 Subject: [PATCH 17/30] feat(DockView): bump version 10.0.18 (#8132) --- src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj b/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj index 4adfad808fc..2402bf14b76 100644 --- a/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj +++ b/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj @@ -34,7 +34,7 @@ - + From d811fa9f432838f3861385cb98f8f23bf856357e Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 19 Jun 2026 09:43:32 +0800 Subject: [PATCH 18/30] feat(UniverSheet): bump version 10.0.14 (#8134) --- src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj b/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj index 2402bf14b76..c56c95f7ded 100644 --- a/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj +++ b/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj @@ -81,7 +81,7 @@ - + From fc2fa3da7b208e6eb0fe7bfb50c0f9f3e4145f0a Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sat, 20 Jun 2026 18:08:34 +0800 Subject: [PATCH 19/30] chore(LLMs): add LLMsDocsGenerator tool (#8136) --- dotnet-tools.json | 7 +++++++ src/BootstrapBlazor.Server/Directory.Build.targets | 10 ++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/dotnet-tools.json b/dotnet-tools.json index 8cc47019440..e56323b0952 100644 --- a/dotnet-tools.json +++ b/dotnet-tools.json @@ -8,6 +8,13 @@ "css-bundler" ], "rollForward": false + }, + "bootstrapblazor.llmsdocsgenerator": { + "version": "10.0.2", + "commands": [ + "llms-docs" + ], + "rollForward": false } } } \ No newline at end of file diff --git a/src/BootstrapBlazor.Server/Directory.Build.targets b/src/BootstrapBlazor.Server/Directory.Build.targets index 38faf0eb713..722564f0ce9 100644 --- a/src/BootstrapBlazor.Server/Directory.Build.targets +++ b/src/BootstrapBlazor.Server/Directory.Build.targets @@ -11,8 +11,14 @@ - - + + + + + + + + From 3aeb67c81a2e6829c1d0a54498ddcb237ea70fa4 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sun, 21 Jun 2026 15:05:55 +0800 Subject: [PATCH 20/30] doc(NavMenu): update component category (#8138) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * doc: 调整键值与组件名称一致 * refactor: 更改图标键值 * doc: 更新标签键值 --- .../Extensions/MenusLocalizerExtensions.cs | 14 +++++++------- src/BootstrapBlazor.Server/Locales/en-US.json | 14 +++++++------- src/BootstrapBlazor.Server/Locales/zh-CN.json | 14 +++++++------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs b/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs index c4c95aceafa..3c0d497349d 100644 --- a/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs +++ b/src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs @@ -321,7 +321,7 @@ void AddQuickStar(DemoMenuItem item) }, new() { - Text = Localizer["Labels"], + Text = Localizer["BootstrapLabel"], Url = "label" }, new() @@ -437,22 +437,22 @@ void AddForm(DemoMenuItem item) new() { Match = NavLinkMatch.All, - Text = Localizer["Input"], + Text = Localizer["BootstrapInput"], Url = "input" }, new() { - Text = Localizer["InputNumber"], + Text = Localizer["BootstrapInputNumber"], Url = "input-number" }, new() { - Text = Localizer["InputGroup"], + Text = Localizer["BootstrapInputGroup"], Url = "input-group" }, new() { - Text = Localizer["Ip"], + Text = Localizer["IpAddress"], Url = "ip" }, new() @@ -751,7 +751,7 @@ void AddData(DemoMenuItem item) }, new() { - Text = Localizer["Icon"], + Text = Localizer["BootstrapBlazorIcon"], Url = "icon" }, new() @@ -1265,7 +1265,7 @@ void AddNotice(DemoMenuItem item) }, new() { - Text = Localizer["Console"], + Text = Localizer["ConsoleLogger"], Url = "console" }, new() diff --git a/src/BootstrapBlazor.Server/Locales/en-US.json b/src/BootstrapBlazor.Server/Locales/en-US.json index b09fbe5fc01..23597e1d4b5 100644 --- a/src/BootstrapBlazor.Server/Locales/en-US.json +++ b/src/BootstrapBlazor.Server/Locales/en-US.json @@ -200,7 +200,7 @@ "Collapse": "Collapse", "ColorPicker": "ColorPicker", "ConnectionService": "ConnectionService", - "Console": "Console", + "ConsoleLogger": "ConsoleLogger", "ContextMenu": "ContextMenu", "CountButton": "CountButton", "CountUp": "CountUp", @@ -278,23 +278,23 @@ "Html2Image": "IHtml2Image", "Html2Pdf": "IHtml2Pdf", "HtmlRenderer": "HtmlRenderer", - "Icon": "Icon", + "BootstrapBlazorIcon": "Icon", "IconPark": "ByteDance IconPark", "Icons": "Icons", "IFrame": "IFrame", "ImageCropper": "ImageCropper", "ImageViewer": "ImageViewer", - "Input": "Input", - "InputGroup": "InputGroup", - "InputNumber": "InputNumber", + "BootstrapInput": "BootstrapInput", + "BootstrapInputGroup": "BootstrapInputGroup", + "BootstrapInputNumber": "BootstrapInputNumber", "InputUpload": "InputUpload", "Install": "Install", "IntersectionObserver": "IntersectionObserver", "Introduction": "Introduction", - "Ip": "IpAddress", + "IpAddress": "IpAddress", "JitViewer": "Jit Viewer", "JSExtension": "JSRuntime Extensions", - "Labels": "Labels", + "BootstrapLabel": "Labels", "Layout": "Layout", "LayoutComponents": "Layouts", "LayoutPage": "Admin Template", diff --git a/src/BootstrapBlazor.Server/Locales/zh-CN.json b/src/BootstrapBlazor.Server/Locales/zh-CN.json index a73b1138679..7450034e10f 100644 --- a/src/BootstrapBlazor.Server/Locales/zh-CN.json +++ b/src/BootstrapBlazor.Server/Locales/zh-CN.json @@ -200,7 +200,7 @@ "Collapse": "折叠 Collapse", "ColorPicker": "颜色拾取器 ColorPicker", "ConnectionService": "在线连接服务 ConnectionService", - "Console": "控制台 Console", + "ConsoleLogger": "控制台 ConsoleLogger", "ContextMenu": "右键菜单 ContextMenu", "CountButton": "倒计时按钮 CountButton", "CountUp": "计数器 CountUp", @@ -278,23 +278,23 @@ "Html2Image": "Html 转 Image IHtml2Image", "Html2Pdf": "Html 转 Pdf IHtml2Pdf", "HtmlRenderer": "Html 转换器 HtmlRenderer", - "Icon": "图标 Icon", + "BootstrapBlazorIcon": "图标 Icon", "IconPark": "字节跳动图标 IconPark", "Icons": "内置图标", "IFrame": "内嵌框架 IFrame", "ImageCropper": "图像裁剪 ImageCropper", "ImageViewer": "图片 ImageViewer", - "Input": "输入框 Input", - "InputGroup": "输入组 InputGroup", - "InputNumber": "数字框 InputNumber", + "BootstrapInput": "输入框 BootstrapInput", + "BootstrapInputGroup": "输入组 BootstrapInputGroup", + "BootstrapInputNumber": "数字框 BootstrapInputNumber", "InputUpload": "上传组件 InputUpload", "Install": "安装", "IntersectionObserver": "交叉观察者 IntersectionObserver", "Introduction": "简介", - "Ip": "IP 地址 IpAddress", + "IpAddress": "IP 地址 IpAddress", "JitViewer": "文件预览器 JitViewer", "JSExtension": "JSRuntime 扩展", - "Labels": "表单标签", + "BootstrapLabel": "表单标签", "Layout": "布局组件 Layout", "LayoutComponents": "布局组件", "LayoutPage": "后台模拟器", From 9f70b5f60f745be14e206d8d1061d0b4e5eb501f Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sun, 21 Jun 2026 15:07:00 +0800 Subject: [PATCH 21/30] chore(Docs): update docs.json configuration file (#8140) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * doc: 调整键值与组件名称一致 * refactor: 更改图标键值 * doc: 更新标签键值 * doc: 更新分类设置 * doc: 更新排序 * doc: 增加服务列表 * doc: 更新组件分类 --- src/BootstrapBlazor.Server/docs.json | 287 ++++++++++++++++++++++++++- 1 file changed, 286 insertions(+), 1 deletion(-) diff --git a/src/BootstrapBlazor.Server/docs.json b/src/BootstrapBlazor.Server/docs.json index 1568c92d9db..79ce1614109 100644 --- a/src/BootstrapBlazor.Server/docs.json +++ b/src/BootstrapBlazor.Server/docs.json @@ -334,5 +334,290 @@ "AtomUI": "https://qinware.com/?from=blazor.zone", "SAPHP": "https://www.swiftadmin.net/?from=blazor.zone", "Veterinary Hospital": "http://animal.jucun.zone/?from=blazor.zone" - } + }, + "category": { + "基类": [ + "BootstrapComponentBase", + "BootstrapModuleComponentBase", + "IdComponentBase" + ], + "工具类组件": [ + "ReconnectorContent", + "RenderTemplate", + "Script", + "ThemeProvider" + ], + "布局组件": [ + "BootstrapBlazorRoot", + "Divider", + "Footer", + "Layout", + "LayoutSplitBar", + "Navbar", + "Row", + "Scroll", + "Split", + "Stack", + "Toolbar", + "ToolbarButtonGroup", + "ToolbarItem", + "ToolbarSeparator", + "ToolbarSpace" + ], + "导航组件": [ + "Anchor", + "AnchorLink", + "Breadcrumb", + "Dropdown", + "DropdownItem", + "GotoNavigator", + "GoTop", + "Logout", + "Menu", + "MenuLink", + "Nav", + "NavbarBrand", + "NavbarCollapse", + "NavbarDropdown", + "NavbarDropdownItem", + "NavbarGroup", + "NavbarItem", + "NavbarLink", + "NavbarToggleButton", + "Pagination", + "PaginationItem", + "RibbonTab", + "RibbonTabHeader", + "SideMenu", + "Step", + "SubCascader", + "SubMenu", + "Tab", + "TabLink", + "TabToolbarButton", + "TopMenu" + ], + "表单组件": [ + "AutoComplete", + "AutoFill", + "BootstrapInput", + "BootstrapInputBase", + "BootstrapInputEventBase", + "BootstrapInputGroup", + "BootstrapInputGroupIcon", + "BootstrapInputNumber", + "BootstrapInputNumberBase", + "Button", + "ButtonBase", + "Cascader", + "Checkbox", + "CheckboxList", + "CheckboxListGeneric", + "ClockPicker", + "ColorPicker", + "DatePickerBody", + "DatePickerCell", + "DateTimePicker", + "DateTimeRange", + "EditorForm", + "FloatingLabel", + "IpAddress", + "ListGroup", + "MultiSelect", + "MultiSelectGeneric", + "NullSwitch", + "OtpInput", + "PopoverCompleteBase", + "PopoverDropdownBase", + "PopoverSelectBase", + "PulseButton", + "Radio", + "RadioList", + "RadioListGeneric", + "Rate", + "SearchForm", + "Select", + "SelectBase", + "SelectGeneric", + "SelectObject", + "SelectTable", + "SelectTree", + "SimpleSelectBase", + "Slider", + "Switch", + "Textarea", + "TimePicker", + "TimePickerCell", + "Toggle", + "ToggleBase", + "ToggleButton", + "TooltipWrapperBase", + "Transfer", + "TransferPanel", + "ValidateBase", + "ValidateForm" + ], + "上传组件": [ + "AvatarUpload", + "ButtonUpload", + "CardUpload", + "DropUpload", + "FileListUploadBase", + "InputUpload", + "UploadBase", + "UploadPreviewList" + ], + "表格组件": [ + "Table", + "TableAdvancedSortDialog", + "TableExtensionButton", + "TableFooterCell", + "TableToolbar" + ], + "数据组件": [ + "Affix", + "Avatar", + "Badge", + "BootstrapBlazorIcon", + "BootstrapLabel", + "BootstrapLabelSetting", + "Calendar", + "Camera", + "Captcha", + "Card", + "Carousel", + "Circle", + "CircleBase", + "Collapse", + "CountUp", + "Display", + "DisplayBase", + "DropdownWidget", + "Empty", + "FileIcon", + "GroupBox", + "Handwritten", + "IFrame", + "ImagePreviewer", + "ImageViewer", + "Link", + "ListView", + "LoadMore", + "LogoutLink", + "PrintButton", + "QueryBuilder", + "QueryGroup", + "Repeater", + "Search", + "Segmented", + "ShieldBadge", + "SpeechWave", + "SvgIcon", + "SwitchButton", + "Tag", + "Timeline", + "Transition", + "Tree", + "TreeView", + "TreeViewRow", + "TreeViewToolbarEditButton", + "Typed", + "Waterfall", + "Watermark" + ], + "通知组件": [ + "Alert", + "AlertBase", + "Console", + "ConsoleLogger", + "ContextMenu", + "ContextMenuZone", + "DialButton", + "Dialog", + "DialogBase", + "Drawer", + "Dropzone", + "EditDialog", + "FlipClock", + "FullScreenButton", + "IconDialog", + "IntersectionObserver", + "IntersectionObserverItem", + "Light", + "Marquee", + "Mask", + "Message", + "Modal", + "ModalDialog", + "NetworkMonitorIndicator", + "PopConfirmButton", + "PopConfirmButtonBase", + "PopConfirmButtonContent", + "Popover", + "Progress", + "ResultDialogFooter", + "SearchDialog", + "SlideButton", + "Spinner", + "SweetAlert", + "SweetAlertBody", + "Timer", + "Toast", + "ToastContainer", + "Tooltip" + ], + "过滤器组件": [ + "BoolFilter", + "DateTimeFilter", + "EnumFilter", + "FilterBase", + "FilterButton", + "FilterLogicItem", + "FilterProvider", + "LookupFilter", + "MultiFilter", + "MultipleFilterBase", + "MultiSelectFilter", + "NotSupportFilter", + "NumberFilter", + "StringFilter", + "TableColumnFilter" + ], + "骨架屏组件": [ + "SkeletonAvatar", + "SkeletonBase", + "SkeletonTable", + "SkeletonTree" + ] + }, + "services": [ + "AjaxService", + "ClipboardService", + "DialogService", + "DownloadService", + "DrawerService", + "EyeDropperService", + "FullScreenService", + "IBrowserFingerService", + "IConnectionService", + "IGeoLocationService", + "IHtml2Image", + "IHtml2Pdf", + "ILookupService", + "INetworkMonitorService", + "IPinyinService", + "ISerialService", + "ITotpService", + "IVersionService", + "IZipArchiveService", + "MaskService", + "MessageService", + "NotificationService", + "PrintService", + "SwalService", + "TitleService", + "ToastService", + "WebClientService", + "WebSpeechService" + ] } From 10991e1a795e58592c01f4d606fe9869ad4c8767 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Mon, 22 Jun 2026 09:19:45 +0800 Subject: [PATCH 22/30] doc(Docs): update extensions components/services (#8142) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * doc: 增加扩展组件与服务配置项 * doc: 补扩展服务 --- src/BootstrapBlazor.Server/docs.json | 228 ++++++++++++++++++++++++++- 1 file changed, 227 insertions(+), 1 deletion(-) diff --git a/src/BootstrapBlazor.Server/docs.json b/src/BootstrapBlazor.Server/docs.json index 79ce1614109..c242d552cac 100644 --- a/src/BootstrapBlazor.Server/docs.json +++ b/src/BootstrapBlazor.Server/docs.json @@ -598,17 +598,25 @@ "DrawerService", "EyeDropperService", "FullScreenService", + "IAudioDevice", + "IBluetooth", "IBrowserFingerService", + "ICalendarFestivals", + "ICalendarHolidays", + "IComponentHtmlRenderer", "IConnectionService", "IGeoLocationService", "IHtml2Image", "IHtml2Pdf", + "IIpLocatorProvider", "ILookupService", "INetworkMonitorService", "IPinyinService", "ISerialService", + "ITableExport", "ITotpService", "IVersionService", + "IVideoDevice", "IZipArchiveService", "MaskService", "MessageService", @@ -619,5 +627,223 @@ "ToastService", "WebClientService", "WebSpeechService" - ] + ], + "extensions": { + "BootstrapBlazor.AntDesignIcon": { + "components": ["AntDesignIcon"], + "services": [] + }, + "BootstrapBlazor.AzureOpenAI": { + "components": [], + "services": ["IAzureOpenAIService"] + }, + "BootstrapBlazor.AzureTranslator": { + "components": [], + "services": ["IAzureTranslatorService"] + }, + "BootstrapBlazor.BaiduOcr": { + "components": [], + "services": ["IBaiduOcr"] + }, + "BootstrapBlazor.BarCode": { + "components": ["BarcodeReader", "QRCode"], + "services": [] + }, + "BootstrapBlazor.BarcodeGenerator": { + "components": ["BarcodeGenerator"], + "services": [] + }, + "BootstrapBlazor.Chart": { + "components": ["Chart"], + "services": [] + }, + "BootstrapBlazor.ChatBot": { + "components": ["MarkdownContent"], + "services": [] + }, + "BootstrapBlazor.CherryMarkdown": { + "components": ["CherryMarkdown"], + "services": [] + }, + "BootstrapBlazor.CodeEditor": { + "components": ["CodeEditor"], + "services": [] + }, + "BootstrapBlazor.Dock": { + "components": ["DockComponent", "DockView"], + "services": [] + }, + "BootstrapBlazor.DockView": { + "components": ["DockViewComponent", "DockViewDropdownIcon", "DockViewIcon", "DockViewTitleBar", "DockViewV2"], + "services": [] + }, + "BootstrapBlazor.Dom2Image": { + "components": [], + "services": ["IDom2ImageService"] + }, + "BootstrapBlazor.DriverJs": { + "components": ["DriverJs"], + "services": [] + }, + "BootstrapBlazor.ElementIcon": { + "components": ["ElementIcon"], + "services": [] + }, + "BootstrapBlazor.EmbedPDF": { + "components": ["EmbedPDF"], + "services": [] + }, + "BootstrapBlazor.FileViewer": { + "components": ["FileViewer"], + "services": [] + }, + "BootstrapBlazor.Gantt": { + "components": ["Gantt"], + "services": [] + }, + "BootstrapBlazor.HikVision": { + "components": ["HikVisionWebPlugin"], + "services": [] + }, + "BootstrapBlazor.Html2Image": { + "components": [], + "services": ["IHtml2Image"] + }, + "BootstrapBlazor.Html2Pdf": { + "components": [], + "services": ["IHtml2Pdf"] + }, + "BootstrapBlazor.IconPark": { + "components": ["ByteDanceIcon"], + "services": [] + }, + "BootstrapBlazor.ImageCropper": { + "components": ["ImageCropper"], + "services": [] + }, + "BootstrapBlazor.JitsiMeet": { + "components": ["Meet"], + "services": [] + }, + "BootstrapBlazor.JitViewer": { + "components": ["JitViewer"], + "services": [] + }, + "BootstrapBlazor.Live2DDisplay": { + "components": ["Live2DDisplay"], + "services": [] + }, + "BootstrapBlazor.Markdown": { + "components": ["Markdown"], + "services": [] + }, + "BootstrapBlazor.MeiliSearch": { + "components": ["MeiliSearchBox"], + "services": [] + }, + "BootstrapBlazor.Mermaid": { + "components": ["Mermaid"], + "services": [] + }, + "BootstrapBlazor.MindMap": { + "components": ["MindMap"], + "services": [] + }, + "BootstrapBlazor.MouseFollower": { + "components": ["MouseFollower"], + "services": [] + }, + "BootstrapBlazor.OctIcon": { + "components": ["OctIcon"], + "services": [] + }, + "BootstrapBlazor.OfficeViewer": { + "components": ["OfficeViewer"], + "services": [] + }, + "BootstrapBlazor.OnScreenKeyboard": { + "components": ["OnScreenKeyboard"], + "services": [] + }, + "BootstrapBlazor.OpcDa": { + "components": [], + "services": ["IOpcDaServer", "IOpcItem", "IOpcSubscription"] + }, + "BootstrapBlazor.PdfReader": { + "components": ["PdfReader"], + "services": [] + }, + "BootstrapBlazor.PdfViewer": { + "components": ["PdfViewer"], + "services": [] + }, + "BootstrapBlazor.Player": { + "components": ["Player"], + "services": [] + }, + "BootstrapBlazor.RDKit": { + "components": ["RDKit"], + "services": [] + }, + "BootstrapBlazor.Region": { + "components": ["SelectCity", "SelectProvince", "SelectRegion"], + "services": ["IRegionService"] + }, + "BootstrapBlazor.SignaturePad": { + "components": ["SignaturePad"], + "services": [] + }, + "BootstrapBlazor.SmilesDrawer": { + "components": ["SmilesDrawer"], + "services": [] + }, + "BootstrapBlazor.Sortable": { + "components": ["SortableList"], + "services": [] + }, + "BootstrapBlazor.Splitting": { + "components": ["Splitting"], + "services": [] + }, + "BootstrapBlazor.SummerNote": { + "components": ["Editor"], + "services": [] + }, + "BootstrapBlazor.SvgEditor": { + "components": ["SvgEditor"], + "services": [] + }, + "BootstrapBlazor.Tasks.Dashboard": { + "components": ["TaskDashboard", "TaskInfo"], + "services": [] + }, + "BootstrapBlazor.Term": { + "components": ["Term"], + "services": [] + }, + "BootstrapBlazor.Topology": { + "components": ["Topology"], + "services": [] + }, + "BootstrapBlazor.UniverIcon": { + "components": ["UniverIcon"], + "services": [] + }, + "BootstrapBlazor.UniverSheet": { + "components": ["UniverSheet"], + "services": [] + }, + "BootstrapBlazor.Vditor": { + "components": ["Vditor"], + "services": [] + }, + "BootstrapBlazor.VideoPlayer": { + "components": ["VideoPlayer"], + "services": [] + }, + "BootstrapBlazor.WinBox": { + "components": ["WinBox"], + "services": ["WinBoxService"] + } + } } From f44940cd963351743965b58649a8ced58716ad28 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Mon, 22 Jun 2026 09:38:03 +0800 Subject: [PATCH 23/30] chore(LLMs): bump version 10.0.3 (#8144) --- dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet-tools.json b/dotnet-tools.json index e56323b0952..a54cec9c9af 100644 --- a/dotnet-tools.json +++ b/dotnet-tools.json @@ -10,7 +10,7 @@ "rollForward": false }, "bootstrapblazor.llmsdocsgenerator": { - "version": "10.0.2", + "version": "10.0.3", "commands": [ "llms-docs" ], From 9b777c4fb4ac90b3a2a6e304c81a172fa4e57344 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Mon, 22 Jun 2026 19:38:01 +0800 Subject: [PATCH 24/30] fix(AutoComplete): skip filter when set SkipMatch to true (#8149) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 修复 AutoComplete SkipMatch 选中候选项后不生效问题 * test: 更新单元测试 * chore: bump version 10.7.3-beta01 * refactor: 精简代码 * chore: bump version 10.7.3-beta02 * chore: bump version 10.7.3-beta01 --- src/BootstrapBlazor/BootstrapBlazor.csproj | 2 +- .../AutoComplete/AutoComplete.razor.cs | 5 +++++ test/UnitTest/Components/AutoCompleteTest.cs | 17 +++++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index 78417ff06e1..3173192f198 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@  - 10.7.2 + 10.7.3-beta01 diff --git a/src/BootstrapBlazor/Components/AutoComplete/AutoComplete.razor.cs b/src/BootstrapBlazor/Components/AutoComplete/AutoComplete.razor.cs index 8b4d1e4c442..14915742646 100644 --- a/src/BootstrapBlazor/Components/AutoComplete/AutoComplete.razor.cs +++ b/src/BootstrapBlazor/Components/AutoComplete/AutoComplete.razor.cs @@ -237,6 +237,11 @@ public async Task TriggerClear() [JSInvokable] public async Task TriggerFilter(string val) { + if (SkipMatch) + { + return; + } + if (OnCustomFilter != null) { var items = await OnCustomFilter(val); diff --git a/test/UnitTest/Components/AutoCompleteTest.cs b/test/UnitTest/Components/AutoCompleteTest.cs index fd38893bba3..2c5c953183a 100644 --- a/test/UnitTest/Components/AutoCompleteTest.cs +++ b/test/UnitTest/Components/AutoCompleteTest.cs @@ -23,6 +23,23 @@ public void SkipMatch_Ok() Assert.Equal(4, items.Count); } + [Fact] + public async Task SkipMatch_AfterSelect_Ok() + { + var cut = Context.Render(pb => + { + pb.Add(a => a.SkipMatch, true); + pb.Add(a => a.Items, new List() { "test1", "test12", "test123", "test1234" }); + }); + + // 选中候选项 test123 后 TriggerFilter 不应过滤候选项 + // 否则再次输入时 SkipMatch 失效 候选项被锁定为上次选中值的过滤子集 + await cut.InvokeAsync(() => cut.FindAll(".dropdown-item")[2].Click()); + + var items = cut.FindAll(".dropdown-item"); + Assert.Equal(4, items.Count); + } + [Fact] public void Items_Ok() { From e2a8ed418b01a4400c22fac0a892a6a18b57f5d3 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Tue, 23 Jun 2026 14:35:35 +0800 Subject: [PATCH 25/30] feat(DockView): make IsLock parameter work event set ShowLock to false (#8151) bump version 10.0.19 --- src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj b/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj index c56c95f7ded..5ee9e9ff11b 100644 --- a/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj +++ b/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj @@ -34,7 +34,7 @@ - + From 2023878d583b28780bc6a3ea22b47c271156e994 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 24 Jun 2026 11:35:57 +0800 Subject: [PATCH 26/30] doc(SvgIcon): update Href parameter doc (#8153) --- src/BootstrapBlazor/Components/Icon/SvgIcon.razor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BootstrapBlazor/Components/Icon/SvgIcon.razor.cs b/src/BootstrapBlazor/Components/Icon/SvgIcon.razor.cs index 4728d9a60b9..1d61cdb98ee 100644 --- a/src/BootstrapBlazor/Components/Icon/SvgIcon.razor.cs +++ b/src/BootstrapBlazor/Components/Icon/SvgIcon.razor.cs @@ -20,8 +20,8 @@ public partial class SvgIcon public string? Name { get; set; } /// - /// 获得 图标地址 - /// Get Icon URL + /// 获得/设置 图标地址,默认 null 未设置,内部使用 BootstrapBlazor.IconPark 包中提供的图标 + /// Gets or sets Icon URL, default is null, internal use BootstrapBlazor.IconPark package provided icons /// [Parameter, NotNull] public string? Href { get; set; } From 81d18e0d189a3dff15064fbc63c338a347169596 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 24 Jun 2026 12:41:32 +0800 Subject: [PATCH 27/30] feat(SvgIcon): add custom class support (#8155) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 增加样式支持 * test: 补充单元测试 --- src/BootstrapBlazor/Components/Icon/SvgIcon.razor.cs | 1 + test/UnitTest/Components/SvgIconTest.cs | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/src/BootstrapBlazor/Components/Icon/SvgIcon.razor.cs b/src/BootstrapBlazor/Components/Icon/SvgIcon.razor.cs index 1d61cdb98ee..8bb0db492cf 100644 --- a/src/BootstrapBlazor/Components/Icon/SvgIcon.razor.cs +++ b/src/BootstrapBlazor/Components/Icon/SvgIcon.razor.cs @@ -32,6 +32,7 @@ public partial class SvgIcon /// private string? ClassString => CssBuilder.Default("bb-svg-icon") .AddClass($"bb-svg-icon-{Name}", !string.IsNullOrEmpty(Name)) + .AddClassFromAttributes(AdditionalAttributes) .Build(); private string? _hrefString; diff --git a/test/UnitTest/Components/SvgIconTest.cs b/test/UnitTest/Components/SvgIconTest.cs index 9cb226ee9dd..7e889c6c51f 100644 --- a/test/UnitTest/Components/SvgIconTest.cs +++ b/test/UnitTest/Components/SvgIconTest.cs @@ -25,4 +25,15 @@ public void SvgIcon_Ok() }); cut.MarkupMatches("
"); } + + [Fact] + public void CustomClass_Ok() + { + var cut = Context.Render(pb => + { + pb.Add(a => a.Name, "test-icon"); + pb.AddUnmatched("class", "custom-class"); + }); + cut.MarkupMatches("
"); + } } From 3cb9f711a0fafb73ce43fe22e028705196d5b505 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 24 Jun 2026 12:59:30 +0800 Subject: [PATCH 28/30] feat(TreeViewItem): add IconTemplate parameter (#8157) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(TreeNodeBase): 增加 svg 雪碧图支持 * feat(TreeView): 支持自定义节点图标 * feat(TreeView): 增加 IconTemplate 参数 * feat: 增加样式支持 * feat: 增加样式 * chore: bump version 10.7.3-beta02 --- src/BootstrapBlazor/BootstrapBlazor.csproj | 2 +- .../Components/TreeView/TreeViewRow.razor | 15 +++++++++++++-- src/BootstrapBlazor/Misc/TreeNodeBase.cs | 18 ++++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index 3173192f198..afd9756f6e9 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@  - 10.7.3-beta01 + 10.7.3-beta02 diff --git a/src/BootstrapBlazor/Components/TreeView/TreeViewRow.razor b/src/BootstrapBlazor/Components/TreeView/TreeViewRow.razor index 0ee10125677..1a756023227 100644 --- a/src/BootstrapBlazor/Components/TreeView/TreeViewRow.razor +++ b/src/BootstrapBlazor/Components/TreeView/TreeViewRow.razor @@ -1,4 +1,4 @@ -@namespace BootstrapBlazor.Components +@namespace BootstrapBlazor.Components @typeparam TItem @inherits ComponentBase @@ -20,7 +20,18 @@ @if (ShowIcon) { - + @if (Item.IconTemplate != null) + { + @Item.IconTemplate + } + else if (Item.UseSvg) + { + + } + else + { + + } } @if (Item.Template == null) { diff --git a/src/BootstrapBlazor/Misc/TreeNodeBase.cs b/src/BootstrapBlazor/Misc/TreeNodeBase.cs index b018f2468d9..bb9cfafde30 100644 --- a/src/BootstrapBlazor/Misc/TreeNodeBase.cs +++ b/src/BootstrapBlazor/Misc/TreeNodeBase.cs @@ -23,6 +23,24 @@ public abstract class TreeNodeBase : NodeBase /// public string? Icon { get; set; } + /// + /// 获得/设置 是否使用 Svg 图标 默认 false + /// Gets or sets whether to use Svg icon, default is false + /// + public bool UseSvg { get; set; } + + /// + /// 获得/设置 Svg 图标地址 + /// Gets or sets Svg icon URL + /// + public string? SvgHref { get; set; } + + /// + /// 获得/设置 自定义 Icon 模板 默认为 null + /// Gets or sets custom Icon template, default is null + /// + public RenderFragment? IconTemplate { get; set; } + /// /// 获得/设置 展开节点状态下的 Icon 图标 /// Gets or sets Icon when node is expanded From 568f40426f0ac4a73eacc82c5f9326aa0a524343 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 24 Jun 2026 15:47:49 +0800 Subject: [PATCH 29/30] test(TreeView): add IconTempalte unit test (#8160) --- test/UnitTest/Components/TreeViewTest.cs | 48 ++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/test/UnitTest/Components/TreeViewTest.cs b/test/UnitTest/Components/TreeViewTest.cs index a7be5b16cb7..c0ce52f196b 100644 --- a/test/UnitTest/Components/TreeViewTest.cs +++ b/test/UnitTest/Components/TreeViewTest.cs @@ -907,6 +907,54 @@ public void ExpandIcon_Ok() cut.Contains("test-expand-icon"); } + [Fact] + public void IconTemplate_Ok() + { + var items = TreeFoo.GetTreeItems(); + items[0].IconTemplate = builder => + { + builder.OpenElement(0, "span"); + builder.AddAttribute(1, "class", "test-icon-template"); + builder.CloseElement(); + }; + var cut = Context.Render>(pb => + { + pb.Add(a => a.ShowIcon, true); + pb.Add(a => a.Items, items); + }); + + // 自定义图标模板渲染成功 + cut.Contains("test-icon-template"); + + // 自定义图标模板优先级最高,未渲染 SvgIcon 与默认 i 标签 + var node = cut.FindAll(".tree-content .tree-node")[0]; + Assert.Contains("test-icon-template", node.InnerHtml); + Assert.DoesNotContain("bb-svg-icon", node.InnerHtml); + } + + [Fact] + public void UseSvg_Ok() + { + var items = TreeFoo.GetTreeItems(); + items[0].UseSvg = true; + items[0].SvgHref = "./test-tree.svg"; + items[0].Icon = "test-svg-icon"; + var cut = Context.Render>(pb => + { + pb.Add(a => a.ShowIcon, true); + pb.Add(a => a.Items, items); + }); + + // 使用 SvgIcon 渲染节点图标 + var icon = cut.FindComponent(); + Assert.Equal("test-svg-icon", icon.Instance.Name); + Assert.Equal("./test-tree.svg", icon.Instance.Href); + + cut.Contains("bb-svg-icon-test-svg-icon"); + cut.Contains("./test-tree.svg#test-svg-icon"); + cut.Contains("tree-icon"); + } + [Fact] public void ModelEqualityComparer_Ok() { From 9a274326cc01041a01bdaee272cd026f0d29209e Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 24 Jun 2026 15:52:47 +0800 Subject: [PATCH 30/30] feat(Transfer): add OnDisabledCallback parameter (#8158) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 增加 OnDisabledCallback 参数 * test: 补充单元测试 * feat: 增加禁用项不能选中逻辑 * test: 增加单元测试 --- .../Components/Transfer/Transfer.razor | 8 +- .../Components/Transfer/Transfer.razor.cs | 8 ++ .../Components/Transfer/TransferPanel.razor | 7 +- .../Transfer/TransferPanel.razor.cs | 35 +++++--- test/UnitTest/Components/TransferPanelTest.cs | 85 +++++++++++++++++++ 5 files changed, 127 insertions(+), 16 deletions(-) diff --git a/src/BootstrapBlazor/Components/Transfer/Transfer.razor b/src/BootstrapBlazor/Components/Transfer/Transfer.razor index 98aa9c2d8a2..651b4240527 100644 --- a/src/BootstrapBlazor/Components/Transfer/Transfer.razor +++ b/src/BootstrapBlazor/Components/Transfer/Transfer.razor @@ -1,4 +1,4 @@ -@namespace BootstrapBlazor.Components +@namespace BootstrapBlazor.Components @typeparam TValue @inherits ValidateBase @@ -10,7 +10,8 @@ + OnDisabledCallback="OnDisabledCallback" + HeaderTemplate="LeftHeaderTemplate" ItemTemplate="LeftItemTemplate">
diff --git a/src/BootstrapBlazor/Components/Transfer/Transfer.razor.cs b/src/BootstrapBlazor/Components/Transfer/Transfer.razor.cs index 5c01fa8c2f8..816a1ffa3e2 100644 --- a/src/BootstrapBlazor/Components/Transfer/Transfer.razor.cs +++ b/src/BootstrapBlazor/Components/Transfer/Transfer.razor.cs @@ -185,6 +185,14 @@ public partial class Transfer [NotNull] public Func? OnSetItemClass { get; set; } + /// + /// 获得/设置 组件是否被禁用回调方法,默认为 null + /// Gets or sets the callback method for whether the component is disabled. Default is null + /// v10.7.3 + /// + [Parameter] + public Func? OnDisabledCallback { get; set; } + /// /// 获得/设置 左侧 Panel Header 模板 /// Gets or sets the left Panel Header template diff --git a/src/BootstrapBlazor/Components/Transfer/TransferPanel.razor b/src/BootstrapBlazor/Components/Transfer/TransferPanel.razor index fa6fd97ae75..283940e168a 100644 --- a/src/BootstrapBlazor/Components/Transfer/TransferPanel.razor +++ b/src/BootstrapBlazor/Components/Transfer/TransferPanel.razor @@ -1,11 +1,12 @@ -@namespace BootstrapBlazor.Components +@namespace BootstrapBlazor.Components @inherits BootstrapComponentBase
@if(HeaderTemplate == null) { - } else @@ -30,7 +31,7 @@ if(ItemTemplate == null) { var state = item.Active ? CheckboxState.Checked : CheckboxState.UnChecked; - } diff --git a/src/BootstrapBlazor/Components/Transfer/TransferPanel.razor.cs b/src/BootstrapBlazor/Components/Transfer/TransferPanel.razor.cs index 3f82fcecaa5..849a260752c 100644 --- a/src/BootstrapBlazor/Components/Transfer/TransferPanel.razor.cs +++ b/src/BootstrapBlazor/Components/Transfer/TransferPanel.razor.cs @@ -114,6 +114,14 @@ public partial class TransferPanel [Parameter] public bool IsDisabled { get; set; } + /// + /// 获得/设置 组件是否被禁用回调方法,默认为 null + /// Gets or sets the callback method for whether the component is disabled. Default is null + /// v10.7.3 + /// + [Parameter] + public Func? OnDisabledCallback { get; set; } + /// /// 获得/设置 Header 模板 /// Gets or sets the header template @@ -157,11 +165,14 @@ protected override void OnParametersSet() protected CheckboxState HeaderCheckState() { var ret = CheckboxState.Indeterminate; - if (Items.Count > 0 && Items.All(i => i.Active)) + + // 全选状态仅根据未被 OnDisabledCallback 禁用的项进行计算 + var items = Items.Where(i => !IsItemDisabled(i)).ToList(); + if (items.Count > 0 && items.All(i => i.Active)) { ret = CheckboxState.Checked; } - else if (!Items.Any(i => i.Active)) + else if (!items.Any(i => i.Active)) { ret = CheckboxState.UnChecked; } @@ -177,14 +188,9 @@ protected async Task OnHeaderCheck(CheckboxState state, SelectedItem item) { if (Items != null) { - if (state == CheckboxState.Checked) - { - GetShownItems().ForEach(i => i.Active = true); - } - else - { - GetShownItems().ForEach(i => i.Active = false); - } + // 全选时过滤掉被 OnDisabledCallback 禁用的项,保持其原有选中状态 + var active = state == CheckboxState.Checked; + GetShownItems().Where(i => !IsItemDisabled(i)).ToList().ForEach(i => i.Active = active); if (OnSelectedItemsChanged != null) { @@ -248,4 +254,13 @@ protected void ClearSearch() private List GetShownItems() => (string.IsNullOrEmpty(SearchText) ? Items : Items.Where(i => i.Text.Contains(SearchText, StringComparison.OrdinalIgnoreCase)).ToList()); + + private bool GetDisableState(SelectedItem? item) => OnDisabledCallback != null + ? OnDisabledCallback(item) + : IsDisabled; + + /// + /// 判断指定数据项是否被 禁用,全选时禁用项将被过滤 + /// + private bool IsItemDisabled(SelectedItem item) => OnDisabledCallback?.Invoke(item) == true; } diff --git a/test/UnitTest/Components/TransferPanelTest.cs b/test/UnitTest/Components/TransferPanelTest.cs index 822c98bd86e..6fef790d159 100644 --- a/test/UnitTest/Components/TransferPanelTest.cs +++ b/test/UnitTest/Components/TransferPanelTest.cs @@ -80,6 +80,91 @@ public void IsDisabled_Ok() cut.Contains("disabled=\"disabled\""); } + [Fact] + public void OnDisabledCallback_Ok() + { + // 回调根据 item 决定禁用状态:Header(null) 与 Value 为 "1" 的项被禁用 + var cut = Context.Render(pb => + { + pb.Add(a => a.Items, + [ + new("1", "Test1"), + new("2", "Test2") + ]); + pb.Add(a => a.OnDisabledCallback, item => item == null || item.Value == "1"); + }); + + // 索引 0 为 Header,回调返回 true 被禁用 + var checkboxes = cut.FindComponents>(); + Assert.True(checkboxes[0].Instance.IsDisabled); + + // 索引 1 为 Test1,回调返回 true 被禁用 + Assert.True(checkboxes[1].Instance.IsDisabled); + + // 索引 2 为 Test2,回调返回 false 未禁用 + Assert.False(checkboxes[2].Instance.IsDisabled); + + // 设置了回调时优先使用回调结果:IsDisabled=true 但回调返回 false 时仍未禁用 + cut.Render(pb => + { + pb.Add(a => a.IsDisabled, true); + pb.Add(a => a.OnDisabledCallback, item => false); + }); + checkboxes = cut.FindComponents>(); + Assert.All(checkboxes, c => Assert.False(c.Instance.IsDisabled)); + } + + [Fact] + public async Task OnHeaderCheck_FilterDisabledItem_Ok() + { + List items = + [ + new("1", "Test1"), + new("2", "Test2") + ]; + var cut = Context.Render(pb => + { + pb.Add(a => a.Items, items); + // 仅禁用 Value 为 "1" 的项,Header(null) 不禁用以便点击全选 + pb.Add(a => a.OnDisabledCallback, item => item?.Value == "1"); + }); + + // 点击 Header 触发全选 + var checkbox = cut.FindComponent>(); + await cut.InvokeAsync(() => checkbox.Instance.SetState(CheckboxState.Checked)); + + // 全选时禁用项被过滤,保持未选中状态 + Assert.False(items[0].Active); + + // 未禁用项被选中 + Assert.True(items[1].Active); + + // 取消全选时禁用项同样被过滤,保持原有状态 + await cut.InvokeAsync(() => checkbox.Instance.SetState(CheckboxState.UnChecked)); + Assert.False(items[0].Active); + Assert.False(items[1].Active); + } + + [Fact] + public void HeaderCheckState_FilterDisabledItem_Ok() + { + // 禁用项 "1" 未选中,未禁用项 "2" 已选中 + List items = + [ + new("1", "Test1") { Active = false }, + new("2", "Test2") { Active = true } + ]; + var cut = Context.Render(pb => + { + pb.Add(a => a.Items, items); + pb.Add(a => a.OnDisabledCallback, item => item?.Value == "1"); + }); + + // 禁用项不参与全选状态计算,未禁用项全部选中时 Header 为 Checked + var checkbox = cut.FindComponent>(); + Assert.Equal(CheckboxState.Checked, checkbox.Instance.State); + } + [Fact] public void HeaderTemplate_Ok() {