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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,14 @@ private AttributeItem[] GetAttributes() =>
DefaultValue = "false"
},
new()
{
Name = "IsAutoClearSearchTextWhenCollapsed",
Description = Localizer["SelectsIsAutoClearSearchTextWhenCollapsed"],
Type = "bool",
ValueList = "true|false",
DefaultValue = "false"
},
new()
{
Name = "DisplayText",
Description = Localizer["SelectsDisplayText"],
Expand Down
3 changes: 2 additions & 1 deletion src/BootstrapBlazor.Server/Locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -3211,7 +3211,7 @@
"SelectsCustomTemplateTitle": "Custom option templates",
"SelectsCustomTemplateIntro": "By setting the <code>ItemTemplate</code> you can customize the option rendering style",
"SelectsShowSearchTitle": "Drop-down box with search box",
"SelectsShowSearchIntro": "Controls whether the search box is displayed by setting the <code>ShowSearch</code> property, which is not displayed by default <b>false</b>",
"SelectsShowSearchIntro": "Controls whether the search box is displayed by setting the <code>ShowSearch</code> property, which is not displayed by default <b>false</b>. You can set the <code>IsAutoClearSearchTextWhenCollapsed</code> parameter to control whether the text in the search box is automatically cleared after the drop-down box is collapsed. The default value is <b>false</b>.",
"SelectsConfirmSelectTitle": "Drop-down box with confirmation",
"SelectsConfirmSelectIntro": "Block changes to the current value by setting the <code>OnBeforeSelectedItemChange</code> delegate.",
"SelectsTimeZoneTitle": "Timezone",
Expand All @@ -3221,6 +3221,7 @@
"SwalFooter": "Click Confirm to change the option value and select Cancel to leave the value unchanged",
"SelectsShowLabel": "Whether to display the front label",
"SelectsShowSearch": "Whether to display the search box",
"SelectsIsAutoClearSearchTextWhenCollapsed": "Whether to automatically clear the search bar when the drop-down box is collapsed",
"SelectsDisplayText": "The front label displays text",
"SelectsClass": "Style",
"SelectsColor": "Color",
Expand Down
3 changes: 2 additions & 1 deletion src/BootstrapBlazor.Server/Locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -3211,7 +3211,7 @@
"SelectsCustomTemplateTitle": "自定义选项模板",
"SelectsCustomTemplateIntro": "通过设置 <code>ItemTemplate</code> 可以自定义选项渲染样式",
"SelectsShowSearchTitle": "带搜索框的下拉框",
"SelectsShowSearchIntro": "通过设置 <code>ShowSearch</code> 属性控制是否显示搜索框,默认为 <b>false</b> 不显示搜索框",
"SelectsShowSearchIntro": "通过设置 <code>ShowSearch</code> 属性控制是否显示搜索框,默认为 <b>false</b> 不显示搜索框,可以通过设置 <code>IsAutoClearSearchTextWhenCollapsed</code> 参数控制下拉框收起后是否自动清空搜索框内文字,默认值为 <b>false</b> 不清空",
"SelectsConfirmSelectTitle": "带确认的下拉框",
"SelectsConfirmSelectIntro": "通过设置 <code>OnBeforeSelectedItemChange</code> 委托,阻止当前值的改变",
"SelectsTimeZoneTitle": "时区下拉框",
Expand All @@ -3221,6 +3221,7 @@
"SwalFooter": "点击确认改变选项值,选择取消后值不变",
"SelectsShowLabel": "是否显示前置标签",
"SelectsShowSearch": "是否显示搜索框",
"SelectsIsAutoClearSearchTextWhenCollapsed": "下拉框收起时是否自动清空搜索栏",
"SelectsDisplayText": "前置标签显示文本",
"SelectsClass": "样式",
"SelectsColor": "颜色",
Expand Down
2 changes: 1 addition & 1 deletion src/BootstrapBlazor/BootstrapBlazor.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">

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

<ItemGroup>
Expand Down
166 changes: 95 additions & 71 deletions src/BootstrapBlazor/Components/Select/Select.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,13 @@ public partial class Select<TValue> : ISelect, ILookup
[NotNull]
private SwalService? SwalService { get; set; }

private string? ClassString => CssBuilder.Default("select dropdown")
.AddClass("is-clearable", IsClearable)
.AddClassFromAttributes(AdditionalAttributes)
.Build();

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? ActiveItem(SelectedItem item) => CssBuilder.Default("dropdown-item")
.AddClass("active", item.Value == CurrentValueAsString)
.AddClass("disabled", item.IsDisabled)
.Build();

private readonly List<SelectedItem> _children = [];
[Inject]
[NotNull]
private IStringLocalizer<Select<TValue>>? Localizer { get; set; }

private string? ScrollIntoViewBehaviorString => ScrollIntoViewBehavior == ScrollIntoViewBehavior.Smooth ? null : ScrollIntoViewBehavior.ToDescriptionString();
[Inject]
[NotNull]
private ILookupService? InjectLookupService { get; set; }

/// <summary>
/// Gets or sets the display template. Default is null.
Expand Down Expand Up @@ -125,31 +112,47 @@ public partial class Select<TValue> : ISelect, ILookup
public string? DefaultVirtualizeItemText { get; set; }

/// <summary>
/// <inheritdoc/>
/// Gets or sets whether auto clear the search text when dropdown closed.
/// </summary>
IEnumerable<SelectedItem>? ILookup.Lookup { get; set; }
[Parameter]
public bool IsAutoClearSearchTextWhenCollapsed { get; set; }

/// <summary>
/// <inheritdoc/>
/// Gets or sets the dropdown collapsed callback method.
/// </summary>
StringComparison ILookup.LookupStringComparison { get; set; }
[Parameter]
public Func<Task>? OnCollapsed { get; set; }

[Inject]
[NotNull]
private IStringLocalizer<Select<TValue>>? Localizer { get; set; }
IEnumerable<SelectedItem>? ILookup.Lookup { get => Items; set => Items = value; }

/// <summary>
/// Gets or sets the injected lookup service instance.
/// </summary>
[Inject]
[NotNull]
private ILookupService? InjectLookupService { get; set; }
StringComparison ILookup.LookupStringComparison { get => StringComparison; set => StringComparison = value; }

/// <summary>
/// <inheritdoc/>
/// </summary>
protected override string? RetrieveId() => InputId;

private string? ClassString => CssBuilder.Default("select dropdown")
.AddClass("is-clearable", IsClearable)
.AddClassFromAttributes(AdditionalAttributes)
.Build();

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? ActiveItem(SelectedItem item) => CssBuilder.Default("dropdown-item")
.AddClass("active", item.Value == CurrentValueAsString)
.AddClass("disabled", item.IsDisabled)
.Build();

private readonly List<SelectedItem> _children = [];

private string? ScrollIntoViewBehaviorString => ScrollIntoViewBehavior == ScrollIntoViewBehavior.Smooth ? null : ScrollIntoViewBehavior.ToDescriptionString();

private string? InputId => $"{Id}_input";

private bool _init = true;
Expand All @@ -169,44 +172,6 @@ private SelectedItem? SelectedRow
}
}

private SelectedItem? GetSelectedRow()
{
if (Value is null)
{
_lastSelectedValueString = "";
_init = false;
return null;
}

var item = IsVirtualize ? GetItemByVirtualized() : GetItemByRows();
if (item != null)
{
if (_init && DisableItemChangedWhenFirstRender)
{

}
else
{
_ = SelectedItemChanged(item);
_init = false;
}
}
return item;
}

private SelectedItem? GetItemWithEnumValue() => ValueType.IsEnum ? Rows.Find(i => i.Value == Convert.ToInt32(Value).ToString()) : null;

private SelectedItem GetItemByVirtualized() => new(CurrentValueAsString, _defaultVirtualizedItemText);

private SelectedItem? GetItemByRows()
{
var item = GetItemWithEnumValue()
?? Rows.Find(i => i.Value == CurrentValueAsString)
?? Rows.Find(i => i.Active)
?? Rows.FirstOrDefault(i => !i.IsDisabled);
return item;
}

/// <summary>
/// <inheritdoc/>
/// </summary>
Expand Down Expand Up @@ -308,9 +273,30 @@ private bool TryParseSelectItem(string value, [MaybeNullWhen(false)] out TValue
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, new
{
ConfirmMethodCallback = nameof(ConfirmSelectedItem),
SearchMethodCallback = nameof(TriggerOnSearch)
SearchMethodCallback = nameof(TriggerOnSearch),
TriggerCollapsed = (OnCollapsed != null || IsAutoClearSearchTextWhenCollapsed) ? nameof(TriggerCollapsed) : null
});

/// <summary>
/// Trigger <see cref="OnCollapsed"/> event callback method. called by JavaScript.
/// </summary>
/// <returns></returns>
[JSInvokable]
public async Task TriggerCollapsed()
{
if (OnCollapsed != null)
{
await OnCollapsed();
}

if (IsAutoClearSearchTextWhenCollapsed)
{
SearchText = string.Empty;
_itemsCache = null;
StateHasChanged();
}
}

/// <summary>
/// <inheritdoc/>
/// </summary>
Expand Down Expand Up @@ -440,4 +426,42 @@ private async Task OnChange(ChangeEventArgs args)
}
}
}

private SelectedItem? GetSelectedRow()
{
if (Value is null)
{
_lastSelectedValueString = "";
_init = false;
return null;
}

var item = IsVirtualize ? GetItemByVirtualized() : GetItemByRows();
if (item != null)
{
if (_init && DisableItemChangedWhenFirstRender)
{

}
else
{
_ = SelectedItemChanged(item);
_init = false;
}
}
return item;
}

private SelectedItem? GetItemWithEnumValue() => ValueType.IsEnum ? Rows.Find(i => i.Value == Convert.ToInt32(Value).ToString()) : null;

private SelectedItem GetItemByVirtualized() => new(CurrentValueAsString, _defaultVirtualizedItemText);

private SelectedItem? GetItemByRows()
{
var item = GetItemWithEnumValue()
?? Rows.Find(i => i.Value == CurrentValueAsString)
?? Rows.Find(i => i.Active)
?? Rows.FirstOrDefault(i => !i.IsDisabled);
return item;
}
}
8 changes: 7 additions & 1 deletion src/BootstrapBlazor/Components/Select/Select.razor.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ export function init(id, invoke, options) {
}

const search = el.querySelector(".search-text")
const popover = Popover.init(el)
const popover = Popover.init(el, {
hideCallback: () => {
if (options.triggerCollapsed) {
invoke.invokeMethodAsync(options.triggerCollapsed);
}
}
});
const input = el.querySelector(`#${id}_input`);
const select = {
el, invoke, options,
Expand Down
28 changes: 28 additions & 0 deletions test/UnitTest/Components/SelectTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,34 @@ public void Color_Ok()
Assert.Contains("border-danger", cut.Markup);
}

[Fact]
public async Task OnCollapsed_Ok()
{
var collapsed = false;
var cut = Context.RenderComponent<Select<string>>(pb =>
{
pb.Add(a => a.OnCollapsed, () => { collapsed = true; return Task.CompletedTask; });
});
await cut.InvokeAsync(() => cut.Instance.TriggerCollapsed());
Assert.True(collapsed);
}

[Fact]
public async Task IsAutoClearSearchTextWhenCollapsed_Ok()
{
var cut = Context.RenderComponent<Select<string>>(pb =>
{
pb.Add(a => a.ShowSearch, true);
pb.Add(a => a.IsAutoClearSearchTextWhenCollapsed, true);
});

await cut.InvokeAsync(() => cut.Instance.TriggerOnSearch("123"));
cut.Contains("value=\"123\"");

await cut.InvokeAsync(() => cut.Instance.TriggerCollapsed());
cut.Contains("value=\"\"");
}

[Fact]
public void Validate_Ok()
{
Expand Down