mirror of
https://github.com/dotnetcore/BootstrapBlazor.git
synced 2025-12-20 02:16:40 +08:00
feat(SelectGeneric): add SelectGeneric component (#4838)
* refactor: 重构 SelectedItem 增加构造函数 * refactor: 移动 SelectedItem 到子类 * refactor: 精简代码移除 SingleSelectBase 类 * refactor: 改造 SelectedItem 泛型 * feat: 增加 SelectGeneric 组件 * feat: 增加 ToSelectList 泛型扩展方法 * refactor: 改造 Select 组件移除对泛型的支持 * doc: 更新泛型 Select 组件示例 * test: 更新单元测试 * test: 更新单元测试 * refactor: 增加排除标签 * chore: bump version 9.1.3-beta06 * test: 增加单元测试 * test: 更新单元测试 * test: 增加 Display 单元测试 * test: 更新 Select 单元测试 * refactor: 更新 SelectGeneric 值不存在时的逻辑
This commit is contained in:
@@ -442,7 +442,7 @@
|
||||
<section ignore>@((MarkupString)Localizer["SelectsGenericDesc"].Value)</section>
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-6">
|
||||
<Select Items="_genericItems" @bind-Value="_selectedFoo" IsEditable="true"></Select>
|
||||
<SelectGeneric Items="_genericItems" @bind-Value="_selectedFoo" IsEditable="true"></SelectGeneric>
|
||||
</div>
|
||||
<div class="col-12 col-sm-6">
|
||||
<Display Value="_selectedFoo?.Address"></Display>
|
||||
|
||||
@@ -242,7 +242,7 @@ public sealed partial class Selects
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private readonly List<SelectedItem<Foo>> _genericItems =
|
||||
private readonly List<SelectedItem<Foo?>> _genericItems =
|
||||
[
|
||||
new() { Text = "Foo1", Value = new Foo() { Id = 1, Address = "Address_F001" } },
|
||||
new() { Text = "Foo2", Value = new Foo() { Id = 2, Address = "Address_F002" } },
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>9.1.3-beta05</Version>
|
||||
<Version>9.1.3-beta06</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -36,6 +36,19 @@ public partial class MultiSelect<TValue>
|
||||
.AddClass("d-none", SelectedItems.Count != 0)
|
||||
.Build();
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 绑定数据集
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[NotNull]
|
||||
public IEnumerable<SelectedItem>? Items { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 选项模板
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment<SelectedItem>? ItemTemplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 组件 PlaceHolder 文字 默认为 点击进行多选 ...
|
||||
/// </summary>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@namespace BootstrapBlazor.Components
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@typeparam TValue
|
||||
@inherits SingleSelectBase<TValue>
|
||||
@inherits SelectBase<TValue>
|
||||
@attribute [BootstrapModuleAutoLoader(JSObjectReference = true)]
|
||||
|
||||
@if (IsShowLabel)
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace BootstrapBlazor.Components;
|
||||
/// Select 组件实现类
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
[ExcludeFromCodeCoverage]
|
||||
public partial class Select<TValue> : ISelect, IModelEqualityComparer<TValue>
|
||||
{
|
||||
[Inject]
|
||||
@@ -50,7 +51,7 @@ public partial class Select<TValue> : ISelect, IModelEqualityComparer<TValue>
|
||||
/// <param name="item"></param>
|
||||
/// <returns></returns>
|
||||
private string? ActiveItem(SelectedItem item) => CssBuilder.Default("dropdown-item")
|
||||
.AddClass("active", Match(item))
|
||||
.AddClass("active", item.Value == CurrentValueAsString)
|
||||
.AddClass("disabled", item.IsDisabled)
|
||||
.Build();
|
||||
|
||||
@@ -191,6 +192,55 @@ public partial class Select<TValue> : ISelect, IModelEqualityComparer<TValue>
|
||||
[NotNull]
|
||||
private Virtualize<SelectedItem>? VirtualizeElement { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 绑定数据集
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[NotNull]
|
||||
public IEnumerable<SelectedItem>? Items { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 选项模板
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment<SelectedItem>? ItemTemplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 下拉框项目改变前回调委托方法 返回 true 时选项值改变,否则选项值不变
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<SelectedItem, Task<bool>>? OnBeforeSelectedItemChange { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// SelectedItemChanged 回调方法
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<SelectedItem, Task>? OnSelectedItemChanged { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 Swal 图标 默认 Question
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public SwalCategory SwalCategory { get; set; } = SwalCategory.Question;
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 Swal 标题 默认 null
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string? SwalTitle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 Swal 内容 默认 null
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string? SwalContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 Footer 默认 null
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string? SwalFooter { get; set; }
|
||||
|
||||
[Inject]
|
||||
[NotNull]
|
||||
private IStringLocalizer<Select<TValue>>? Localizer { get; set; }
|
||||
@@ -214,6 +264,11 @@ public partial class Select<TValue> : ISelect, IModelEqualityComparer<TValue>
|
||||
|
||||
private ItemsProviderResult<SelectedItem> _result;
|
||||
|
||||
/// <summary>
|
||||
/// 当前选择项实例
|
||||
/// </summary>
|
||||
private SelectedItem? SelectedItem { get; set; }
|
||||
|
||||
private List<SelectedItem> Rows
|
||||
{
|
||||
get
|
||||
@@ -234,7 +289,7 @@ public partial class Select<TValue> : ISelect, IModelEqualityComparer<TValue>
|
||||
|
||||
private SelectedItem? GetSelectedRow()
|
||||
{
|
||||
var item = Rows.Find(Match)
|
||||
var item = Rows.Find(i => i.Value == CurrentValueAsString)
|
||||
?? Rows.Find(i => i.Active)
|
||||
?? Rows.Where(i => !i.IsDisabled).FirstOrDefault()
|
||||
?? GetVirtualizeItem();
|
||||
@@ -374,8 +429,6 @@ public partial class Select<TValue> : ISelect, IModelEqualityComparer<TValue>
|
||||
/// <returns></returns>
|
||||
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, nameof(ConfirmSelectedItem));
|
||||
|
||||
private bool Match(SelectedItem i) => i is SelectedItem<TValue> d ? Equals(d.Value, Value) : i.Value.Equals(CurrentValueAsString, StringComparison);
|
||||
|
||||
/// <summary>
|
||||
/// 客户端回车回调方法
|
||||
/// </summary>
|
||||
@@ -453,13 +506,13 @@ public partial class Select<TValue> : ISelect, IModelEqualityComparer<TValue>
|
||||
{
|
||||
if (_lastSelectedValueString != item.Value)
|
||||
{
|
||||
_lastSelectedValueString = item.Value;
|
||||
|
||||
item.Active = true;
|
||||
SelectedItem = item;
|
||||
|
||||
// 触发 StateHasChanged
|
||||
CurrentValueAsString = item.Value;
|
||||
_lastSelectedValueString = item.Value ?? "";
|
||||
CurrentValueAsString = _lastSelectedValueString;
|
||||
|
||||
// 触发 SelectedItemChanged 事件
|
||||
if (OnSelectedItemChanged != null)
|
||||
@@ -518,41 +571,13 @@ public partial class Select<TValue> : ISelect, IModelEqualityComparer<TValue>
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
// 判断是否为泛型 SelectedItem
|
||||
var itemType = Items.GetType();
|
||||
var isGeneric = false;
|
||||
if (itemType.IsGenericType)
|
||||
{
|
||||
isGeneric = itemType.GetGenericArguments()[0].IsGenericType;
|
||||
}
|
||||
if (isGeneric)
|
||||
{
|
||||
TValue? val = default;
|
||||
if (TextConvertToValueCallback != null)
|
||||
{
|
||||
val = await TextConvertToValueCallback(v);
|
||||
}
|
||||
item = new SelectedItem<TValue>() { Text = v, Value = val };
|
||||
}
|
||||
else
|
||||
{
|
||||
item = new SelectedItem(v, v);
|
||||
}
|
||||
item = new SelectedItem(v, v);
|
||||
|
||||
var items = new List<SelectedItem>() { item };
|
||||
items.AddRange(Items);
|
||||
Items = items;
|
||||
CurrentValueAsString = v;
|
||||
}
|
||||
|
||||
if (item is SelectedItem<TValue> value)
|
||||
{
|
||||
CurrentValue = value.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentValueAsString = v;
|
||||
}
|
||||
CurrentValueAsString = v;
|
||||
|
||||
if (OnInputChangedCallback != null)
|
||||
{
|
||||
|
||||
@@ -16,13 +16,6 @@ public abstract class SelectBase<TValue> : PopoverSelectBase<TValue>
|
||||
[Parameter]
|
||||
public Color Color { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 绑定数据集
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[NotNull]
|
||||
public IEnumerable<SelectedItem>? Items { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 是否显示搜索框 默认为 false 不显示
|
||||
/// </summary>
|
||||
@@ -54,12 +47,6 @@ public abstract class SelectBase<TValue> : PopoverSelectBase<TValue>
|
||||
[Parameter]
|
||||
public StringComparison StringComparison { get; set; } = StringComparison.OrdinalIgnoreCase;
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 选项模板
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment<SelectedItem>? ItemTemplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 分组项模板
|
||||
/// </summary>
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace BootstrapBlazor.Components;
|
||||
/// <summary>
|
||||
/// SelectOption 组件
|
||||
/// </summary>
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class SelectOption : ComponentBase
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
|
||||
|
||||
namespace BootstrapBlazor.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Select 组件基类
|
||||
/// </summary>
|
||||
public abstract class SingleSelectBase<TValue> : SelectBase<TValue>
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前选择项实例
|
||||
/// </summary>
|
||||
protected SelectedItem? SelectedItem { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 Swal 图标 默认 Question
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public SwalCategory SwalCategory { get; set; } = SwalCategory.Question;
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 Swal 标题 默认 null
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string? SwalTitle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 Swal 内容 默认 null
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string? SwalContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 Footer 默认 null
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string? SwalFooter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 下拉框项目改变前回调委托方法 返回 true 时选项值改变,否则选项值不变
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<SelectedItem, Task<bool>>? OnBeforeSelectedItemChange { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// SelectedItemChanged 回调方法
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<SelectedItem, Task>? OnSelectedItemChanged { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
|
||||
|
||||
namespace BootstrapBlazor.Components;
|
||||
|
||||
/// <summary>
|
||||
/// ISelect 接口
|
||||
/// </summary>
|
||||
public interface ISelectGeneric<TValue>
|
||||
{
|
||||
/// <summary>
|
||||
/// 增加 SelectedItem 项方法
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
void Add(SelectedItem<TValue> item);
|
||||
}
|
||||
115
src/BootstrapBlazor/Components/SelectGeneric/SelectGeneric.razor
Normal file
115
src/BootstrapBlazor/Components/SelectGeneric/SelectGeneric.razor
Normal file
@@ -0,0 +1,115 @@
|
||||
@namespace BootstrapBlazor.Components
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@typeparam TValue
|
||||
@inherits SelectBase<TValue>
|
||||
@attribute [BootstrapModuleAutoLoader(JSObjectReference = true)]
|
||||
|
||||
@if (IsShowLabel)
|
||||
{
|
||||
<BootstrapLabel required="@Required" for="@InputId" ShowLabelTooltip="ShowLabelTooltip" Value="@DisplayText" />
|
||||
}
|
||||
<div @attributes="AdditionalAttributes" id="@Id" class="@ClassString">
|
||||
<CascadingValue Value="this" IsFixed="true">
|
||||
@Options
|
||||
</CascadingValue>
|
||||
<RenderTemplate>
|
||||
<div class="dropdown-toggle" data-bs-toggle="@ToggleString" data-bs-placement="@PlacementString" data-bs-offset="@OffsetString" data-bs-custom-class="@CustomClassString">
|
||||
@if (DisplayTemplate != null)
|
||||
{
|
||||
<div id="@InputId" class="@InputClassString" tabindex="0">
|
||||
@DisplayTemplate(SelectedRow)
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<input type="text" id="@InputId" disabled="@Disabled" placeholder="@PlaceHolder" class="@InputClassString" value="@SelectedRow?.Text" @onchange="OnChange" readonly="@ReadonlyString" />
|
||||
}
|
||||
<span class="@AppendClassString"><i class="@DropdownIcon"></i></span>
|
||||
</div>
|
||||
@if (GetClearable())
|
||||
{
|
||||
<span class="@ClearClassString" @onclick="OnClearValue"><i class="@ClearIcon"></i></span>
|
||||
}
|
||||
<div class="dropdown-menu">
|
||||
@if (IsVirtualize)
|
||||
{
|
||||
@if (ShowSearch)
|
||||
{
|
||||
<div class="@SearchClassString">
|
||||
<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">
|
||||
<i class="@SearchIconString"></i>
|
||||
</div>
|
||||
}
|
||||
<div class="dropdown-virtual">
|
||||
@if (OnQueryAsync == null)
|
||||
{
|
||||
<Virtualize ItemSize="RowHeight" OverscanCount="OverscanCount" Items="@GetVirtualItems()" ChildContent="RenderRow" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<Virtualize ItemSize="RowHeight" OverscanCount="OverscanCount" ItemsProvider="LoadItems" Placeholder="RenderPlaceHolderRow" ItemContent="RenderRow" @ref="VirtualizeElement" />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (ShowSearch)
|
||||
{
|
||||
<div class="@SearchClassString">
|
||||
<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">
|
||||
<i class="@SearchIconString"></i>
|
||||
</div>
|
||||
}
|
||||
@foreach (var itemGroup in Rows.GroupBy(i => i.GroupName))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(itemGroup.Key))
|
||||
{
|
||||
if (GroupItemTemplate != null)
|
||||
{
|
||||
@GroupItemTemplate(itemGroup.Key)
|
||||
}
|
||||
else
|
||||
{
|
||||
<Divider Text="@itemGroup.Key" />
|
||||
}
|
||||
}
|
||||
@foreach (var item in itemGroup)
|
||||
{
|
||||
@RenderRow(item)
|
||||
}
|
||||
}
|
||||
@if (Rows.Count == 0)
|
||||
{
|
||||
<div class="dropdown-item">@NoSearchDataText</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@if (!IsPopover)
|
||||
{
|
||||
<div class="dropdown-menu-arrow"></div>
|
||||
}
|
||||
</RenderTemplate>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
RenderFragment<SelectedItem<TValue>> RenderRow => item =>
|
||||
@<div class="@ActiveItem(item)" @onclick="() => OnClickItem(item)">
|
||||
@if (ItemTemplate != null)
|
||||
{
|
||||
@ItemTemplate(item)
|
||||
}
|
||||
else if (IsMarkupString)
|
||||
{
|
||||
@((MarkupString)item.Text)
|
||||
}
|
||||
else
|
||||
{
|
||||
@item.Text
|
||||
}
|
||||
</div>;
|
||||
|
||||
RenderFragment<PlaceholderContext> RenderPlaceHolderRow => context =>
|
||||
@<div class="dropdown-item">
|
||||
<div class="is-ph"></div>
|
||||
</div>;
|
||||
}
|
||||
@@ -0,0 +1,566 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
|
||||
|
||||
using Microsoft.AspNetCore.Components.Web.Virtualization;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace BootstrapBlazor.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Select 组件实现类
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
public partial class SelectGeneric<TValue> : ISelectGeneric<TValue>, IModelEqualityComparer<TValue>
|
||||
{
|
||||
[Inject]
|
||||
[NotNull]
|
||||
private SwalService? SwalService { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得 样式集合
|
||||
/// </summary>
|
||||
private string? ClassString => CssBuilder.Default("select dropdown")
|
||||
.AddClass("cls", IsClearable)
|
||||
.AddClassFromAttributes(AdditionalAttributes)
|
||||
.Build();
|
||||
|
||||
/// <summary>
|
||||
/// 获得 样式集合
|
||||
/// </summary>
|
||||
private string? InputClassString => CssBuilder.Default("form-select form-control")
|
||||
.AddClass($"border-{Color.ToDescriptionString()}", Color != Color.None && !IsDisabled && !IsValid.HasValue)
|
||||
.AddClass($"border-success", IsValid.HasValue && IsValid.Value)
|
||||
.AddClass($"border-danger", IsValid.HasValue && !IsValid.Value)
|
||||
.AddClass(CssClass).AddClass(ValidCss)
|
||||
.Build();
|
||||
|
||||
private string? ClearClassString => CssBuilder.Default("clear-icon")
|
||||
.AddClass($"text-{Color.ToDescriptionString()}", Color != Color.None)
|
||||
.AddClass($"text-success", IsValid.HasValue && IsValid.Value)
|
||||
.AddClass($"text-danger", IsValid.HasValue && !IsValid.Value)
|
||||
.Build();
|
||||
|
||||
private bool GetClearable() => IsClearable && !IsDisabled;
|
||||
|
||||
/// <summary>
|
||||
/// 设置当前项是否 Active 方法
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <returns></returns>
|
||||
private string? ActiveItem(SelectedItem<TValue> item) => CssBuilder.Default("dropdown-item")
|
||||
.AddClass("active", Equals(item.Value, Value))
|
||||
.AddClass("disabled", item.IsDisabled)
|
||||
.Build();
|
||||
|
||||
private string? SearchClassString => CssBuilder.Default("search")
|
||||
.AddClass("is-fixed", IsFixedSearch)
|
||||
.Build();
|
||||
|
||||
private readonly List<SelectedItem<TValue>> _children = [];
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 右侧清除图标 默认 fa-solid fa-angle-up
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[NotNull]
|
||||
public string? ClearIcon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 搜索文本发生变化时回调此方法
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<string, IEnumerable<SelectedItem<TValue>>>? OnSearchTextChanged { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 是否固定下拉框中的搜索栏 默认 false
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool IsFixedSearch { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 是否可编辑 默认 false
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool IsEditable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 选项输入更新后回调方法 默认 null
|
||||
/// </summary>
|
||||
/// <remarks>设置 <see cref="IsEditable"/> 后生效</remarks>
|
||||
[Parameter]
|
||||
public Func<string, Task>? OnInputChangedCallback { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 选项输入更新后转换为 Value 回调方法 默认 null
|
||||
/// </summary>
|
||||
/// <remarks>设置 <see cref="IsEditable"/> 后生效</remarks>
|
||||
[Parameter]
|
||||
public Func<string, Task<TValue>>? TextConvertToValueCallback { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 无搜索结果时显示文字
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string? NoSearchDataText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得 PlaceHolder 属性
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string? PlaceHolder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 是否可清除 默认 false
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool IsClearable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 选项模板支持静态数据
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment? Options { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 显示部分模板 默认 null
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment<SelectedItem<TValue>?>? DisplayTemplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 是否开启虚拟滚动 默认 false 未开启 注意:开启虚拟滚动后不支持 <see cref="SelectBase{TValue}.ShowSearch"/> <see cref="PopoverSelectBase{TValue}.IsPopover"/> <seealso cref="IsFixedSearch"/> 参数设置,设置初始值时请设置 <see cref="DefaultVirtualizeItemText"/>
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool IsVirtualize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 虚拟滚动行高 默认为 33
|
||||
/// </summary>
|
||||
/// <remarks>需要设置 <see cref="IsVirtualize"/> 值为 true 时生效</remarks>
|
||||
[Parameter]
|
||||
public float RowHeight { get; set; } = 33f;
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 过载阈值数 默认为 4
|
||||
/// </summary>
|
||||
/// <remarks>需要设置 <see cref="IsVirtualize"/> 值为 true 时生效</remarks>
|
||||
[Parameter]
|
||||
public int OverscanCount { get; set; } = 4;
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 默认文本 <see cref="IsVirtualize"/> 时生效 默认 null
|
||||
/// </summary>
|
||||
/// <remarks>开启 <see cref="IsVirtualize"/> 并且通过 <see cref="OnQueryAsync"/> 提供数据源时,由于渲染时还未调用或者调用后数据集未包含 <see cref="DisplayBase{TValue}.Value"/> 选项值,此时使用 DefaultText 值渲染</remarks>
|
||||
[Parameter]
|
||||
public string? DefaultVirtualizeItemText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 清除文本内容 OnClear 回调方法 默认 null
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<Task>? OnClearAsync { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 禁止首次加载时触发 OnSelectedItemChanged 回调方法 默认 false
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool DisableItemChangedWhenFirstRender { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 比较数据是否相同回调方法 默认为 null
|
||||
/// <para>提供此回调方法时忽略 <see cref="CustomKeyAttribute"/> 属性</para>
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<TValue, TValue, bool>? ValueEqualityComparer { get; set; }
|
||||
|
||||
Func<TValue, TValue, bool>? IModelEqualityComparer<TValue>.ModelEqualityComparer
|
||||
{
|
||||
get => ValueEqualityComparer;
|
||||
set => ValueEqualityComparer = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 数据主键标识标签 默认为 <see cref="KeyAttribute"/>用于判断数据主键标签,如果模型未设置主键时可使用 <see cref="ValueEqualityComparer"/> 参数自定义判断数据模型支持联合主键
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[NotNull]
|
||||
public Type? CustomKeyAttribute { get; set; } = typeof(KeyAttribute);
|
||||
|
||||
[NotNull]
|
||||
private Virtualize<SelectedItem<TValue>>? VirtualizeElement { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 绑定数据集
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[NotNull]
|
||||
public IEnumerable<SelectedItem<TValue>>? Items { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 选项模板
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment<SelectedItem<TValue>>? ItemTemplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 下拉框项目改变前回调委托方法 返回 true 时选项值改变,否则选项值不变
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<SelectedItem<TValue>, Task<bool>>? OnBeforeSelectedItemChange { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// SelectedItemChanged 回调方法
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<SelectedItem<TValue>, Task>? OnSelectedItemChanged { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 Swal 图标 默认 Question
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public SwalCategory SwalCategory { get; set; } = SwalCategory.Question;
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 Swal 标题 默认 null
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string? SwalTitle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 Swal 内容 默认 null
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string? SwalContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 Footer 默认 null
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string? SwalFooter { get; set; }
|
||||
|
||||
[Inject]
|
||||
[NotNull]
|
||||
private IStringLocalizer<Select<TValue>>? Localizer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得 input 组件 Id 方法
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected override string? RetrieveId() => InputId;
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 Select 内部 Input 组件 Id
|
||||
/// </summary>
|
||||
private string? InputId => $"{Id}_input";
|
||||
|
||||
private TValue? _lastSelectedValue;
|
||||
|
||||
private bool _init = true;
|
||||
|
||||
private List<SelectedItem<TValue>>? _itemsCache;
|
||||
|
||||
private ItemsProviderResult<SelectedItem<TValue>> _result;
|
||||
|
||||
/// <summary>
|
||||
/// 当前选择项实例
|
||||
/// </summary>
|
||||
private SelectedItem<TValue>? SelectedItem { get; set; }
|
||||
|
||||
private List<SelectedItem<TValue>> Rows
|
||||
{
|
||||
get
|
||||
{
|
||||
_itemsCache ??= string.IsNullOrEmpty(SearchText) ? GetRowsByItems() : GetRowsBySearch();
|
||||
return _itemsCache;
|
||||
}
|
||||
}
|
||||
|
||||
private SelectedItem<TValue>? SelectedRow
|
||||
{
|
||||
get
|
||||
{
|
||||
SelectedItem ??= GetSelectedRow();
|
||||
return SelectedItem;
|
||||
}
|
||||
}
|
||||
|
||||
private SelectedItem<TValue>? GetSelectedRow()
|
||||
{
|
||||
var item = Rows.Find(i => Equals(i.Value, Value))
|
||||
?? Rows.Find(i => i.Active)
|
||||
?? Rows.Where(i => !i.IsDisabled).FirstOrDefault()
|
||||
?? new SelectedItem<TValue>(Value, DefaultVirtualizeItemText!);
|
||||
|
||||
if (!_init || !DisableItemChangedWhenFirstRender)
|
||||
{
|
||||
_ = SelectedItemChanged(item);
|
||||
_init = false;
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
private List<SelectedItem<TValue>> GetRowsByItems()
|
||||
{
|
||||
var items = new List<SelectedItem<TValue>>();
|
||||
items.AddRange(Items);
|
||||
items.AddRange(_children);
|
||||
return items;
|
||||
}
|
||||
|
||||
private List<SelectedItem<TValue>> GetRowsBySearch()
|
||||
{
|
||||
var items = OnSearchTextChanged?.Invoke(SearchText) ?? FilterBySearchText(GetRowsByItems());
|
||||
return items.ToList();
|
||||
}
|
||||
|
||||
private IEnumerable<SelectedItem<TValue>> FilterBySearchText(IEnumerable<SelectedItem<TValue>> source) => string.IsNullOrEmpty(SearchText)
|
||||
? source
|
||||
: source.Where(i => i.Text.Contains(SearchText, StringComparison));
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
base.OnParametersSet();
|
||||
|
||||
Items ??= [];
|
||||
PlaceHolder ??= Localizer[nameof(PlaceHolder)];
|
||||
NoSearchDataText ??= Localizer[nameof(NoSearchDataText)];
|
||||
DropdownIcon ??= IconTheme.GetIconByKey(ComponentIcons.SelectDropdownIcon);
|
||||
ClearIcon ??= IconTheme.GetIconByKey(ComponentIcons.SelectClearIcon);
|
||||
|
||||
// 内置对枚举类型的支持
|
||||
if (!Items.Any() && ValueType.IsEnum())
|
||||
{
|
||||
var item = NullableUnderlyingType == null ? "" : PlaceHolder;
|
||||
Items = ValueType.ToSelectList<TValue>(string.IsNullOrEmpty(item) ? null : new SelectedItem<TValue>(default!, item));
|
||||
}
|
||||
|
||||
_itemsCache = null;
|
||||
SelectedItem = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 数据总条目
|
||||
/// </summary>
|
||||
private int TotalCount { get; set; }
|
||||
|
||||
private List<SelectedItem<TValue>> GetVirtualItems() => FilterBySearchText(GetRowsByItems()).ToList();
|
||||
|
||||
/// <summary>
|
||||
/// 虚拟滚动数据加载回调方法
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[NotNull]
|
||||
public Func<VirtualizeQueryOption, Task<QueryData<SelectedItem<TValue>>>>? OnQueryAsync { get; set; }
|
||||
|
||||
private async ValueTask<ItemsProviderResult<SelectedItem<TValue>>> LoadItems(ItemsProviderRequest request)
|
||||
{
|
||||
// 有搜索条件时使用原生请求数量
|
||||
// 有总数时请求剩余数量
|
||||
var count = !string.IsNullOrEmpty(SearchText) ? request.Count : GetCountByTotal();
|
||||
var data = await OnQueryAsync(new() { StartIndex = request.StartIndex, Count = count, SearchText = SearchText });
|
||||
|
||||
TotalCount = data.TotalCount;
|
||||
var items = data.Items ?? [];
|
||||
_result = new ItemsProviderResult<SelectedItem<TValue>>(items, TotalCount);
|
||||
return _result;
|
||||
|
||||
int GetCountByTotal() => TotalCount == 0 ? request.Count : Math.Min(request.Count, TotalCount - request.StartIndex);
|
||||
}
|
||||
|
||||
private async Task SearchTextChanged(string val)
|
||||
{
|
||||
SearchText = val;
|
||||
_itemsCache = null;
|
||||
|
||||
if (OnQueryAsync != null)
|
||||
{
|
||||
// 通过 ItemProvider 提供数据
|
||||
await VirtualizeElement.RefreshDataAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, nameof(ConfirmSelectedItem));
|
||||
|
||||
/// <summary>
|
||||
/// 客户端回车回调方法
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <returns></returns>
|
||||
[JSInvokable]
|
||||
public async Task ConfirmSelectedItem(int index)
|
||||
{
|
||||
if (index < Rows.Count)
|
||||
{
|
||||
await OnClickItem(Rows[index]);
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下拉框选项点击时调用此方法
|
||||
/// </summary>
|
||||
private async Task OnClickItem(SelectedItem<TValue> item)
|
||||
{
|
||||
var ret = true;
|
||||
if (OnBeforeSelectedItemChange != null)
|
||||
{
|
||||
ret = await OnBeforeSelectedItemChange(item);
|
||||
if (ret)
|
||||
{
|
||||
// 返回 True 弹窗提示
|
||||
var option = new SwalOption()
|
||||
{
|
||||
Category = SwalCategory,
|
||||
Title = SwalTitle,
|
||||
Content = SwalContent
|
||||
};
|
||||
if (!string.IsNullOrEmpty(SwalFooter))
|
||||
{
|
||||
option.ShowFooter = true;
|
||||
option.FooterTemplate = builder => builder.AddContent(0, SwalFooter);
|
||||
}
|
||||
ret = await SwalService.ShowModal(option);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 返回 False 直接运行
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
if (ret)
|
||||
{
|
||||
await SelectedItemChanged(item);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SelectedItemChanged(SelectedItem<TValue> item)
|
||||
{
|
||||
if (!Equals(item.Value, Value))
|
||||
{
|
||||
item.Active = true;
|
||||
SelectedItem = item;
|
||||
|
||||
CurrentValue = item.Value;
|
||||
|
||||
// 触发 SelectedItemChanged 事件
|
||||
if (OnSelectedItemChanged != null)
|
||||
{
|
||||
await OnSelectedItemChanged(SelectedItem);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await ValueTypeChanged(item);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ValueTypeChanged(SelectedItem<TValue> item)
|
||||
{
|
||||
if (!Equals(_lastSelectedValue, item.Value))
|
||||
{
|
||||
_lastSelectedValue = item.Value;
|
||||
|
||||
item.Active = true;
|
||||
SelectedItem = item;
|
||||
|
||||
// 触发 StateHasChanged
|
||||
CurrentValue = item.Value;
|
||||
|
||||
// 触发 SelectedItemChanged 事件
|
||||
if (OnSelectedItemChanged != null)
|
||||
{
|
||||
await OnSelectedItemChanged(SelectedItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加静态下拉项方法
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
public void Add(SelectedItem<TValue> item) => _children.Add(item);
|
||||
|
||||
/// <summary>
|
||||
/// 清空搜索栏文本内容
|
||||
/// </summary>
|
||||
public void ClearSearchText() => SearchText = null;
|
||||
|
||||
private async Task OnClearValue()
|
||||
{
|
||||
if (ShowSearch)
|
||||
{
|
||||
ClearSearchText();
|
||||
}
|
||||
if (OnClearAsync != null)
|
||||
{
|
||||
await OnClearAsync();
|
||||
}
|
||||
|
||||
SelectedItem<TValue>? item;
|
||||
if (OnQueryAsync != null)
|
||||
{
|
||||
await VirtualizeElement.RefreshDataAsync();
|
||||
item = _result.Items.FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
item = Items.FirstOrDefault();
|
||||
}
|
||||
if (item != null)
|
||||
{
|
||||
await SelectedItemChanged(item);
|
||||
}
|
||||
}
|
||||
|
||||
private string? ReadonlyString => IsEditable ? null : "readonly";
|
||||
|
||||
private async Task OnChange(ChangeEventArgs args)
|
||||
{
|
||||
if (args.Value is string v)
|
||||
{
|
||||
// Items 中没有时插入一个 SelectedItem
|
||||
var item = Items.FirstOrDefault(i => i.Text == v);
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
TValue? val = default;
|
||||
if (TextConvertToValueCallback != null)
|
||||
{
|
||||
val = await TextConvertToValueCallback(v);
|
||||
}
|
||||
item = new SelectedItem<TValue>(val, v);
|
||||
|
||||
var items = new List<SelectedItem<TValue>>() { item };
|
||||
items.AddRange(Items);
|
||||
Items = items;
|
||||
CurrentValue = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentValue = item.Value;
|
||||
}
|
||||
|
||||
if (OnInputChangedCallback != null)
|
||||
{
|
||||
await OnInputChangedCallback(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
/// <returns></returns>
|
||||
public bool Equals(TValue? x, TValue? y) => this.Equals<TValue>(x, y);
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import { getHeight, getInnerHeight, getTransitionDelayDurationFromElement } from "../../modules/utility.js"
|
||||
import Data from "../../modules/data.js"
|
||||
import EventHandler from "../../modules/event-handler.js"
|
||||
import Popover from "../../modules/base-popover.js"
|
||||
|
||||
export function init(id, invoke, method) {
|
||||
const el = document.getElementById(id)
|
||||
|
||||
if (el == null) {
|
||||
return
|
||||
}
|
||||
|
||||
const search = el.querySelector("input.search-text")
|
||||
const popover = Popover.init(el)
|
||||
|
||||
const shown = () => {
|
||||
if (search) {
|
||||
search.focus();
|
||||
}
|
||||
const prev = popover.toggleMenu.querySelector('.dropdown-item.preActive')
|
||||
if (prev) {
|
||||
prev.classList.remove('preActive')
|
||||
}
|
||||
scrollToActive(popover.toggleMenu, prev)
|
||||
}
|
||||
|
||||
const keydown = e => {
|
||||
if (popover.toggleElement.classList.contains('show')) {
|
||||
const items = popover.toggleMenu.querySelectorAll('.dropdown-item:not(.search, .disabled)')
|
||||
let activeItem = popover.toggleMenu.querySelector('.dropdown-item.preActive')
|
||||
if (activeItem == null) activeItem = popover.toggleMenu.querySelector('.dropdown-item.active')
|
||||
|
||||
if (activeItem) {
|
||||
if (items.length > 1) {
|
||||
activeItem.classList.remove('preActive')
|
||||
if (e.key === "ArrowUp") {
|
||||
do {
|
||||
activeItem = activeItem.previousElementSibling
|
||||
}
|
||||
while (activeItem && !activeItem.classList.contains('dropdown-item'))
|
||||
if (!activeItem) {
|
||||
activeItem = items[items.length - 1]
|
||||
}
|
||||
activeItem.classList.add('preActive')
|
||||
scrollToActive(popover.toggleMenu, activeItem)
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}
|
||||
else if (e.key === "ArrowDown") {
|
||||
do {
|
||||
activeItem = activeItem.nextElementSibling
|
||||
}
|
||||
while (activeItem && !activeItem.classList.contains('dropdown-item'))
|
||||
if (!activeItem) {
|
||||
activeItem = items[0]
|
||||
}
|
||||
activeItem.classList.add('preActive')
|
||||
scrollToActive(popover.toggleMenu, activeItem)
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}
|
||||
}
|
||||
|
||||
if (e.key === "Enter") {
|
||||
popover.toggleMenu.classList.remove('show')
|
||||
let index = indexOf(el, activeItem)
|
||||
invoke.invokeMethodAsync(method, index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EventHandler.on(el, 'shown.bs.dropdown', shown);
|
||||
EventHandler.on(el, 'keydown', keydown)
|
||||
|
||||
const select = {
|
||||
el,
|
||||
popover
|
||||
}
|
||||
Data.set(id, select)
|
||||
}
|
||||
|
||||
export function show(id) {
|
||||
const select = Data.get(id)
|
||||
if (select) {
|
||||
const delay = getTransitionDelayDurationFromElement(select.popover.toggleElement);
|
||||
const handler = setTimeout(() => {
|
||||
clearTimeout(handler);
|
||||
select.popover.show();
|
||||
}, delay);
|
||||
}
|
||||
}
|
||||
|
||||
export function hide(id) {
|
||||
const select = Data.get(id)
|
||||
const delay = getTransitionDelayDurationFromElement(select.popover.toggleElement);
|
||||
if (select) {
|
||||
const handler = setTimeout(() => {
|
||||
clearTimeout(handler);
|
||||
select.popover.hide();
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
|
||||
export function dispose(id) {
|
||||
const select = Data.get(id)
|
||||
Data.remove(id)
|
||||
|
||||
if (select) {
|
||||
EventHandler.off(select.el, 'shown.bs.dropdown')
|
||||
EventHandler.off(select.el, 'keydown')
|
||||
Popover.dispose(select.popover)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function scrollToActive(el, activeItem) {
|
||||
if (!activeItem) {
|
||||
activeItem = el.querySelector('.dropdown-item.active')
|
||||
}
|
||||
|
||||
if (activeItem) {
|
||||
const innerHeight = getInnerHeight(el)
|
||||
const itemHeight = getHeight(activeItem);
|
||||
const index = indexOf(el, activeItem)
|
||||
const margin = itemHeight * index - (innerHeight - itemHeight) / 2;
|
||||
if (margin >= 0) {
|
||||
el.scrollTo(0, margin);
|
||||
}
|
||||
else {
|
||||
el.scrollTo(0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function indexOf(el, element) {
|
||||
const items = el.querySelectorAll('.dropdown-item')
|
||||
return Array.prototype.indexOf.call(items, element)
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
.select,
|
||||
.popover-dropdown {
|
||||
--bb-dropdown-link-pre-active-bg: #{$bb-dropdown-link-pre-active-bg};
|
||||
}
|
||||
|
||||
.select {
|
||||
--bb-select-focus-shadow: #{$bb-select-focus-shadow};
|
||||
--bb-select-padding-right: #{$bb-select-padding-right};
|
||||
--bb-select-padding: #{$bb-select-padding};
|
||||
--bb-select-search-padding: #{$bb-select-search-padding};
|
||||
--bb-select-search-margin-bottom: #{$bb-select-search-margin-bottom};
|
||||
--bb-select-search-border-color: #{$bb-select-search-border-color};
|
||||
--bb-select-search-padding-right: #{$bb-select-search-padding-right};
|
||||
--bb-select-search-icon-color: #{$bb-select-search-icon-color};
|
||||
--bb-select-search-icon-right: #{$bb-select-search-icon-right};
|
||||
--bb-select-search-icon-top: #{$bb-select-search-icon-top};
|
||||
--bb-select-search-height: #{$bb-select-search-height};
|
||||
--bb-select-append-width: #{$bb-select-append-width};
|
||||
--bb-select-append-color: #{$bb-select-append-color};
|
||||
}
|
||||
|
||||
.select:not(.cascade) .dropdown-menu {
|
||||
overflow-x: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cascade,
|
||||
.select {
|
||||
--bb-select-dropdown-menu-margin-top: 8px;
|
||||
}
|
||||
|
||||
.cascade .dropdown-menu,
|
||||
.selec .dropdown-menu {
|
||||
margin-block-start: var(--bb-select-dropdown-menu-margin-top) !important;
|
||||
}
|
||||
|
||||
.select .form-select {
|
||||
background-image: none;
|
||||
background-color: var(--bs-body-bg);
|
||||
border: var(--bs-border-width) solid var(--bs-border-color);
|
||||
border-radius: var(--bs-border-radius);
|
||||
padding: var(--bb-select-padding);
|
||||
padding-inline-end: var(--bb-select-padding-right);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.select .form-select:disabled {
|
||||
background-color: var(--bs-secondary-bg);
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
--bs-dropdown-border-radius: var(--bs-border-radius);
|
||||
overflow: auto;
|
||||
max-height: var(--bb-dropdown-max-height);
|
||||
}
|
||||
|
||||
.dropdown-menu .dropdown-virtual {
|
||||
overflow-y: auto;
|
||||
margin: calc(0px - var(--bs-dropdown-padding-y)) var(--bs-dropdown-padding-x);
|
||||
max-height: calc(var(--bb-dropdown-max-height) - 2px);
|
||||
padding: var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x);
|
||||
}
|
||||
|
||||
.dropdown-menu .search + .dropdown-virtual {
|
||||
max-height: calc(var(--bb-dropdown-max-height) - var(--bb-select-search-height));
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropdown-item.preActive {
|
||||
background-color: var(--bb-dropdown-link-pre-active-bg);
|
||||
}
|
||||
|
||||
.dropdown-menu-arrow {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-width: 0 6px 6px;
|
||||
border-style: solid;
|
||||
border-color: transparent transparent rgba(0,0,0,.15);
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
margin-block-start: 4px;
|
||||
z-index: 1001;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dropdown-menu-arrow:after {
|
||||
content: " ";
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-width: 0 6px 6px;
|
||||
border-style: solid;
|
||||
border-color: transparent transparent var(--bs-body-bg);
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: -6px;
|
||||
}
|
||||
|
||||
[data-bs-theme='dark'] .dropdown-menu-arrow:after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.show > .dropdown-menu,
|
||||
.show > .dropdown-menu-arrow {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.form-select:focus {
|
||||
box-shadow: var(--bb-select-focus-shadow);
|
||||
border-color: var(--bb-border-focus-color);
|
||||
}
|
||||
|
||||
.form-select:not(:disabled):hover {
|
||||
border-color: var(--bb-border-hover-color);
|
||||
}
|
||||
|
||||
.form-select.show + .form-select-append i {
|
||||
transform: rotate(0);
|
||||
}
|
||||
|
||||
.dropdown-menu[data-popper-placement="bottom-start"].show + .dropdown-menu-arrow,
|
||||
.dropdown-menu[data-bs-popper="none"].show + .dropdown-menu-arrow {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.form-select-append {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: var(--bb-select-append-width);
|
||||
right: 0;
|
||||
top: 0;
|
||||
color: var(--bb-select-append-color);
|
||||
pointer-events: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.form-select-append i {
|
||||
transition: all .3s;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.show > .form-select-append i {
|
||||
transform: rotate(0);
|
||||
}
|
||||
|
||||
.select .clear-icon {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: var(--bb-select-append-width);
|
||||
right: 0;
|
||||
top: 0;
|
||||
color: var(--bb-select-append-color);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.select:hover .clear-icon {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.select.cls:hover .form-select-append {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.form-select.is-valid:focus,
|
||||
.was-validated .form-select:valid:focus,
|
||||
.form-select.is-invalid:focus,
|
||||
.was-validated .form-select:invalid:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.form-select.is-valid:not([multiple]):not([size]),
|
||||
.form-select.is-valid:not([multiple])[size="1"],
|
||||
.was-validated .form-select:valid:not([multiple]):not([size]),
|
||||
.was-validated .form-select:valid:not([multiple])[size="1"],
|
||||
.form-select.is-invalid:not([multiple]):not([size]),
|
||||
.form-select.is-invalid:not([multiple])[size="1"],
|
||||
.was-validated .form-select:invalid:not([multiple]):not([size]),
|
||||
.was-validated .form-select:invalid:not([multiple])[size="1"] {
|
||||
background-position: right -1rem center, center right 1.5rem;
|
||||
padding-inline-end: var(--bb-select-padding-right);
|
||||
}
|
||||
|
||||
.arrow-danger {
|
||||
border-color: transparent transparent var(--bs-danger);
|
||||
}
|
||||
|
||||
.arrow-success {
|
||||
border-color: transparent transparent var(--bs-success);
|
||||
}
|
||||
|
||||
.arrow-primary {
|
||||
border-color: transparent transparent var(--bs-primary);
|
||||
}
|
||||
|
||||
.arrow-warning {
|
||||
border-color: transparent transparent var(--bs-warning);
|
||||
}
|
||||
|
||||
.arrow-info {
|
||||
border-color: transparent transparent var(--bs-info);
|
||||
}
|
||||
|
||||
.dropdown-menu .search {
|
||||
padding: var(--bb-select-search-padding);
|
||||
position: relative;
|
||||
border-block-end: var(--bs-border-width) solid var(--bb-select-search-border-color);
|
||||
margin-block-end: var(--bb-select-search-margin-bottom);
|
||||
}
|
||||
|
||||
.dropdown-menu .search.is-fixed {
|
||||
position: sticky;
|
||||
top: calc(-1 * var(--bs-dropdown-padding-y));
|
||||
background-color: var(--bs-dropdown-bg);
|
||||
}
|
||||
|
||||
.dropdown-menu .search .search-text {
|
||||
padding-inline-end: var(--bb-select-search-padding-right);
|
||||
}
|
||||
|
||||
.dropdown-menu .search .icon {
|
||||
position: absolute;
|
||||
right: var(--bb-select-search-icon-right);
|
||||
top: var(--bb-select-search-icon-top);
|
||||
color: var(--bb-select-search-icon-color);
|
||||
}
|
||||
|
||||
.select:not(.multi-select) .dropdown-toggle {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.select .dropdown-toggle:after,
|
||||
.btn-popover-confirm.dropdown-toggle:after {
|
||||
content: none;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
|
||||
|
||||
namespace BootstrapBlazor.Components;
|
||||
|
||||
/// <summary>
|
||||
/// SelectOptionPro 组件
|
||||
/// </summary>
|
||||
public class SelectOptionGeneric<TValue> : ComponentBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 获得/设置 显示名称
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string? Text { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 选项值
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public TValue? Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 是否选中 默认 false
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool Active { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 是否禁用 默认 false
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool IsDisabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 分组名称
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string? GroupName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 父组件通过级联参数获得
|
||||
/// </summary>
|
||||
[CascadingParameter]
|
||||
private ISelectGeneric<TValue>? Container { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// OnInitialized 方法
|
||||
/// </summary>
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
|
||||
Container?.Add(ToSelectedItem());
|
||||
}
|
||||
|
||||
private SelectedItem<TValue> ToSelectedItem() => new(Value, Text ?? "")
|
||||
{
|
||||
Active = Active,
|
||||
GroupName = GroupName ?? "",
|
||||
IsDisabled = IsDisabled
|
||||
};
|
||||
}
|
||||
@@ -72,6 +72,33 @@ public static class EnumExtensions
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定枚举类型的枚举值集合,默认通过 DisplayAttribute DescriptionAttribute 标签显示 DisplayName 支持资源文件 回退机制显示字段名称
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="additionalItem"></param>
|
||||
/// <returns></returns>
|
||||
public static List<SelectedItem<TValue>> ToSelectList<TValue>(this Type type, SelectedItem<TValue>? additionalItem = null)
|
||||
{
|
||||
var ret = new List<SelectedItem<TValue>>();
|
||||
if (additionalItem != null)
|
||||
{
|
||||
ret.Add(additionalItem);
|
||||
}
|
||||
|
||||
if (type.IsEnum())
|
||||
{
|
||||
var t = Nullable.GetUnderlyingType(type) ?? type;
|
||||
foreach (var field in Enum.GetNames(t))
|
||||
{
|
||||
var desc = Utility.GetDisplayName(t, field);
|
||||
var val = (TValue)Enum.Parse(t, field);
|
||||
ret.Add(new SelectedItem<TValue>(val, desc));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断类型是否为枚举类型
|
||||
/// </summary>
|
||||
|
||||
@@ -18,12 +18,16 @@ public class SelectedItem
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
public SelectedItem(string value, string text) => (Value, Text) = (value, text);
|
||||
public SelectedItem(string value, string text)
|
||||
{
|
||||
Value = value ?? "";
|
||||
Text = text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 显示名称
|
||||
/// </summary>
|
||||
public virtual string Text { get; set; } = "";
|
||||
public string Text { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 选项值
|
||||
|
||||
@@ -6,12 +6,48 @@
|
||||
namespace BootstrapBlazor.Components;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="SelectedItem"/> 泛型实现类
|
||||
/// 泛型实现类
|
||||
/// </summary>
|
||||
public class SelectedItem<T> : SelectedItem
|
||||
public class SelectedItem<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// 获得/设置 泛型值
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
public new T? Value { get; set; }
|
||||
public SelectedItem() { }
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="text"></param>
|
||||
public SelectedItem(T? value, string text)
|
||||
{
|
||||
Value = value;
|
||||
Text = text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 显示名称
|
||||
/// </summary>
|
||||
public string Text { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 选项值
|
||||
/// </summary>
|
||||
public T? Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 是否选中
|
||||
/// </summary>
|
||||
public bool Active { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 是否禁用
|
||||
/// </summary>
|
||||
public bool IsDisabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 分组名称
|
||||
/// </summary>
|
||||
public string GroupName { get; set; } = "";
|
||||
}
|
||||
|
||||
@@ -226,6 +226,14 @@ public class DisplayTest : BootstrapBlazorTestBase
|
||||
Assert.Contains("中学", cut.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_Test()
|
||||
{
|
||||
var cut = Context.RenderComponent<MockComponent>();
|
||||
var result = cut.Instance.Test(new SelectedItem("1", "Test"));
|
||||
Assert.Equal("1", result);
|
||||
}
|
||||
|
||||
class DisplayGenericValueMock<T>
|
||||
{
|
||||
[NotNull]
|
||||
@@ -246,4 +254,12 @@ public class DisplayTest : BootstrapBlazorTestBase
|
||||
return Value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
class MockComponent : DisplayBase<SelectedItem>
|
||||
{
|
||||
public string? Test(SelectedItem v)
|
||||
{
|
||||
return base.FormatValueAsString(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,15 +13,22 @@ namespace UnitTest.Components;
|
||||
|
||||
public class SelectTest : BootstrapBlazorTestBase
|
||||
{
|
||||
[Fact]
|
||||
public void SeletectedItem_Ok()
|
||||
{
|
||||
var item = new SelectedItem(null!, "Text");
|
||||
Assert.Equal(item.Value, string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OnSearchTextChanged_Null()
|
||||
{
|
||||
var cut = Context.RenderComponent<BootstrapBlazorRoot>(pb =>
|
||||
{
|
||||
pb.AddChildContent<Select<string>>(pb =>
|
||||
pb.AddChildContent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.ShowSearch, true);
|
||||
pb.Add(a => a.Items, new List<SelectedItem>()
|
||||
pb.Add(a => a.Items, new List<SelectedItem<string>>()
|
||||
{
|
||||
new("1", "Test1"),
|
||||
new("2", "Test2") { IsDisabled = true }
|
||||
@@ -29,7 +36,7 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
});
|
||||
});
|
||||
|
||||
var ctx = cut.FindComponent<Select<string>>();
|
||||
var ctx = cut.FindComponent<SelectGeneric<string>>();
|
||||
await ctx.InvokeAsync(async () =>
|
||||
{
|
||||
await ctx.Instance.ConfirmSelectedItem(0);
|
||||
@@ -54,7 +61,7 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
pb.Add(a => a.OnSelectedItemChanged, null);
|
||||
pb.Add(a => a.OnSearchTextChanged, text =>
|
||||
{
|
||||
return new List<SelectedItem>()
|
||||
return new List<SelectedItem<string>>()
|
||||
{
|
||||
new("1", "Test1")
|
||||
};
|
||||
@@ -71,11 +78,11 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
[Fact]
|
||||
public void Options_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<Select<string>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Options, builder =>
|
||||
{
|
||||
builder.OpenComponent<SelectOption>(0);
|
||||
builder.OpenComponent<SelectOptionGeneric<string>>(0);
|
||||
builder.AddAttribute(1, "Text", "Test-Select");
|
||||
builder.CloseComponent();
|
||||
|
||||
@@ -89,13 +96,13 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
[Fact]
|
||||
public void Disabled_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<Select<string>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.IsDisabled, true);
|
||||
pb.Add(a => a.Options, builder =>
|
||||
{
|
||||
builder.OpenComponent<SelectOption>(0);
|
||||
builder.AddAttribute(1, nameof(SelectOption.IsDisabled), true);
|
||||
builder.OpenComponent<SelectOptionGeneric<string>>(0);
|
||||
builder.AddAttribute(1, nameof(SelectOptionGeneric<string>.IsDisabled), true);
|
||||
builder.CloseComponent();
|
||||
|
||||
builder.OpenComponent<SelectOption>(2);
|
||||
@@ -110,10 +117,10 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
public void IsClearable_Ok()
|
||||
{
|
||||
var val = "Test2";
|
||||
var cut = Context.RenderComponent<Select<string>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.IsClearable, true);
|
||||
pb.Add(a => a.Items, new List<SelectedItem>()
|
||||
pb.Add(a => a.Items, new List<SelectedItem<string>>()
|
||||
{
|
||||
new("", "请选择"),
|
||||
new("2", "Test2"),
|
||||
@@ -137,10 +144,10 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
pb.Add(a => a.Color, Color.Danger);
|
||||
});
|
||||
|
||||
var validPi = typeof(Select<string>).GetProperty("IsValid", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!;
|
||||
var validPi = typeof(SelectGeneric<string>).GetProperty("IsValid", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!;
|
||||
validPi.SetValue(select.Instance, true);
|
||||
|
||||
var pi = typeof(Select<string>).GetProperty("ClearClassString", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!;
|
||||
var pi = typeof(SelectGeneric<string>).GetProperty("ClearClassString", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!;
|
||||
val = pi.GetValue(select.Instance, null)!.ToString();
|
||||
Assert.Contains("text-success", val);
|
||||
|
||||
@@ -152,7 +159,7 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
[Fact]
|
||||
public void SelectOption_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<SelectOption>(pb =>
|
||||
var cut = Context.RenderComponent<SelectOptionGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Text, "Test-SelectOption");
|
||||
pb.Add(a => a.GroupName, "Test-GroupName");
|
||||
@@ -165,14 +172,14 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
[Fact]
|
||||
public void Enum_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<Select<EnumEducation>>();
|
||||
var cut = Context.RenderComponent<SelectGeneric<EnumEducation>>();
|
||||
Assert.Equal(2, cut.FindAll(".dropdown-item").Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullableEnum_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<Select<EnumEducation?>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<EnumEducation?>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.AdditionalAttributes, new Dictionary<string, object>()
|
||||
{
|
||||
@@ -188,9 +195,9 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
var triggered = false;
|
||||
|
||||
// 空值时,不触发 OnSelectedItemChanged 回调
|
||||
var cut = Context.RenderComponent<Select<string>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, new SelectedItem[]
|
||||
pb.Add(a => a.Items, new SelectedItem<string>[]
|
||||
{
|
||||
new("", "Test"),
|
||||
new("1", "Test2")
|
||||
@@ -202,35 +209,29 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
});
|
||||
Assert.False(triggered);
|
||||
Assert.True(triggered);
|
||||
|
||||
// 切换候选项时触发 OnSelectedItemChanged 回调测试
|
||||
await cut.InvokeAsync(() =>
|
||||
{
|
||||
var items = cut.FindAll(".dropdown-item");
|
||||
var count = items.Count;
|
||||
Assert.Equal(2, count);
|
||||
var items = cut.FindAll(".dropdown-item");
|
||||
var count = items.Count;
|
||||
Assert.Equal(2, count);
|
||||
|
||||
var item = items[1];
|
||||
item.Click();
|
||||
});
|
||||
var item = items[1];
|
||||
await cut.InvokeAsync(() => { item.Click(); });
|
||||
Assert.True(triggered);
|
||||
|
||||
// 切换回 空值 触发 OnSelectedItemChanged 回调测试
|
||||
triggered = false;
|
||||
await cut.InvokeAsync(() =>
|
||||
{
|
||||
var items = cut.FindAll(".dropdown-item");
|
||||
var item = items[0];
|
||||
item.Click();
|
||||
});
|
||||
items = cut.FindAll(".dropdown-item");
|
||||
item = items[0];
|
||||
await cut.InvokeAsync(() => { item.Click(); });
|
||||
Assert.True(triggered);
|
||||
|
||||
// 首次加载值不为空时触发 OnSelectedItemChanged 回调测试
|
||||
triggered = false;
|
||||
cut.SetParametersAndRender(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, new SelectedItem[]
|
||||
pb.Add(a => a.Items, new SelectedItem<string>[]
|
||||
{
|
||||
new("", "Test"),
|
||||
new("1", "Test1"),
|
||||
@@ -242,14 +243,11 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
|
||||
// 切换回 空值 触发 OnSelectedItemChanged 回调测试
|
||||
triggered = false;
|
||||
await cut.InvokeAsync(() =>
|
||||
{
|
||||
var items = cut.FindAll(".dropdown-item");
|
||||
var count = items.Count;
|
||||
Assert.Equal(3, count);
|
||||
var item = items[0];
|
||||
item.Click();
|
||||
});
|
||||
items = cut.FindAll(".dropdown-item");
|
||||
count = items.Count;
|
||||
Assert.Equal(3, count);
|
||||
item = items[0];
|
||||
await cut.InvokeAsync(() => { item.Click(); });
|
||||
Assert.True(triggered);
|
||||
}
|
||||
|
||||
@@ -257,7 +255,7 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
public async Task OnSelectedItemChanged_Generic()
|
||||
{
|
||||
Foo? selectedValue = null;
|
||||
var cut = Context.RenderComponent<Select<Foo>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<Foo>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, new SelectedItem<Foo>[]
|
||||
{
|
||||
@@ -291,9 +289,9 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
var triggered = false;
|
||||
|
||||
// 空值时,不触发 OnSelectedItemChanged 回调
|
||||
var cut = Context.RenderComponent<Select<string>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, new SelectedItem[]
|
||||
pb.Add(a => a.Items, new SelectedItem<string>[]
|
||||
{
|
||||
new("1", "Test"),
|
||||
new("2", "Test2")
|
||||
@@ -312,7 +310,7 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
[Fact]
|
||||
public void Color_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<Select<string>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Color, Color.Danger);
|
||||
});
|
||||
@@ -338,7 +336,7 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
builder.Add(a => a.Model, model);
|
||||
builder.AddChildContent<Select<string>>(pb =>
|
||||
builder.AddChildContent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Value, model.Name);
|
||||
pb.Add(a => a.OnValueChanged, v =>
|
||||
@@ -347,7 +345,7 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
pb.Add(a => a.ValueExpression, model.GenerateValueExpression());
|
||||
pb.Add(a => a.Items, new SelectedItem[]
|
||||
pb.Add(a => a.Items, new SelectedItem<string>[]
|
||||
{
|
||||
new("", "Test"),
|
||||
new("1", "Test1") { GroupName = "Test1" },
|
||||
@@ -363,7 +361,7 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
Assert.True(valid);
|
||||
});
|
||||
|
||||
var ctx = cut.FindComponent<Select<string>>();
|
||||
var ctx = cut.FindComponent<SelectGeneric<string>>();
|
||||
ctx.InvokeAsync(async () =>
|
||||
{
|
||||
await ctx.Instance.ConfirmSelectedItem(0);
|
||||
@@ -376,9 +374,9 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
[Fact]
|
||||
public void ItemTemplate_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<Select<string>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, new SelectedItem[]
|
||||
pb.Add(a => a.Items, new SelectedItem<string>[]
|
||||
{
|
||||
new("1", "Test1") { GroupName = "Test1" },
|
||||
new("2", "Test2") { GroupName = "Test2" }
|
||||
@@ -398,9 +396,9 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
[Fact]
|
||||
public void GroupItemTemplate_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<Select<string>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, new SelectedItem[]
|
||||
pb.Add(a => a.Items, new SelectedItem<string>[]
|
||||
{
|
||||
new("1", "Test1") { GroupName = "Test1" },
|
||||
new("2", "Test2") { GroupName = "Test2" }
|
||||
@@ -421,19 +419,19 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
[Fact]
|
||||
public void NullItems_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<Select<string>>();
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>();
|
||||
Assert.Contains("select", cut.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullBool_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<Select<bool?>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<bool?>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, new List<SelectedItem>
|
||||
pb.Add(a => a.Items, new List<SelectedItem<bool?>>
|
||||
{
|
||||
new("true", "True"),
|
||||
new("false", "False"),
|
||||
new(true, "True"),
|
||||
new(false, "False"),
|
||||
});
|
||||
pb.Add(a => a.Value, null);
|
||||
});
|
||||
@@ -443,32 +441,12 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
Assert.True(cut.Instance.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectItem_Ok()
|
||||
{
|
||||
var v = new SelectedItem("2", "Text2");
|
||||
var cut = Context.RenderComponent<Select<SelectedItem>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, new List<SelectedItem>
|
||||
{
|
||||
new("1", "Text1"),
|
||||
new("2", "Text2"),
|
||||
});
|
||||
pb.Add(a => a.Value, v);
|
||||
pb.Add(a => a.ValueChanged, EventCallback.Factory.Create<SelectedItem?>(this, i => v = i));
|
||||
});
|
||||
Assert.Equal("2", cut.Instance.Value.Value);
|
||||
|
||||
cut.InvokeAsync(() => cut.Find(".dropdown-item").Click());
|
||||
Assert.Equal("1", cut.Instance.Value.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SearchIcon_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<Select<string>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, new SelectedItem[]
|
||||
pb.Add(a => a.Items, new SelectedItem<string>[]
|
||||
{
|
||||
new("1", "Test1"),
|
||||
new("2", "Test2")
|
||||
@@ -483,9 +461,9 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
[Fact]
|
||||
public void IsFixedSearch_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<Select<string>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, new SelectedItem[]
|
||||
pb.Add(a => a.Items, new SelectedItem<string>[]
|
||||
{
|
||||
new("1", "Test1"),
|
||||
new("2", "Test2")
|
||||
@@ -501,9 +479,9 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
[Fact]
|
||||
public void CustomClass_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<Select<string>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, new SelectedItem[]
|
||||
pb.Add(a => a.Items, new SelectedItem<string>[]
|
||||
{
|
||||
new("1", "Test1"),
|
||||
new("2", "Test2")
|
||||
@@ -517,9 +495,9 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
[Fact]
|
||||
public void ShowShadow_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<Select<string>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, new SelectedItem[]
|
||||
pb.Add(a => a.Items, new SelectedItem<string>[]
|
||||
{
|
||||
new("1", "Test1"),
|
||||
new("2", "Test2")
|
||||
@@ -538,9 +516,9 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
[Fact]
|
||||
public void DropdownIcon_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<Select<string>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, new SelectedItem[]
|
||||
pb.Add(a => a.Items, new SelectedItem<string>[]
|
||||
{
|
||||
new("1", "Test1"),
|
||||
new("2", "Test2")
|
||||
@@ -554,9 +532,9 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
[Fact]
|
||||
public void DisplayTemplate_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<Select<string>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, new SelectedItem[]
|
||||
pb.Add(a => a.Items, new SelectedItem<string>[]
|
||||
{
|
||||
new("1", "Test1"),
|
||||
new("2", "Test2")
|
||||
@@ -573,9 +551,9 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
[Fact]
|
||||
public void IsPopover_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<Select<string>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, new SelectedItem[]
|
||||
pb.Add(a => a.Items, new SelectedItem<string>[]
|
||||
{
|
||||
new("1", "Test1"),
|
||||
new("2", "Test2")
|
||||
@@ -590,9 +568,9 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
[Fact]
|
||||
public void Offset_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<Select<string>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, new SelectedItem[]
|
||||
pb.Add(a => a.Items, new SelectedItem<string>[]
|
||||
{
|
||||
new("1", "Test1"),
|
||||
new("2", "Test2")
|
||||
@@ -607,9 +585,9 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
[Fact]
|
||||
public void Placement_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<Select<string>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, new SelectedItem[]
|
||||
pb.Add(a => a.Items, new SelectedItem<string>[]
|
||||
{
|
||||
new("1", "Test1"),
|
||||
new("2", "Test2")
|
||||
@@ -629,9 +607,9 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
[Fact]
|
||||
public void ItemClick_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<Select<string>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, new SelectedItem[]
|
||||
pb.Add(a => a.Items, new SelectedItem<string>[]
|
||||
{
|
||||
new("1", "Test1"),
|
||||
new("2", "Test2")
|
||||
@@ -651,9 +629,9 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
[Fact]
|
||||
public void IsVirtualize_Items()
|
||||
{
|
||||
var cut = Context.RenderComponent<Select<string>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, new SelectedItem[]
|
||||
pb.Add(a => a.Items, new SelectedItem<string>[]
|
||||
{
|
||||
new("1", "Test1"),
|
||||
new("2", "Test2")
|
||||
@@ -676,9 +654,9 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
[Fact]
|
||||
public async Task IsVirtualize_Items_Clearable_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<Select<string>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, new SelectedItem[]
|
||||
pb.Add(a => a.Items, new SelectedItem<string>[]
|
||||
{
|
||||
new("1", "Test1"),
|
||||
new("2", "Test2")
|
||||
@@ -727,7 +705,7 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
var startIndex = 0;
|
||||
var requestCount = 0;
|
||||
var searchText = string.Empty;
|
||||
var cut = Context.RenderComponent<Select<string>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.OnQueryAsync, option =>
|
||||
{
|
||||
@@ -735,7 +713,7 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
startIndex = option.StartIndex;
|
||||
requestCount = option.Count;
|
||||
searchText = option.SearchText;
|
||||
return Task.FromResult(new QueryData<SelectedItem>()
|
||||
return Task.FromResult(new QueryData<SelectedItem<string>>()
|
||||
{
|
||||
Items = string.IsNullOrEmpty(searchText)
|
||||
? [new("", "All"), new("1", "Test1"), new("2", "Test2")]
|
||||
@@ -779,22 +757,22 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsVirtualize_BindValue()
|
||||
public async Task IsVirtualize_BindValue()
|
||||
{
|
||||
var value = new SelectedItem("3", "Test 3");
|
||||
var cut = Context.RenderComponent<Select<SelectedItem>>(pb =>
|
||||
var value = "3";
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Value, value);
|
||||
pb.Add(a => a.IsVirtualize, true);
|
||||
pb.Add(a => a.ValueChanged, EventCallback.Factory.Create<SelectedItem?>(this, new Action<SelectedItem?>(item =>
|
||||
pb.Add(a => a.ValueChanged, EventCallback.Factory.Create<string?>(this, new Action<string?>(item =>
|
||||
{
|
||||
value = item;
|
||||
})));
|
||||
pb.Add(a => a.OnQueryAsync, option =>
|
||||
{
|
||||
return Task.FromResult(new QueryData<SelectedItem>()
|
||||
return Task.FromResult(new QueryData<SelectedItem<string>>()
|
||||
{
|
||||
Items = new SelectedItem[]
|
||||
Items = new SelectedItem<string>[]
|
||||
{
|
||||
new("1", "Test1"),
|
||||
new("2", "Test2")
|
||||
@@ -804,31 +782,25 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
});
|
||||
});
|
||||
|
||||
cut.InvokeAsync(() =>
|
||||
{
|
||||
var input = cut.Find(".form-select");
|
||||
Assert.Equal("Test 3", input.GetAttribute("value"));
|
||||
});
|
||||
cut.Contains("Test 3");
|
||||
var input = cut.Find(".form-select");
|
||||
Assert.Null(input.GetAttribute("value"));
|
||||
|
||||
var select = cut.Instance;
|
||||
Assert.Equal("3", select.Value?.Value);
|
||||
Assert.Equal("3", select.Value);
|
||||
|
||||
cut.InvokeAsync(() =>
|
||||
{
|
||||
var item = cut.Find(".dropdown-item");
|
||||
item.Click();
|
||||
Assert.Equal("1", value.Value);
|
||||
var item = cut.Find(".dropdown-item");
|
||||
await cut.InvokeAsync(() => { item.Click(); });
|
||||
Assert.Equal("1", value);
|
||||
|
||||
var input = cut.Find(".form-select");
|
||||
Assert.Equal("Test1", input.GetAttribute("value"));
|
||||
});
|
||||
input = cut.Find(".form-select");
|
||||
Assert.Equal("Test1", input.GetAttribute("value"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsVirtualize_DefaultVirtualizeItemText()
|
||||
{
|
||||
string? value = "3";
|
||||
var cut = Context.RenderComponent<Select<string>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.IsVirtualize, true);
|
||||
pb.Add(a => a.DefaultVirtualizeItemText, "Test 3");
|
||||
@@ -839,9 +811,9 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
})));
|
||||
pb.Add(a => a.OnQueryAsync, option =>
|
||||
{
|
||||
return Task.FromResult(new QueryData<SelectedItem>()
|
||||
return Task.FromResult(new QueryData<SelectedItem<string>>()
|
||||
{
|
||||
Items = new SelectedItem[]
|
||||
Items = new SelectedItem<string>[]
|
||||
{
|
||||
new("1", "Test1"),
|
||||
new("2", "Test2")
|
||||
@@ -861,11 +833,11 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
[Fact]
|
||||
public void LoadItems_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<Select<string>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.OnQueryAsync, option =>
|
||||
{
|
||||
return Task.FromResult(new QueryData<SelectedItem>());
|
||||
return Task.FromResult(new QueryData<SelectedItem<string>>());
|
||||
});
|
||||
pb.Add(a => a.Value, "2");
|
||||
pb.Add(a => a.IsVirtualize, true);
|
||||
@@ -879,40 +851,12 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
mi?.Invoke(select, [new ItemsProviderRequest(0, 1, CancellationToken.None)]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParseValueFromString_Ok()
|
||||
{
|
||||
var items = new SelectedItem[]
|
||||
{
|
||||
new("1", "Test1"),
|
||||
new("2", "Test2")
|
||||
};
|
||||
var cut = Context.RenderComponent<Select<SelectedItem>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, items);
|
||||
pb.Add(a => a.Value, new SelectedItem("1", "Test1"));
|
||||
pb.Add(a => a.IsVirtualize, true);
|
||||
});
|
||||
var select = cut.Instance;
|
||||
var mi = select.GetType().GetMethod("TryParseSelectItem", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
string value = "";
|
||||
SelectedItem result = new();
|
||||
string? msg = null;
|
||||
mi?.Invoke(select, [value, result, msg]);
|
||||
|
||||
var p = select.GetType().GetProperty("VirtualItems", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
p?.SetValue(select, items);
|
||||
value = "1";
|
||||
mi?.Invoke(select, [value, result, msg]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsMarkupString_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<Select<string>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, new SelectedItem[]
|
||||
pb.Add(a => a.Items, new SelectedItem<string>[]
|
||||
{
|
||||
new("1", "<div>Test1</div>"),
|
||||
new("2", "<div>Test2</div>")
|
||||
@@ -926,9 +870,9 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
[Fact]
|
||||
public async Task IsEditable_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<Select<string>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, new SelectedItem[]
|
||||
pb.Add(a => a.Items, new SelectedItem<string>[]
|
||||
{
|
||||
new("1", "<div>Test1</div>"),
|
||||
new("2", "<div>Test2</div>")
|
||||
@@ -947,13 +891,14 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
updated = true;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
pb.Add(a => a.TextConvertToValueCallback, v =>
|
||||
{
|
||||
return Task.FromResult(v);
|
||||
});
|
||||
});
|
||||
Assert.False(input.IsReadOnly());
|
||||
|
||||
await cut.InvokeAsync(() =>
|
||||
{
|
||||
input.Change("Test3");
|
||||
});
|
||||
await cut.InvokeAsync(() => { input.Change("Test3"); });
|
||||
Assert.Equal("Test3", cut.Instance.Value);
|
||||
Assert.True(updated);
|
||||
}
|
||||
@@ -966,7 +911,7 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
new() { Value = new Foo() { Id = 1, Address = "Foo1" }, Text = "test1" },
|
||||
new() { Value = new Foo() { Id = 2, Address = "Foo2" }, Text = "test2" }
|
||||
};
|
||||
var cut = Context.RenderComponent<Select<Foo>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<Foo>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, items);
|
||||
pb.Add(a => a.Value, new Foo() { Id = 1, Address = "Foo1" });
|
||||
@@ -989,9 +934,9 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
public async Task OnClearAsync_Ok()
|
||||
{
|
||||
var clear = false;
|
||||
var cut = Context.RenderComponent<Select<string>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, new SelectedItem[]
|
||||
pb.Add(a => a.Items, new SelectedItem<string>[]
|
||||
{
|
||||
new("1", "<div>Test1</div>"),
|
||||
new("2", "<div>Test2</div>")
|
||||
@@ -1019,9 +964,9 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
[Fact]
|
||||
public async Task Toggle_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<Select<string>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, new SelectedItem[]
|
||||
pb.Add(a => a.Items, new SelectedItem<string>[]
|
||||
{
|
||||
new("1", "Test1"),
|
||||
new("2", "Test2")
|
||||
@@ -1047,7 +992,7 @@ public class SelectTest : BootstrapBlazorTestBase
|
||||
Text = "Foo2"
|
||||
}
|
||||
};
|
||||
var cut = Context.RenderComponent<Select<Foo>>(pb =>
|
||||
var cut = Context.RenderComponent<SelectGeneric<Foo>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, items);
|
||||
});
|
||||
|
||||
@@ -212,9 +212,9 @@ public class SwalTest : BootstrapBlazorTestBase
|
||||
// 带确认框的 Select
|
||||
cut.SetParametersAndRender(pb =>
|
||||
{
|
||||
pb.AddChildContent<Select<string>>(pb =>
|
||||
pb.AddChildContent<SelectGeneric<string>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.Items, new List<SelectedItem>()
|
||||
pb.Add(a => a.Items, new List<SelectedItem<string>>()
|
||||
{
|
||||
new("1", "Test1"),
|
||||
new("2", "Test2") { IsDisabled = true }
|
||||
@@ -228,7 +228,7 @@ public class SwalTest : BootstrapBlazorTestBase
|
||||
});
|
||||
});
|
||||
|
||||
Task.Run(() => cut.InvokeAsync(() => cut.FindComponent<Select<string>>().Instance.ConfirmSelectedItem(0)));
|
||||
Task.Run(() => cut.InvokeAsync(() => cut.FindComponent<SelectGeneric<string>>().Instance.ConfirmSelectedItem(0)));
|
||||
tick = DateTime.Now;
|
||||
while (!cut.Markup.Contains("test-swal-footer"))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user