Skip to content

Commit acb3c9e

Browse files
authored
refactor(AutoFill): prevent jitter when mouse scrolling too fast (#5670)
* refactor: 更新示例 * refactor: 更新 dom 使用 div * refactor: 修复 offset 失效问题 * refactor: 更新样式 * refactor: 更新样式 * refactor: 更新清空逻辑 * refactor: 增加占位符样式 * perf: 提高性能 * chore: bump version 9.5.0-beta09
1 parent 1e1454a commit acb3c9e

File tree

9 files changed

+102
-60
lines changed

9 files changed

+102
-60
lines changed

src/BootstrapBlazor.Server/Components/Samples/AutoFills.razor

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<section ignore>
1010
@((MarkupString)@Localizer["NormalDesc"].Value)
1111
</section>
12-
<AutoFill @bind-Value="Model1" Items="Items1" class="mb-3"
12+
<AutoFill @bind-Value="Model1" Items="Items1"
1313
IsLikeMatch="true" OnGetDisplayText="OnGetDisplayText" IsSelectAllTextOnFocus="true">
1414
<ItemTemplate>
1515
<div class="d-flex">
@@ -32,7 +32,7 @@
3232
<DemoBlock Title="@Localizer["CustomFilterTitle"]" Introduction="@Localizer["CustomFilterIntro"]" Name="CustomFilter">
3333
<section ignore>@((MarkupString)Localizer["CustomFilterDesc"].Value)</section>
3434
<AutoFill @bind-Value="Model2" Items="Items2" OnCustomFilter="OnCustomFilter"
35-
OnGetDisplayText="OnGetDisplayText" class="mb-3">
35+
OnGetDisplayText="OnGetDisplayText">
3636
<ItemTemplate>
3737
<div class="d-flex">
3838
<div>
@@ -54,7 +54,7 @@
5454
<section ignore>
5555
@((MarkupString)@Localizer["ShowDropdownListOnFocusDesc"].Value)
5656
</section>
57-
<AutoFill @bind-Value="Model3" Items="Items3" ShowDropdownListOnFocus="false" OnGetDisplayText="OnGetDisplayText" class="mb-3">
57+
<AutoFill @bind-Value="Model3" Items="Items3" ShowDropdownListOnFocus="false" OnGetDisplayText="OnGetDisplayText">
5858
<ItemTemplate>
5959
<div class="d-flex">
6060
<div>
@@ -87,10 +87,9 @@
8787

8888
<p class="code-label">1. 使用 OnQueryAsync 作为数据源</p>
8989
<div class="row mb-3">
90-
<div class="col-12">
91-
<AutoFill @bind-Value="Model4" OnQueryAsync="OnQueryAsync" OnGetDisplayText="OnGetDisplayText" class="mb-3"
92-
IsSelectAllTextOnFocus="true" OnCustomFilter="OnCustomVirtulizeFilter"
93-
IsVirtualize="true" RowHeight="58f" IsClearable="_isClearable">
90+
<div class="col-6">
91+
<AutoFill @bind-Value="Model4" OnQueryAsync="OnQueryAsync" OnGetDisplayText="OnGetDisplayText"
92+
IsSelectAllTextOnFocus="true" IsVirtualize="true" RowHeight="58f" IsClearable="_isClearable">
9493
<ItemTemplate>
9594
<div class="d-flex">
9695
<div>
@@ -103,19 +102,19 @@
103102
</div>
104103
</ItemTemplate>
105104
</AutoFill>
106-
<section ignore>
107-
@if (Model4 != null)
108-
{
109-
<EditorForm Model="@Model4" RowType="RowType.Inline" ItemsPerRow="2" />
110-
}
111-
</section>
112105
</div>
113106
</div>
107+
<section ignore>
108+
@if (Model4 != null)
109+
{
110+
<EditorForm Model="@Model4" RowType="RowType.Inline" ItemsPerRow="2"></EditorForm>
111+
}
112+
</section>
114113

115114
<p class="code-label">2. 使用 Items 作为数据源</p>
116-
<div class="row">
117-
<div class="col-12">
118-
<AutoFill @bind-Value="Model4" Items="Items4" OnGetDisplayText="OnGetDisplayText" class="mb-3"
115+
<div class="row mb-3">
116+
<div class="col-6">
117+
<AutoFill @bind-Value="Model5" Items="Items5" OnGetDisplayText="OnGetDisplayText"
119118
IsSelectAllTextOnFocus="true" OnCustomFilter="OnCustomVirtulizeFilter"
120119
IsVirtualize="true" RowHeight="58f" IsClearable="_isClearable">
121120
<ItemTemplate>
@@ -130,14 +129,14 @@
130129
</div>
131130
</ItemTemplate>
132131
</AutoFill>
133-
<section ignore>
134-
@if (Model4 != null)
135-
{
136-
<EditorForm Model="@Model4" RowType="RowType.Inline" ItemsPerRow="2" />
137-
}
138-
</section>
139132
</div>
140133
</div>
134+
<section ignore>
135+
@if (Model5 != null)
136+
{
137+
<EditorForm Model="@Model5" RowType="RowType.Inline" ItemsPerRow="2"></EditorForm>
138+
}
139+
</section>
141140
</DemoBlock>
142141

143142
<AttributeTable Items="@GetAttributes()" />

src/BootstrapBlazor.Server/Components/Samples/AutoFills.razor.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ partial class AutoFills
2222
[NotNull]
2323
private Foo Model4 { get; set; } = new();
2424

25+
[NotNull]
26+
private Foo Model5 { get; set; } = new();
27+
2528
private static string? OnGetDisplayText(Foo? foo) => foo?.Name;
2629

2730
[NotNull]
@@ -36,6 +39,9 @@ partial class AutoFills
3639
[NotNull]
3740
private IEnumerable<Foo>? Items4 { get; set; }
3841

42+
[NotNull]
43+
private IEnumerable<Foo>? Items5 { get; set; }
44+
3945
[Inject]
4046
[NotNull]
4147
private IStringLocalizer<Foo>? LocalizerFoo { get; set; }
@@ -58,6 +64,9 @@ protected override void OnInitialized()
5864

5965
Items4 = Foo.GenerateFoo(LocalizerFoo);
6066
Model4 = Items3.First();
67+
68+
Items5 = Foo.GenerateFoo(LocalizerFoo);
69+
Model5 = Items3.First();
6170
}
6271

6372
private Task<IEnumerable<Foo>> OnCustomFilter(string searchText)
@@ -68,7 +77,7 @@ private Task<IEnumerable<Foo>> OnCustomFilter(string searchText)
6877

6978
private Task<IEnumerable<Foo>> OnCustomVirtulizeFilter(string searchText)
7079
{
71-
var items = string.IsNullOrEmpty(searchText) ? Items4 : Items4.Where(i => i.Name!.Contains(searchText));
80+
var items = string.IsNullOrEmpty(searchText) ? Items5 : Items5.Where(i => i.Name!.Contains(searchText));
7281
return Task.FromResult(items);
7382
}
7483

src/BootstrapBlazor/BootstrapBlazor.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk.Razor">
22

33
<PropertyGroup>
4-
<Version>9.5.0-beta08</Version>
4+
<Version>9.5.0-beta09</Version>
55
</PropertyGroup>
66

77
<ItemGroup>

src/BootstrapBlazor/Components/AutoComplete/AutoComplete.razor

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,25 @@
2323

2424
@code {
2525
RenderFragment RenderDropdown =>
26-
@<ul class="dropdown-menu">
27-
@foreach (var item in Rows)
28-
{
29-
<li @key="item" class="dropdown-item" @onclick="() => OnClickItem(item)">
30-
@if (ItemTemplate == null)
31-
{
32-
<div>@item</div>
33-
}
34-
else
35-
{
36-
@ItemTemplate(item)
37-
}
38-
</li>
39-
}
40-
@if (ShowNoDataTip && Rows.Count == 0)
41-
{
42-
<li class="dropdown-item">@NoDataTip</li>
43-
}
44-
</ul>;
26+
@<div class="dropdown-menu">
27+
<div class="dropdown-menu-body">
28+
@foreach (var item in Rows)
29+
{
30+
<div @key="item" class="dropdown-item" @onclick="() => OnClickItem(item)">
31+
@if (ItemTemplate == null)
32+
{
33+
<div>@item</div>
34+
}
35+
else
36+
{
37+
@ItemTemplate(item)
38+
}
39+
</div>
40+
}
41+
@if (ShowNoDataTip && Rows.Count == 0)
42+
{
43+
<div class="dropdown-item">@NoDataTip</div>
44+
}
45+
</div>
46+
</div>;
4547
}

src/BootstrapBlazor/Components/AutoComplete/AutoComplete.razor.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ public partial class AutoComplete
1616
/// Gets the component style
1717
/// </summary>
1818
private string? ClassString => CssBuilder.Default("auto-complete")
19-
.AddClassFromAttributes(AdditionalAttributes)
2019
.Build();
2120

2221
/// <summary>

src/BootstrapBlazor/Components/AutoComplete/AutoComplete.razor.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,25 @@ export function init(id, invoke) {
1616
if (isPopover) {
1717
ac.popover = Popover.init(el, { toggleClass: '[data-bs-toggle="bb.dropdown"]' });
1818
}
19+
else {
20+
const extraClass = input.getAttribute('data-bs-custom-class');
21+
if (extraClass) {
22+
menu.classList.add(...extraClass.split(' '))
23+
}
24+
const offset = input.getAttribute('data-bs-offset');
25+
if (offset) {
26+
const [x, y] = offset.split(',');
27+
const xValue = parseFloat(x);
28+
const yValue = parseFloat(y);
29+
30+
if (xValue > 0) {
31+
menu.style.setProperty('margin-left', `${xValue}px`);
32+
}
33+
if (yValue > 0) {
34+
menu.style.setProperty('margin-top', `${yValue}px`);
35+
}
36+
}
37+
}
1938

2039
// debounce
2140
const duration = parseInt(input.getAttribute('data-bb-debounce') || '0');

src/BootstrapBlazor/Components/AutoComplete/PopoverCompleteBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ protected override void OnParametersSet()
155155
{
156156
base.OnParametersSet();
157157

158-
Offset ??= "[0, 10]";
158+
Offset ??= "[0, 6]";
159159
}
160160

161161
/// <summary>

src/BootstrapBlazor/Components/AutoFill/AutoFill.razor

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
{
2323
<span class="@ClearClassString" @onclick="OnClearValue"><i class="@ClearIcon"></i></span>
2424
}
25-
<ul class="dropdown-menu">
25+
<div class="dropdown-menu">
2626
@if (IsVirtualize)
2727
{
2828
<div class="dropdown-menu-body dropdown-virtual">
@@ -41,7 +41,7 @@
4141
}
4242
else if (ShowNoDataTip && Rows.Count == 0)
4343
{
44-
<li class="dropdown-item">@NoDataTip</li>
44+
<div class="dropdown-item">@NoDataTip</div>
4545
}
4646
else
4747
{
@@ -52,12 +52,12 @@
5252
}
5353
</div>
5454
}
55-
</ul>
55+
</div>
5656
</div>
5757

5858
@code {
5959
RenderFragment<TValue> RenderRow => item =>
60-
@<li @key="@item" class="dropdown-item" @onclick="() => OnClickItem(item)">
60+
@<div @key="@item" class="dropdown-item" @onclick="() => OnClickItem(item)">
6161
@if (ItemTemplate != null)
6262
{
6363
@ItemTemplate(item)
@@ -66,10 +66,10 @@
6666
{
6767
<div>@GetDisplayText(item)</div>
6868
}
69-
</li>;
69+
</div>;
7070

7171
RenderFragment<PlaceholderContext> RenderPlaceHolderRow => context =>
72-
@<div class="dropdown-item">
72+
@<div class="dropdown-item" style="@PlaceHolderStyleString">
7373
<div class="is-ph"></div>
7474
</div>;
7575
}

src/BootstrapBlazor/Components/AutoFill/AutoFill.razor.cs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ public partial class AutoFill<TValue>
1919
/// </summary>
2020
private string? ClassString => CssBuilder.Default("auto-complete auto-fill")
2121
.AddClass("is-clearable", IsClearable)
22-
.AddClassFromAttributes(AdditionalAttributes)
2322
.Build();
2423

2524
/// <summary>
@@ -104,14 +103,14 @@ public partial class AutoFill<TValue>
104103
/// </summary>
105104
/// <remarks>Effective when <see cref="IsVirtualize"/> is set to true.</remarks>
106105
[Parameter]
107-
public float RowHeight { get; set; } = 33f;
106+
public float RowHeight { get; set; } = 50f;
108107

109108
/// <summary>
110-
/// Gets or sets the overscan count for virtual scrolling. Default is 4.
109+
/// Gets or sets the overscan count for virtual scrolling. Default is 3.
111110
/// </summary>
112111
/// <remarks>Effective when <see cref="IsVirtualize"/> is set to true.</remarks>
113112
[Parameter]
114-
public int OverscanCount { get; set; } = 4;
113+
public int OverscanCount { get; set; } = 3;
115114

116115
/// <summary>
117116
/// Gets or sets the callback method for loading virtualized items.
@@ -161,6 +160,10 @@ public partial class AutoFill<TValue>
161160
.AddClass($"text-danger", IsValid.HasValue && !IsValid.Value)
162161
.Build();
163162

163+
private string? PlaceHolderStyleString => RowHeight > 50f
164+
? CssBuilder.Default().AddStyle("height", $"{RowHeight}px").Build()
165+
: null;
166+
164167
/// <summary>
165168
/// <inheritdoc/>
166169
/// </summary>
@@ -178,6 +181,14 @@ protected override void OnParametersSet()
178181
Items ??= [];
179182
}
180183

184+
private bool _render = true;
185+
186+
/// <summary>
187+
/// <inheritdoc/>
188+
/// </summary>
189+
/// <returns></returns>
190+
protected override bool ShouldRender() => _render;
191+
181192
private bool IsNullable() => !ValueType.IsValueType || NullableUnderlyingType != null;
182193

183194
/// <summary>
@@ -197,6 +208,10 @@ private async Task OnClearValue()
197208
await OnClearAsync();
198209
}
199210
CurrentValue = default;
211+
_displayText = null;
212+
_filterItems = null;
213+
_searchText = null;
214+
200215
if (OnQueryAsync != null)
201216
{
202217
await _virtualizeElement.RefreshDataAsync();
@@ -222,14 +237,13 @@ private async Task OnClickItem(TValue val)
222237

223238
private List<TValue> Rows => _filterItems ?? [.. Items];
224239

225-
private int _totalCount;
226-
227240
private async ValueTask<ItemsProviderResult<TValue>> LoadItems(ItemsProviderRequest request)
228241
{
229-
var count = _totalCount == 0 ? request.Count : Math.Min(request.Count, _totalCount - request.StartIndex);
230-
var data = await OnQueryAsync(new() { StartIndex = request.StartIndex, Count = count, SearchText = _searchText });
242+
_render = false;
243+
var data = await OnQueryAsync(new() { StartIndex = request.StartIndex, Count = request.Count, SearchText = _searchText });
244+
_render = true;
231245

232-
_totalCount = data.TotalCount;
246+
var _totalCount = data.TotalCount;
233247
var items = data.Items ?? [];
234248
return new ItemsProviderResult<TValue>(items, _totalCount);
235249
}

0 commit comments

Comments
 (0)