Skip to content

Commit c3ebfc3

Browse files
authored
Merge branch 'main' into fix-dropdown
Signed-off-by: Argo Zhang <[email protected]>
2 parents c549808 + 24fc677 commit c3ebfc3

File tree

11 files changed

+2180
-23
lines changed

11 files changed

+2180
-23
lines changed

src/BootstrapBlazor/Components/Select/Select.razor

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@
3131
<span class="@ClearClassString" @onclick="OnClearValue"><i class="@ClearIcon"></i></span>
3232
}
3333
<div class="dropdown-menu">
34+
@if (ShowSearch)
35+
{
36+
<div class="@SearchClassString">
37+
<input type="text" class="search-text form-control" autocomplete="off" value="@SearchText" aria-label="Search">
38+
<i class="@SearchIconString"></i>
39+
</div>
40+
}
3441
@if (IsVirtualize)
3542
{
36-
@if (ShowSearch)
37-
{
38-
<div class="@SearchClassString">
39-
<input type="text" class="search-text form-control" autocomplete="off" value="@SearchText" @oninput="EventCallback.Factory.CreateBinder<string>(this, async v => await SearchTextChanged(v), SearchText)" aria-label="Search">
40-
<i class="@SearchIconString"></i>
41-
</div>
42-
}
4343
<div class="dropdown-virtual">
4444
@if (OnQueryAsync == null)
4545
{
@@ -53,13 +53,6 @@
5353
}
5454
else
5555
{
56-
@if (ShowSearch)
57-
{
58-
<div class="@SearchClassString">
59-
<input type="text" class="search-text form-control" autocomplete="off" value="@SearchText" @oninput="EventCallback.Factory.CreateBinder<string>(this, async v => await SearchTextChanged(v), SearchText)" aria-label="Search">
60-
<i class="@SearchIconString"></i>
61-
</div>
62-
}
6356
@foreach (var itemGroup in Rows.GroupBy(i => i.GroupName))
6457
{
6558
if (!string.IsNullOrEmpty(itemGroup.Key))

src/BootstrapBlazor/Components/Select/Select.razor.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -354,9 +354,8 @@ private async ValueTask<ItemsProviderResult<SelectedItem>> LoadItems(ItemsProvid
354354

355355
private async Task SearchTextChanged(string val)
356356
{
357-
SearchText = val;
358357
_itemsCache = null;
359-
358+
SearchText = val;
360359
if (OnQueryAsync != null)
361360
{
362361
// 通过 ItemProvider 提供数据
@@ -399,7 +398,7 @@ private bool TryParseSelectItem(string value, [MaybeNullWhen(false)] out TValue
399398
/// <inheritdoc/>
400399
/// </summary>
401400
/// <returns></returns>
402-
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, nameof(ConfirmSelectedItem));
401+
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, new { ConfirmMethodCallback = nameof(ConfirmSelectedItem), SearchMethodCallback = nameof(TriggerOnSearch) });
403402

404403
/// <summary>
405404
/// 客户端回车回调方法
@@ -416,6 +415,18 @@ public async Task ConfirmSelectedItem(int index)
416415
}
417416
}
418417

418+
/// <summary>
419+
/// 客户端搜索栏回调方法
420+
/// </summary>
421+
/// <param name="searchText"></param>
422+
/// <returns></returns>
423+
[JSInvokable]
424+
public async Task TriggerOnSearch(string searchText)
425+
{
426+
await SearchTextChanged(searchText);
427+
StateHasChanged();
428+
}
429+
419430
/// <summary>
420431
/// 下拉框选项点击时调用此方法
421432
/// </summary>

src/BootstrapBlazor/Components/Select/Select.razor.js

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { getHeight, getInnerHeight, getTransitionDelayDurationFromElement } from "../../modules/utility.js"
1+
import { debounce, getHeight, getInnerHeight, getTransitionDelayDurationFromElement } from "../../modules/utility.js"
22
import Data from "../../modules/data.js"
33
import EventHandler from "../../modules/event-handler.js"
44
import Popover from "../../modules/base-popover.js"
55

6-
export function init(id, invoke, method) {
6+
export function init(id, invoke, options) {
77
const el = document.getElementById(id)
8-
8+
const { confirmMethodCallback, searchMethodCallback } = options;
99
if (el == null) {
1010
return
1111
}
@@ -64,7 +64,7 @@ export function init(id, invoke, method) {
6464
if (e.key === "Enter") {
6565
popover.toggleMenu.classList.remove('show')
6666
let index = indexOf(el, activeItem)
67-
invoke.invokeMethodAsync(method, index)
67+
invoke.invokeMethodAsync(confirmMethodCallback, index)
6868
}
6969
}
7070
}
@@ -77,7 +77,27 @@ export function init(id, invoke, method) {
7777
el,
7878
popover
7979
}
80-
Data.set(id, select)
80+
Data.set(id, select);
81+
82+
const onSearch = debounce(v => invoke.invokeMethodAsync(searchMethodCallback, v));
83+
let isComposing = false;
84+
85+
EventHandler.on(el, 'input', '.search-text', e => {
86+
if (isComposing) {
87+
return;
88+
}
89+
90+
onSearch(e.delegateTarget.value);
91+
});
92+
93+
EventHandler.on(el, 'compositionstart', '.search-text', e => {
94+
isComposing = true;
95+
});
96+
97+
EventHandler.on(el, 'compositionend', '.search-text', e => {
98+
isComposing = false;
99+
onSearch(e.delegateTarget.value);
100+
});
81101
}
82102

83103
export function show(id) {
@@ -108,7 +128,10 @@ export function dispose(id) {
108128

109129
if (select) {
110130
EventHandler.off(select.el, 'shown.bs.dropdown')
111-
EventHandler.off(select.el, 'keydown')
131+
EventHandler.off(select.el, 'keydown');
132+
EventHandler.off(select.el, 'input');
133+
EventHandler.off(select.el, 'compositionstart');
134+
EventHandler.off(select.el, 'compositionend')
112135
Popover.dispose(select.popover)
113136
}
114137
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License
3+
// See the LICENSE file in the project root for more information.
4+
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone
5+
6+
namespace BootstrapBlazor.Components;
7+
8+
/// <summary>
9+
/// ISelect 接口
10+
/// </summary>
11+
public interface ISelectGeneric<TValue>
12+
{
13+
/// <summary>
14+
/// 增加 SelectedItem 项方法
15+
/// </summary>
16+
/// <param name="item"></param>
17+
void Add(SelectedItem<TValue> item);
18+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
@namespace BootstrapBlazor.Components
2+
@using Microsoft.AspNetCore.Components.Web.Virtualization
3+
@typeparam TValue
4+
@inherits SelectBase<TValue>
5+
@attribute [JSModuleAutoLoader("./_content/BootstrapBlazor/Components/Select/Select.razor.js", JSObjectReference = true)]
6+
7+
@if (IsShowLabel)
8+
{
9+
<BootstrapLabel required="@Required" for="@InputId" ShowLabelTooltip="ShowLabelTooltip" Value="@DisplayText" />
10+
}
11+
<div @attributes="AdditionalAttributes" id="@Id" class="@ClassString">
12+
<CascadingValue Value="this" IsFixed="true">
13+
@Options
14+
</CascadingValue>
15+
<RenderTemplate>
16+
<div class="dropdown-toggle" data-bs-toggle="@ToggleString" data-bs-placement="@PlacementString" data-bs-offset="@OffsetString" data-bs-custom-class="@CustomClassString">
17+
@if (DisplayTemplate != null)
18+
{
19+
<div id="@InputId" class="@InputClassString" tabindex="0">
20+
@DisplayTemplate(SelectedRow)
21+
</div>
22+
}
23+
else
24+
{
25+
<input type="text" id="@InputId" disabled="@Disabled" placeholder="@PlaceHolder" class="@InputClassString" value="@SelectedRow.Text" @onchange="OnChange" readonly="@ReadonlyString" />
26+
}
27+
<span class="@AppendClassString"><i class="@DropdownIcon"></i></span>
28+
</div>
29+
@if (GetClearable())
30+
{
31+
<span class="@ClearClassString" @onclick="OnClearValue"><i class="@ClearIcon"></i></span>
32+
}
33+
<div class="dropdown-menu">
34+
@if (IsVirtualize)
35+
{
36+
@if (ShowSearch)
37+
{
38+
<div class="@SearchClassString">
39+
<input type="text" class="search-text form-control" autocomplete="off" value="@SearchText" @oninput="EventCallback.Factory.CreateBinder<string>(this, async v => await SearchTextChanged(v), SearchText)" aria-label="Search">
40+
<i class="@SearchIconString"></i>
41+
</div>
42+
}
43+
<div class="dropdown-virtual">
44+
@if (OnQueryAsync == null)
45+
{
46+
<Virtualize ItemSize="RowHeight" OverscanCount="OverscanCount" Items="@GetVirtualItems()" ChildContent="RenderRow" />
47+
}
48+
else
49+
{
50+
<Virtualize ItemSize="RowHeight" OverscanCount="OverscanCount" ItemsProvider="LoadItems" Placeholder="RenderPlaceHolderRow" ItemContent="RenderRow" @ref="VirtualizeElement" />
51+
}
52+
</div>
53+
}
54+
else
55+
{
56+
@if (ShowSearch)
57+
{
58+
<div class="@SearchClassString">
59+
<input type="text" class="search-text form-control" autocomplete="off" value="@SearchText" @oninput="EventCallback.Factory.CreateBinder<string>(this, async v => await SearchTextChanged(v), SearchText)" aria-label="Search">
60+
<i class="@SearchIconString"></i>
61+
</div>
62+
}
63+
@foreach (var itemGroup in Rows.GroupBy(i => i.GroupName))
64+
{
65+
if (!string.IsNullOrEmpty(itemGroup.Key))
66+
{
67+
if (GroupItemTemplate != null)
68+
{
69+
@GroupItemTemplate(itemGroup.Key)
70+
}
71+
else
72+
{
73+
<Divider Text="@itemGroup.Key" />
74+
}
75+
}
76+
@foreach (var item in itemGroup)
77+
{
78+
@RenderRow(item)
79+
}
80+
}
81+
@if (Rows.Count == 0)
82+
{
83+
<div class="dropdown-item">@NoSearchDataText</div>
84+
}
85+
}
86+
</div>
87+
@if (!IsPopover)
88+
{
89+
<div class="dropdown-menu-arrow"></div>
90+
}
91+
</RenderTemplate>
92+
</div>
93+
94+
@code {
95+
RenderFragment<SelectedItem<TValue>> RenderRow => item =>
96+
@<div class="@ActiveItem(item)" @onclick="() => OnClickItem(item)">
97+
@if (ItemTemplate != null)
98+
{
99+
@ItemTemplate(item)
100+
}
101+
else if (IsMarkupString)
102+
{
103+
@((MarkupString)item.Text)
104+
}
105+
else
106+
{
107+
@item.Text
108+
}
109+
</div>;
110+
111+
RenderFragment<PlaceholderContext> RenderPlaceHolderRow => context =>
112+
@<div class="dropdown-item">
113+
<div class="is-ph"></div>
114+
</div>;
115+
}

0 commit comments

Comments
 (0)