mirror of
https://gitee.com/ant-design-blazor/ant-design-blazor.git
synced 2024-11-29 18:48:50 +08:00
fix(module select): simulate setTimeout, ensure that the click event is processed properly (#3525)
* fix(module select): dom symmetrical difference event * refactor: selector * fix: test fix: bunit cannot handle js event --------- Co-authored-by: James Yeung <shunjiey@hotmail.com>
This commit is contained in:
parent
517fee6316
commit
fd73cc980f
@ -423,7 +423,7 @@ namespace AntDesign.Internal
|
||||
/// </summary>
|
||||
/// <param name="args">MouseEventArgs</param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task OnClickDiv(MouseEventArgs args)
|
||||
protected virtual async Task OnClickDiv(MouseEventArgs args)
|
||||
{
|
||||
if (!IsButton)
|
||||
{
|
||||
@ -479,6 +479,10 @@ namespace AntDesign.Internal
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// on document mouse up
|
||||
/// </summary>
|
||||
/// <param name="element"></param>
|
||||
protected virtual void OnMouseUp(JsonElement element)
|
||||
{
|
||||
if (_mouseUpInOverlay)
|
||||
@ -491,10 +495,10 @@ namespace AntDesign.Internal
|
||||
{
|
||||
if (OnMaskClick.HasDelegate)
|
||||
{
|
||||
OnMaskClick.InvokeAsync(null);
|
||||
_ = OnMaskClick.InvokeAsync(null);
|
||||
}
|
||||
|
||||
Hide();
|
||||
_ = Hide();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ namespace AntDesign.JsInterop
|
||||
return new DomEventKey(selector, eventName, _id);
|
||||
}
|
||||
|
||||
public void AddExclusive<T>(object dom, string eventName, Action<T> callback, bool preventDefault = false)
|
||||
public void AddExclusive<T>(object dom, string eventName, Action<T> callback, bool preventDefault = false, bool stopPropagation = false)
|
||||
{
|
||||
var key = FormatKey(dom, eventName);
|
||||
if (_exclusiveDotNetObjectStore.ContainsKey(key))
|
||||
@ -48,7 +48,7 @@ namespace AntDesign.JsInterop
|
||||
{
|
||||
callback(p);
|
||||
}));
|
||||
_jsRuntime.InvokeAsync<string>(JSInteropConstants.AddDomEventListener, dom, eventName, preventDefault, dotNetObject);
|
||||
_jsRuntime.InvokeAsync<string>(JSInteropConstants.AddDomEventListener, dom, eventName, preventDefault, dotNetObject, stopPropagation);
|
||||
_exclusiveDotNetObjectStore.Add(key, dotNetObject);
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ namespace AntDesign.JsInterop
|
||||
|
||||
void AddEventListenerToFirstChild<T>(object dom, string eventName, Action<T> callback, bool preventDefault = false);
|
||||
|
||||
void AddExclusive<T>(object dom, string eventName, Action<T> callback, bool preventDefault = false);
|
||||
void AddExclusive<T>(object dom, string eventName, Action<T> callback, bool preventDefault = false, bool stopPropagation = false);
|
||||
|
||||
ValueTask AddResizeObserver(ElementReference dom, Action<List<ResizeObserverEntry>> callback);
|
||||
|
||||
|
@ -10,7 +10,7 @@ export class eventHelper {
|
||||
return element.dispatchEvent(evt);
|
||||
}
|
||||
|
||||
static addDomEventListener(element, eventName: string, preventDefault: boolean, invoker: any) {
|
||||
static addDomEventListener(element, eventName: string, preventDefault: boolean, invoker: any, stopPropagation: boolean = false) {
|
||||
const callback = args => {
|
||||
const obj = {};
|
||||
for (let k in args) {
|
||||
@ -28,6 +28,9 @@ export class eventHelper {
|
||||
if (preventDefault === true) {
|
||||
args.preventDefault();
|
||||
}
|
||||
if (stopPropagation) {
|
||||
args.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
const dom = domInfoHelper.get(element);
|
||||
|
@ -110,7 +110,7 @@ namespace AntDesign
|
||||
/// </summary>
|
||||
/// <param name="args">MouseEventArgs</param>
|
||||
/// <returns></returns>
|
||||
public override async Task OnClickDiv(MouseEventArgs args)
|
||||
protected override async Task OnClickDiv(MouseEventArgs args)
|
||||
{
|
||||
if (!IsButton)
|
||||
{
|
||||
|
@ -7,7 +7,6 @@
|
||||
|
||||
<CascadingValue Value="this" IsFixed="@(!IsGroupingEnabled)">
|
||||
<CascadingValue Value=@("ant-select-dropdown") Name="PrefixCls" IsFixed>
|
||||
<div class="@ClassMapper.Class" style="@Style" id="@Id" tabindex="-1" @ref="Ref">
|
||||
<OverlayTrigger @ref="@_dropDown"
|
||||
Visible="Open"
|
||||
Disabled="Disabled"
|
||||
@ -106,15 +105,15 @@
|
||||
<CascadingValue Value="@MaxTagPlaceholder" Name="ParentMaxTagPlaceholerTemplate">
|
||||
<CascadingValue Value="@ShowSearchIcon" Name="ShowSearchIcon">
|
||||
<CascadingValue Value="@ShowArrowIcon" Name="ShowArrowIcon">
|
||||
<SelectContent Prefix="@ClassPrefix"
|
||||
<SelectContent Prefix="@ClassPrefix"
|
||||
RefBack="@context"
|
||||
@ref="_selectContent"
|
||||
TItemValue="TItemValue"
|
||||
TItem="TItem"
|
||||
SearchValue="@_searchValue"
|
||||
SearchDebounceMilliseconds="@SearchDebounceMilliseconds"
|
||||
SearchDebounceMilliseconds="@SearchDebounceMilliseconds"
|
||||
IsOverlayShow="@_dropDown.IsOverlayShow()"
|
||||
MaxTagCount="@_maxTagCountAsInt"
|
||||
MaxTagCount="@_maxTagCountAsInt"
|
||||
OnInput="@OnInputAsync"
|
||||
OnKeyUp="@OnKeyUpAsync"
|
||||
OnKeyDown="@OnKeyDownAsync"
|
||||
@ -131,7 +130,6 @@
|
||||
</CascadingValue>
|
||||
</Unbound>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
|
||||
|
@ -791,7 +791,7 @@ namespace AntDesign
|
||||
protected async Task SetDropdownStyleAsync()
|
||||
{
|
||||
string maxWidth = "", minWidth = "", definedWidth = "";
|
||||
var domRect = await JsInvokeAsync<DomRect>(JSInteropConstants.GetBoundingClientRect, Ref);
|
||||
var domRect = await JsInvokeAsync<DomRect>(JSInteropConstants.GetBoundingClientRect, _selectContent.Ref);
|
||||
var width = domRect.Width.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture);
|
||||
minWidth = $"min-width: {width}px;";
|
||||
if (DropdownMatchSelectWidth.IsT0 && DropdownMatchSelectWidth.AsT0)
|
||||
|
@ -59,6 +59,8 @@ namespace AntDesign
|
||||
|
||||
internal RenderFragment FeedbackIcon => FormItem?.FeedbackIcon;
|
||||
|
||||
internal ClassMapper CurrentClassMapper => ClassMapper;
|
||||
|
||||
/// <summary>
|
||||
/// Overlay adjustment strategy (when for example browser resize is happening)
|
||||
/// </summary>
|
||||
@ -981,7 +983,7 @@ namespace AntDesign
|
||||
/// Check if Focused property is False; Set the Focused property to true, change the
|
||||
/// style and set the Focus on the Input element via DOM. It also invoke the OnFocus Action.
|
||||
/// </summary>
|
||||
protected async Task SetInputFocusAsync()
|
||||
internal async Task SetInputFocusAsync()
|
||||
{
|
||||
if (!Focused)
|
||||
{
|
||||
@ -1004,11 +1006,6 @@ namespace AntDesign
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task OnArrowClick(MouseEventArgs args)
|
||||
{
|
||||
await _dropDown.OnClickDiv(args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Close the overlay
|
||||
/// </summary>
|
||||
|
@ -1,218 +1,33 @@
|
||||
@using AntDesign
|
||||
@using System.Text.Json
|
||||
@using AntDesign.JsInterop
|
||||
@namespace AntDesign.Select.Internal
|
||||
@typeparam TItemValue
|
||||
@typeparam TItem
|
||||
@inherits AntDomComponentBase
|
||||
|
||||
@if (ParentSelect.SelectMode == SelectMode.Default)
|
||||
{
|
||||
<div class="@Prefix-selector" @ref="@Ref" style="@(ParentSelect.PrefixIcon is null?"":"padding-left: 4px;")">
|
||||
@if (ParentSelect.PrefixIcon != null)
|
||||
<div class="@ParentSelect.CurrentClassMapper.Class"
|
||||
style="@ParentSelect.Style"
|
||||
id="@Id"
|
||||
tabindex="-1"
|
||||
@ref="@Ref"
|
||||
>
|
||||
@if (ParentSelect.SelectMode == SelectMode.Default)
|
||||
{
|
||||
<span class="@Prefix-prefix" unselectable="on" aria-hidden="true" style="user-select: none;display:flex;align-items: center;padding-right: 4px;">
|
||||
@ParentSelect.PrefixIcon
|
||||
</span>
|
||||
}
|
||||
<span class="@Prefix-selection-search" style="@_inputWidth">
|
||||
<input @ref="ParentSelect._inputRef"
|
||||
@oninput="@OnInputChange"
|
||||
@onkeyup="OnKeyUp"
|
||||
@onkeydown="OnKeyDown"
|
||||
@attributes=@AdditonalAttributes()
|
||||
@bind-value="@SearchValue"
|
||||
id="@(ParentSelect.Id)_list"
|
||||
type="search"
|
||||
readonly="@(!ParentSelect.IsSearchEnabled)"
|
||||
unselectable="@(ParentSelect.IsSearchEnabled ? false : "on")"
|
||||
role="combobox"
|
||||
class="@Prefix-selection-search-input"
|
||||
autocomplete="off"
|
||||
aria-owns="@(ParentSelect.Id)_list"
|
||||
aria-expanded="@IsOverlayShow"
|
||||
aria-autocomplete="list"
|
||||
aria-controls="@(ParentSelect.Id)_list"
|
||||
aria-haspopup="listbox"
|
||||
accesskey="@ParentSelect?.AccessKey"
|
||||
style="@_inputStyle" />
|
||||
</span>
|
||||
@if (ShowPlaceholder)
|
||||
{
|
||||
<span class="@Prefix-selection-placeholder">@Placeholder</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
var selectedItem = ParentSelect.SelectedOptionItems.FirstOrDefault();
|
||||
if (string.IsNullOrEmpty(SearchValue) && selectedItem != null)
|
||||
{
|
||||
@if (ParentLabelTemplate != null)
|
||||
<div class="@Prefix-selector" style="@(ParentSelect.PrefixIcon is null?"":"padding-left: 4px;")">
|
||||
@if (ParentSelect.PrefixIcon != null)
|
||||
{
|
||||
<CascadingValue Value="this" Name="SelectContent">
|
||||
<CascadingValue Value="@selectedItem" Name="SelectOption">
|
||||
@ParentLabelTemplate(selectedItem.Item)
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="@Prefix-selection-item">
|
||||
@getLabel(selectedItem)
|
||||
</span>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
<SelectSuffixIcon
|
||||
TItem="TItem"
|
||||
TItemValue="TItemValue"
|
||||
IsOverlayShow="@IsOverlayShow"
|
||||
OnClearClick="@OnClearClick"
|
||||
ShowArrowIcon="@ShowArrowIcon"
|
||||
ShowSearchIcon="@ShowArrowIcon"/>
|
||||
}
|
||||
else //ParentSelect.SelectMode != SelectMode.Default
|
||||
{
|
||||
<div class="@Prefix-selector" @ref="@Ref" style="@(ParentSelect.PrefixIcon is null?"":"padding-left: 4px;")">
|
||||
<div class="@Prefix-selection-overflow" @ref="@_overflow">
|
||||
@if (ParentSelect.PrefixIcon != null)
|
||||
{
|
||||
<div class="@Prefix-selection-overflow-item" style="opacity: 1;order:-100;">
|
||||
<span @ref="@_prefixRef" class="@Prefix-prefix" unselectable="on" aria-hidden="true" style="user-select: none;display:flex;align-items: center;padding-right: 4px;min-height:28px;">
|
||||
<span class="@Prefix-prefix" unselectable="on" aria-hidden="true" style="user-select: none;display:flex;align-items: center;padding-right: 4px;">
|
||||
@ParentSelect.PrefixIcon
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
@if (!ShowPlaceholder)
|
||||
{
|
||||
var selectedItems = ParentSelect.SelectedOptionItems;
|
||||
@if (ParentSelect.HasTagCount)
|
||||
{
|
||||
@for (int i = 0; i < Math.Min(ParentSelect.MaxTagCount.AsT0, selectedItems.Count); i++)
|
||||
{
|
||||
var selectedOption = selectedItems[i];
|
||||
<div class="@Prefix-selection-overflow-item" style="@OverflowStyle(i)">
|
||||
@if (ParentLabelTemplate != null)
|
||||
{
|
||||
<CascadingValue Value="this" Name="SelectContent">
|
||||
<CascadingValue Value="@selectedOption" Name="SelectOption">
|
||||
@ParentLabelTemplate(selectedOption.Item)
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="@Prefix-selection-item">
|
||||
<span class="@Prefix-selection-item-content">@getTagLabel(this,selectedOption)</span>
|
||||
<span unselectable="on" aria-hidden="true" style="user-select: none;" class="@Prefix-selection-item-remove"
|
||||
@onmousedown="(e)=> RemoveClicked(e, selectedOption)" onclick="event.stopPropagation()">
|
||||
<Icon Type="close"></Icon>
|
||||
</span>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (selectedItems.Count > ParentSelect.MaxTagCount.AsT0)
|
||||
{
|
||||
<div class="@Prefix-selection-overflow-item @Prefix-selection-overflow-item-rest" style="opacity: 1; order: @(selectedItems.Count-1);">
|
||||
@if (ParentMaxTagPlaceholerTemplate != null)
|
||||
{
|
||||
<CascadingValue Value="this" Name="SelectContent">
|
||||
@ParentMaxTagPlaceholerTemplate(selectedItems.Skip(ParentSelect.MaxTagCount.AsT0).Select(i => i.Item))
|
||||
</CascadingValue>
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
<span class="@Prefix-selection-item">
|
||||
<span class="@Prefix-selection-item-content">+ @(selectedItems.Count - ParentSelect.MaxTagCount.AsT0)@Ellipse</span>
|
||||
</span>
|
||||
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else if (ParentSelect.IsResponsive)
|
||||
{
|
||||
@for (int i = 0; i < selectedItems.Count; i++)
|
||||
{
|
||||
var selectedOption = selectedItems[i];
|
||||
<div class="@Prefix-selection-overflow-item" @key="@selectedOption.InternalId" @ref="@selectedOption.SelectedTagRef" style="@OverflowStyle(i)">
|
||||
@if (ParentLabelTemplate != null)
|
||||
{
|
||||
<CascadingValue Value="this" Name="SelectContent">
|
||||
<CascadingValue Value="@selectedOption" Name="SelectOption">
|
||||
@ParentLabelTemplate(selectedOption.Item)
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="@Prefix-selection-item">
|
||||
<span class="@Prefix-selection-item-content">@getTagLabel(this,selectedOption)</span>
|
||||
<span unselectable="on" aria-hidden="true" style="user-select: none;" class="@Prefix-selection-item-remove"
|
||||
@onmousedown="(e)=> RemoveClicked(e, selectedOption)" onclick="event.stopPropagation()">
|
||||
<Icon Type="close"></Icon>
|
||||
</span>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (selectedItems.Count > _calculatedMaxCount)
|
||||
{
|
||||
<div class="@Prefix-selection-overflow-item @Prefix-selection-overflow-item-rest" @key="@_internalId" @ref="@_aggregateTag" style="opacity 1; order: @(_calculatedMaxCount-1);">
|
||||
@if (ParentMaxTagPlaceholerTemplate != null)
|
||||
{
|
||||
<CascadingValue Value="this" Name="SelectContent">
|
||||
@ParentMaxTagPlaceholerTemplate(selectedItems.Skip(_calculatedMaxCount).Select(i => i.Item))
|
||||
</CascadingValue>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="@Prefix-selection-item">
|
||||
<span class="@Prefix-selection-item-content">+ @(selectedItems.Count-_calculatedMaxCount)@Ellipse</span>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string firstAfterPrefix = $"max-width: {GetFirstItemMaxWidth()}%;";
|
||||
@foreach (var selectedOption in selectedItems)
|
||||
{
|
||||
<div class="@Prefix-selection-overflow-item" style="opacity: 1;@firstAfterPrefix">
|
||||
@if (ParentLabelTemplate != null)
|
||||
{
|
||||
<CascadingValue Value="this" Name="SelectContent">
|
||||
<CascadingValue Value="@selectedOption" Name="SelectOption">
|
||||
@ParentLabelTemplate(selectedOption.Item)
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="@Prefix-selection-item">
|
||||
<span class="@Prefix-selection-item-content">@getTagLabel(this,selectedOption)</span>
|
||||
<span unselectable="on" aria-hidden="true" style="user-select: none;" class="@Prefix-selection-item-remove"
|
||||
@onmousedown="(e)=> RemoveClicked(e, selectedOption)" onclick="event.stopPropagation()">
|
||||
<Icon Type="close"></Icon>
|
||||
</span>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
firstAfterPrefix = "max-width: 98%;";
|
||||
}
|
||||
}
|
||||
}
|
||||
<div class="@Prefix-selection-overflow-item @Prefix-selection-overflow-item-suffix" style="opacity: 1; order: @(ParentSelect.IsResponsive?_calculatedMaxCount-1:ParentSelect.SelectedOptionItems.Count)">
|
||||
<div class="@Prefix-selection-search" style="@_inputWidth">
|
||||
<span class="@Prefix-selection-search" style="@_inputWidth">
|
||||
<input @ref="ParentSelect._inputRef"
|
||||
@oninput="OnInputChange"
|
||||
@oninput="@OnInputChange"
|
||||
@onkeyup="OnKeyUp"
|
||||
@onkeydown="OnKeyDown"
|
||||
@attributes=@AdditonalAttributes()
|
||||
@bind-value="@SearchValue"
|
||||
id="@ParentSelect.Id"
|
||||
id="@(ParentSelect.Id)_list"
|
||||
type="search"
|
||||
readonly="@(!ParentSelect.IsSearchEnabled)"
|
||||
unselectable="@(ParentSelect.IsSearchEnabled ? false : "on")"
|
||||
@ -224,25 +39,218 @@ else //ParentSelect.SelectMode != SelectMode.Default
|
||||
aria-autocomplete="list"
|
||||
aria-controls="@(ParentSelect.Id)_list"
|
||||
aria-haspopup="listbox"
|
||||
accesskey="@ParentSelect?.AccessKey"
|
||||
style="@_inputStyle" />
|
||||
<span class="@Prefix-selection-search-mirror" aria-hidden="true"> </span>
|
||||
</span>
|
||||
@if (ShowPlaceholder)
|
||||
{
|
||||
<span class="@Prefix-selection-placeholder">@Placeholder</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
var selectedItem = ParentSelect.SelectedOptionItems.FirstOrDefault();
|
||||
if (string.IsNullOrEmpty(SearchValue) && selectedItem != null)
|
||||
{
|
||||
@if (ParentLabelTemplate != null)
|
||||
{
|
||||
<CascadingValue Value="this" Name="SelectContent">
|
||||
<CascadingValue Value="@selectedItem" Name="SelectOption">
|
||||
@ParentLabelTemplate(selectedItem.Item)
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="@Prefix-selection-item">
|
||||
@getLabel(selectedItem)
|
||||
</span>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
<SelectSuffixIcon
|
||||
TItem="TItem"
|
||||
TItemValue="TItemValue"
|
||||
IsOverlayShow="@IsOverlayShow"
|
||||
OnClearClick="@OnClearClick"
|
||||
ShowArrowIcon="@ShowArrowIcon"
|
||||
ShowSearchIcon="@ShowArrowIcon"/>
|
||||
}
|
||||
else //ParentSelect.SelectMode != SelectMode.Default
|
||||
{
|
||||
<div class="@Prefix-selector" style="@(ParentSelect.PrefixIcon is null?"":"padding-left: 4px;")">
|
||||
<div class="@Prefix-selection-overflow" @ref="@_overflow">
|
||||
@if (ParentSelect.PrefixIcon != null)
|
||||
{
|
||||
<div class="@Prefix-selection-overflow-item" style="opacity: 1;order:-100;">
|
||||
<span @ref="@_prefixRef" class="@Prefix-prefix" unselectable="on" aria-hidden="true" style="user-select: none;display:flex;align-items: center;padding-right: 4px;min-height:28px;">
|
||||
@ParentSelect.PrefixIcon
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
@if (!ShowPlaceholder)
|
||||
{
|
||||
var selectedItems = ParentSelect.SelectedOptionItems;
|
||||
@if (ParentSelect.HasTagCount)
|
||||
{
|
||||
@for (int i = 0; i < Math.Min(ParentSelect.MaxTagCount.AsT0, selectedItems.Count); i++)
|
||||
{
|
||||
var selectedOption = selectedItems[i];
|
||||
<div class="@Prefix-selection-overflow-item" style="@OverflowStyle(i)">
|
||||
@if (ParentLabelTemplate != null)
|
||||
{
|
||||
<CascadingValue Value="this" Name="SelectContent">
|
||||
<CascadingValue Value="@selectedOption" Name="SelectOption">
|
||||
@ParentLabelTemplate(selectedOption.Item)
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="@Prefix-selection-item">
|
||||
<span class="@Prefix-selection-item-content">@getTagLabel(this,selectedOption)</span>
|
||||
<span unselectable="on" aria-hidden="true" style="user-select: none;" class="@Prefix-selection-item-remove"
|
||||
@onmousedown="(e)=> RemoveClicked(e, selectedOption)" onclick="event.stopPropagation()">
|
||||
<Icon Type="close"></Icon>
|
||||
</span>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (selectedItems.Count > ParentSelect.MaxTagCount.AsT0)
|
||||
{
|
||||
<div class="@Prefix-selection-overflow-item @Prefix-selection-overflow-item-rest" style="opacity: 1; order: @(selectedItems.Count-1);">
|
||||
@if (ParentMaxTagPlaceholerTemplate != null)
|
||||
{
|
||||
<CascadingValue Value="this" Name="SelectContent">
|
||||
@ParentMaxTagPlaceholerTemplate(selectedItems.Skip(ParentSelect.MaxTagCount.AsT0).Select(i => i.Item))
|
||||
</CascadingValue>
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
<span class="@Prefix-selection-item">
|
||||
<span class="@Prefix-selection-item-content">+ @(selectedItems.Count - ParentSelect.MaxTagCount.AsT0)@Ellipse</span>
|
||||
</span>
|
||||
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else if (ParentSelect.IsResponsive)
|
||||
{
|
||||
@for (int i = 0; i < selectedItems.Count; i++)
|
||||
{
|
||||
var selectedOption = selectedItems[i];
|
||||
<div class="@Prefix-selection-overflow-item" @key="@selectedOption.InternalId" @ref="@selectedOption.SelectedTagRef" style="@OverflowStyle(i)">
|
||||
@if (ParentLabelTemplate != null)
|
||||
{
|
||||
<CascadingValue Value="this" Name="SelectContent">
|
||||
<CascadingValue Value="@selectedOption" Name="SelectOption">
|
||||
@ParentLabelTemplate(selectedOption.Item)
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="@Prefix-selection-item">
|
||||
<span class="@Prefix-selection-item-content">@getTagLabel(this,selectedOption)</span>
|
||||
<span unselectable="on" aria-hidden="true" style="user-select: none;" class="@Prefix-selection-item-remove"
|
||||
@onmousedown="(e)=> RemoveClicked(e, selectedOption)" onclick="event.stopPropagation()">
|
||||
<Icon Type="close"></Icon>
|
||||
</span>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (selectedItems.Count > _calculatedMaxCount)
|
||||
{
|
||||
<div class="@Prefix-selection-overflow-item @Prefix-selection-overflow-item-rest" @key="@_internalId" @ref="@_aggregateTag" style="opacity 1; order: @(_calculatedMaxCount-1);">
|
||||
@if (ParentMaxTagPlaceholerTemplate != null)
|
||||
{
|
||||
<CascadingValue Value="this" Name="SelectContent">
|
||||
@ParentMaxTagPlaceholerTemplate(selectedItems.Skip(_calculatedMaxCount).Select(i => i.Item))
|
||||
</CascadingValue>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="@Prefix-selection-item">
|
||||
<span class="@Prefix-selection-item-content">+ @(selectedItems.Count-_calculatedMaxCount)@Ellipse</span>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string firstAfterPrefix = $"max-width: {GetFirstItemMaxWidth()}%;";
|
||||
@foreach (var selectedOption in selectedItems)
|
||||
{
|
||||
<div class="@Prefix-selection-overflow-item" style="opacity: 1;@firstAfterPrefix">
|
||||
@if (ParentLabelTemplate != null)
|
||||
{
|
||||
<CascadingValue Value="this" Name="SelectContent">
|
||||
<CascadingValue Value="@selectedOption" Name="SelectOption">
|
||||
@ParentLabelTemplate(selectedOption.Item)
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="@Prefix-selection-item">
|
||||
<span class="@Prefix-selection-item-content">@getTagLabel(this,selectedOption)</span>
|
||||
<span unselectable="on" aria-hidden="true" style="user-select: none;" class="@Prefix-selection-item-remove"
|
||||
@onmousedown="(e)=> RemoveClicked(e, selectedOption)" onclick="event.stopPropagation()">
|
||||
<Icon Type="close"></Icon>
|
||||
</span>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
firstAfterPrefix = "max-width: 98%;";
|
||||
}
|
||||
}
|
||||
}
|
||||
<div class="@Prefix-selection-overflow-item @Prefix-selection-overflow-item-suffix" style="opacity: 1; order: @(ParentSelect.IsResponsive?_calculatedMaxCount-1:ParentSelect.SelectedOptionItems.Count)">
|
||||
<div class="@Prefix-selection-search" style="@_inputWidth">
|
||||
<input @ref="ParentSelect._inputRef"
|
||||
@oninput="OnInputChange"
|
||||
@onkeyup="OnKeyUp"
|
||||
@onkeydown="OnKeyDown"
|
||||
@attributes=@AdditonalAttributes()
|
||||
@bind-value="@SearchValue"
|
||||
id="@ParentSelect.Id"
|
||||
type="search"
|
||||
readonly="@(!ParentSelect.IsSearchEnabled)"
|
||||
unselectable="@(ParentSelect.IsSearchEnabled ? false : "on")"
|
||||
role="combobox"
|
||||
class="@Prefix-selection-search-input"
|
||||
autocomplete="off"
|
||||
aria-owns="@(ParentSelect.Id)_list"
|
||||
aria-expanded="@IsOverlayShow"
|
||||
aria-autocomplete="list"
|
||||
aria-controls="@(ParentSelect.Id)_list"
|
||||
aria-haspopup="listbox"
|
||||
style="@_inputStyle" />
|
||||
<span class="@Prefix-selection-search-mirror" aria-hidden="true"> </span>
|
||||
</div>
|
||||
</div>
|
||||
@if (ShowPlaceholder)
|
||||
{
|
||||
<span class="@Prefix-selection-placeholder" style="@(ParentSelect.PrefixIcon != null?"margin-left: 7px;":"")">@Placeholder</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@if (ShowPlaceholder)
|
||||
{
|
||||
<span class="@Prefix-selection-placeholder" style="@(ParentSelect.PrefixIcon != null?"margin-left: 7px;":"")">@Placeholder</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SelectSuffixIcon
|
||||
TItem="TItem"
|
||||
TItemValue="TItemValue"
|
||||
IsOverlayShow="@IsOverlayShow"
|
||||
OnClearClick="@OnClearClick"
|
||||
ShowArrowIcon="@ShowArrowIcon"
|
||||
ShowSearchIcon="@ShowArrowIcon"/>
|
||||
}
|
||||
|
||||
<SelectSuffixIcon
|
||||
TItem="TItem"
|
||||
TItemValue="TItemValue"
|
||||
IsOverlayShow="@IsOverlayShow"
|
||||
OnClearClick="@OnClearClick"
|
||||
ShowArrowIcon="@ShowArrowIcon"
|
||||
ShowSearchIcon="@ShowArrowIcon"/>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
RenderFragment getLabel(SelectOptionItem<TItemValue, TItem> option) =>
|
||||
|
@ -423,11 +423,6 @@ namespace AntDesign.Select.Internal
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnClearClickAsync(MouseEventArgs args)
|
||||
{
|
||||
await OnClearClick.InvokeAsync(args);
|
||||
}
|
||||
|
||||
private async Task RemoveClicked(MouseEventArgs e, SelectOptionItem<TItemValue, TItem> selectedOption)
|
||||
{
|
||||
if (e.Button == 0)
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
@if (ParentSelect.SuffixIcon != null)
|
||||
{
|
||||
<span class="ant-select-arrow" unselectable="on" aria-hidden="true" style="user-select: none;" @onclick="@ParentSelect.OnArrowClick" @onclick:stopPropagation="true">
|
||||
<span class="ant-select-arrow" unselectable="on" aria-hidden="true" style="user-select: none;" >
|
||||
@ParentSelect.SuffixIcon
|
||||
</span>
|
||||
}
|
||||
@ -18,7 +18,7 @@ else
|
||||
{
|
||||
if (ShowArrowIcon || ParentSelect.FeedbackIcon != null)
|
||||
{
|
||||
<span class="ant-select-arrow" unselectable="on" aria-hidden="true" style="user-select: none;" @onclick="@ParentSelect.OnArrowClick" @onclick:stopPropagation="true">
|
||||
<span class="ant-select-arrow" unselectable="on" aria-hidden="true" style="user-select: none;" >
|
||||
@if (ParentSelect.IsSearchEnabled && IsOverlayShow)
|
||||
{
|
||||
if (ShowSearchIcon)
|
||||
@ -39,7 +39,7 @@ else
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="ant-select-arrow" unselectable="on" aria-hidden="true" style="user-select: none;" @onclick="@ParentSelect.OnArrowClick" @onclick:stopPropagation="true">
|
||||
<span class="ant-select-arrow" unselectable="on" aria-hidden="true" style="user-select: none;" >
|
||||
@if (ParentSelect.IsSearchEnabled && IsOverlayShow)
|
||||
{
|
||||
if (ShowSearchIcon)
|
||||
@ -52,7 +52,11 @@ else
|
||||
|
||||
@if (!ParentSelect.Disabled && ParentSelect.AllowClear && ParentSelect.HasValue)
|
||||
{
|
||||
<span class="ant-select-clear" unselectable="on" aria-hidden="true" style="user-select: none;" @onclick="OnClearClick" @onclick:stopPropagation="true">
|
||||
<span class="ant-select-clear"
|
||||
unselectable="on"
|
||||
aria-hidden="true"
|
||||
style="user-select: none;"
|
||||
@ref="@_clearRef">
|
||||
<Icon Type="close-circle" Theme="fill"></Icon>
|
||||
</span>
|
||||
}
|
||||
|
@ -1,4 +1,8 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using AntDesign.JsInterop;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
|
||||
namespace AntDesign.Select.Internal
|
||||
@ -20,5 +24,40 @@ namespace AntDesign.Select.Internal
|
||||
public EventCallback<MouseEventArgs> OnClearClick { get; set; }
|
||||
|
||||
[CascadingParameter(Name = "ParentSelect")] internal SelectBase<TItemValue, TItem> ParentSelect { get; set; }
|
||||
|
||||
[Inject] private IDomEventListener DomEventListener { get; set; }
|
||||
|
||||
private string _id = "";
|
||||
private ElementReference _clearRef;
|
||||
protected override Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
_id = _clearRef.Id;
|
||||
if (!ParentSelect.Disabled && ParentSelect.AllowClear && ParentSelect.HasValue)
|
||||
{
|
||||
DomEventListener.AddExclusive<JsonElement>(_clearRef, "click", OnClear, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (_clearRef.Id != _id)
|
||||
{
|
||||
_id = _clearRef.Id;
|
||||
if (!ParentSelect.Disabled && ParentSelect.AllowClear && ParentSelect.HasValue)
|
||||
{
|
||||
DomEventListener.AddExclusive<JsonElement>(_clearRef, "click", OnClear, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnAfterRenderAsync(firstRender);
|
||||
}
|
||||
|
||||
private async void OnClear(JsonElement jsonElement)
|
||||
{
|
||||
if (OnClearClick.HasDelegate)
|
||||
{
|
||||
await OnClearClick.InvokeAsync(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ namespace AntDesign.Tests
|
||||
return;
|
||||
}
|
||||
|
||||
public void AddExclusive<T>(object dom, string eventName, Action<T> callback, bool preventDefault = false)
|
||||
public void AddExclusive<T>(object dom, string eventName, Action<T> callback, bool preventDefault = false, bool stopPropagation = false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -1,138 +0,0 @@
|
||||
@inherits AntDesignTestBase
|
||||
@code {
|
||||
record Person(int Id, string Name);
|
||||
List<Person> _persons = new()
|
||||
{
|
||||
new(1, "John"),
|
||||
new(2, "Lucy"),
|
||||
new(3, "Jack"),
|
||||
new(4, "Emily"),
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void DefaultSelectWithoutAllowClear()
|
||||
{
|
||||
JSInterop.Setup<DomRect>(JSInteropConstants.GetBoundingClientRect, _ => true)
|
||||
.SetResult(new DomRect());
|
||||
var cut = Render<Select<int, Person>>(
|
||||
@<AntDesign.Select DataSource="@_persons"
|
||||
LabelName="@nameof(Person.Name)"
|
||||
ValueName="@nameof(Person.Id)"
|
||||
Value="0">
|
||||
</AntDesign.Select>);
|
||||
cut.Invoking(c => c.Find("span.ant-select-clear")).Should().Throw<ElementNotFoundException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultSelectWithAllowClear()
|
||||
{
|
||||
JSInterop.Setup<DomRect>(JSInteropConstants.GetBoundingClientRect, _ => true)
|
||||
.SetResult(new DomRect());
|
||||
int? value = 1;
|
||||
var cut = Render<Select<int?, Person>>(
|
||||
@<AntDesign.Select
|
||||
DataSource="@_persons"
|
||||
ValueName="@nameof(Person.Id)"
|
||||
LabelName="@nameof(Person.Name)"
|
||||
@bind-Value="@value"
|
||||
AllowClear/>
|
||||
);
|
||||
var clear = cut.Find("span.ant-select-clear");
|
||||
clear.Click();
|
||||
value.Should().Be(default(int?));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultNoSelectWithAllowClear()
|
||||
{
|
||||
JSInterop.Setup<DomRect>(JSInteropConstants.GetBoundingClientRect, _ => true)
|
||||
.SetResult(new DomRect());
|
||||
int? value = 5;
|
||||
var cut = Render<Select<int?, Person>>(
|
||||
@<AntDesign.Select DataSource="@_persons"
|
||||
ValueName="@nameof(Person.Id)"
|
||||
LabelName="@nameof(Person.Name)"
|
||||
@bind-Value="@value"
|
||||
DefaultValue="5"
|
||||
AllowClear />
|
||||
);
|
||||
var input = cut.Find("input");
|
||||
|
||||
value.Should().Be(default(int?));
|
||||
input.GetAttribute("value").Should().Be("");
|
||||
}
|
||||
|
||||
|
||||
IEnumerable<string> _datasource = new List<string>{ "Lucy", "John", "Jack", "Emily" };
|
||||
|
||||
[Fact]
|
||||
public void MultipleSelectWithoutAllowClear()
|
||||
{
|
||||
JSInterop.Setup<DomRect>(JSInteropConstants.GetBoundingClientRect, _ => true)
|
||||
.SetResult(new DomRect());
|
||||
IEnumerable<string> values = new List<string>();
|
||||
var cut = Render<Select<string, string>>(
|
||||
@<Select
|
||||
Mode="multiple"
|
||||
DataSource="@_datasource"
|
||||
@bind-Values="@values">
|
||||
<SelectOptions>
|
||||
@foreach (var data in _datasource)
|
||||
{
|
||||
<SelectOption TItemValue="string" TItem="string" Value="@data" Label="@data"/>
|
||||
}
|
||||
</SelectOptions>
|
||||
</Select>
|
||||
);
|
||||
cut.Invoking(c => c.Find("span.ant-select-clear")).Should().Throw<ElementNotFoundException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultipleSelectWithAllowClearAndEmptyValues()
|
||||
{
|
||||
JSInterop.Setup<DomRect>(JSInteropConstants.GetBoundingClientRect, _ => true)
|
||||
.SetResult(new DomRect());
|
||||
IEnumerable<string> values = new List<string>();
|
||||
var cut = Render<Select<string, string>>(
|
||||
@<Select
|
||||
Mode="multiple"
|
||||
DataSource="@_datasource"
|
||||
@bind-Values="@values"
|
||||
AllowClear>
|
||||
<SelectOptions>
|
||||
@foreach (var data in _datasource)
|
||||
{
|
||||
<SelectOption TItemValue="string" TItem="string" Value="@data" Label="@data"/>
|
||||
}
|
||||
</SelectOptions>
|
||||
</Select>
|
||||
);
|
||||
cut.Invoking(c => c.Find("span.ant-select-clear")).Should().Throw<ElementNotFoundException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultipleSelectWithAllowClearAndNotEmptyValues()
|
||||
{
|
||||
JSInterop.Setup<DomRect>(JSInteropConstants.GetBoundingClientRect, _ => true)
|
||||
.SetResult(new DomRect());
|
||||
IEnumerable<string> values = new List<string>{ "Lucy" };
|
||||
var cut = Render<Select<string, string>>(
|
||||
@<Select
|
||||
Mode="multiple"
|
||||
DataSource="@_datasource"
|
||||
@bind-Values="@values"
|
||||
AllowClear>
|
||||
<SelectOptions>
|
||||
@foreach (var data in _datasource)
|
||||
{
|
||||
<SelectOption TItemValue="string" TItem="string" Value="@data" Label="@data"/>
|
||||
}
|
||||
</SelectOptions>
|
||||
</Select>
|
||||
);
|
||||
cut.Render();
|
||||
var clear = cut.Find("span.ant-select-clear");
|
||||
clear.Click();
|
||||
values.Should().BeEquivalentTo(default);
|
||||
}
|
||||
}
|
@ -45,62 +45,6 @@
|
||||
arrow.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact] //covers scenario from issue #1989
|
||||
public async Task Render_select_dropdown_on_arrow_click()
|
||||
{
|
||||
#if !NET6_0_OR_GREATER
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
JSInterop.SetupVoid(JSInteropConstants.Focus, _ => true).SetVoidResult();
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
#endif
|
||||
JSInterop.SetupVoid(JSInteropConstants.ScrollTo, _ => true).SetVoidResult();
|
||||
JSInterop.Setup<OverlayPosition>(JSInteropConstants.OverlayComponentHelper.AddOverlayToContainer, _ => true)
|
||||
.SetResult(new OverlayPosition() { Top = 0, Left = 0, ZIndex = 5000, Placement = Placement.BottomLeft });
|
||||
|
||||
var mock = base.MockedDomEventListener;
|
||||
Action<JsonElement>? onTriggerMouseUpCallback = default;
|
||||
Action<JsonElement>? onTriggerClickCallback = default;
|
||||
mock.Setup(s => s.AddShared<JsonElement>("document", "mouseup", It.IsAny<Action<JsonElement>>(), It.IsAny<bool>()))
|
||||
.Callback((object dom, string eventName, Action<JsonElement> callback, bool preventDefault) =>
|
||||
{
|
||||
if (callback.Target is AntDesign.Internal.OverlayTrigger)
|
||||
{
|
||||
onTriggerMouseUpCallback = callback;
|
||||
}
|
||||
});
|
||||
mock.Setup(s => s.AddExclusive<JsonElement>(It.IsAny<ElementReference>(), "click", It.IsAny<Action<JsonElement>>(), It.IsAny<bool>()))
|
||||
.Callback((object dom, string eventName, Action<JsonElement> callback, bool preventDefault) =>
|
||||
{
|
||||
if (callback.Target is AntDesign.Internal.OverlayTrigger)
|
||||
{
|
||||
onTriggerClickCallback = callback;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var plannedInvocation = JSInterop.Setup<string>(JSInteropConstants.AddDomEventListener, _ => true);
|
||||
var cut = Render<EnumSelect<City>>(@<EnumSelect TEnum="City"></EnumSelect>);
|
||||
//Act
|
||||
var arrow = cut.Find("span.ant-select-arrow");
|
||||
onTriggerMouseUpCallback.Should().NotBeNull();
|
||||
onTriggerClickCallback.Should().NotBeNull();
|
||||
|
||||
await cut.InvokeAsync(() => arrow.Click());
|
||||
onTriggerMouseUpCallback?.Invoke(JsonDocument.Parse("{}").RootElement);
|
||||
onTriggerClickCallback?.Invoke(default);
|
||||
cut.WaitForState(() => !cut.Find("div.ant-select-dropdown").ClassList.Contains("ant-select-dropdown-hidden"));
|
||||
//This is probably not the best solution. However this test is awkward - we are testing
|
||||
//for something that should not appear. In failing scenarios it appears after some
|
||||
//time (due to multiple async/await and re-renderings). That actually points to
|
||||
//a probable wrong design in the way the OverlayTrigger & Overlay are functioning.
|
||||
//So here we wait and hope the delay is going be long enough for the component
|
||||
//to finish all its renderings.
|
||||
await Task.Delay(500);
|
||||
//Assert
|
||||
var dropdown = cut.Find("div.ant-select-dropdown");
|
||||
dropdown.ClassList.Should().NotContain("ant-select-dropdown-hidden");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllowClear_button_not_rendered_for_no_data()
|
||||
{
|
||||
|
@ -120,92 +120,6 @@
|
||||
// cut.Instance.Value.Should().Be(10);
|
||||
// }
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(AllowClearWithoutValueOnClearTheory))]
|
||||
public void AllowClear_button_behavior_without_ValueOnClear_set_with_DataSource(List<PersonNullable> dataSource,
|
||||
string? initialValue, bool defaultActiveFirstOption, string? expectedValue, string? expectedLabel)
|
||||
{
|
||||
string? value = "-1"; //set initial value to a not-possible value
|
||||
Action<string?> ValueChanged = (v) =>value = v;
|
||||
var cut = Render<AntDesign.Select<string?, PersonNullable>>(
|
||||
@<AntDesign.Select DataSource="@dataSource"
|
||||
LabelName="@nameof(PersonNullable.Name)"
|
||||
ValueName="@nameof(PersonNullable.Id)"
|
||||
Value="@initialValue"
|
||||
ValueChanged="@ValueChanged"
|
||||
DefaultActiveFirstOption="@defaultActiveFirstOption"
|
||||
AllowClear>
|
||||
</AntDesign.Select>);
|
||||
//Act
|
||||
|
||||
//normally blazor would rerender and in Select.OnParametersSet()
|
||||
//would load newly set value into the SelectContent, but bUnit does
|
||||
//not rerender, so it has to be forced. This could probably be fixed
|
||||
//by forcing StateHasChanged on the Select component, but requires
|
||||
//investigation if it won't cause multiple re-renders.
|
||||
cut.Render();
|
||||
cut.Find("span.ant-select-clear").Click();
|
||||
if (expectedLabel == string.Empty)
|
||||
{
|
||||
cut.Invoking(c => c.Find("span.ant-select-selection-item"))
|
||||
.Should().Throw<Bunit.ElementNotFoundException>();
|
||||
}
|
||||
else
|
||||
{
|
||||
var selectContent = cut.Find("span.ant-select-selection-item");
|
||||
selectContent.TextContent.Trim().Should().Be(expectedLabel);
|
||||
}
|
||||
|
||||
value.Should().Be(expectedValue);
|
||||
cut.Instance.Value.Should().Be(expectedValue);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(AllowClearWithoutValueOnClearTheory))]
|
||||
public void AllowClear_button_behavior_without_ValueOnClear_set_with_SelectOption(List<PersonNullable> dataSource,
|
||||
string? initialValue, bool defaultActiveFirstOption, string? expectedValue, string? expectedLabel)
|
||||
{
|
||||
string? value = "-1"; //set initial value to a not-possible value
|
||||
Action<string?> ValueChanged = (v) =>value = v;
|
||||
var cut = Render<AntDesign.Select<string?, string>>(
|
||||
@<AntDesign.Select
|
||||
TItemValue="string?"
|
||||
TItem="string"
|
||||
Value="@initialValue"
|
||||
ValueChanged="@ValueChanged"
|
||||
DefaultActiveFirstOption="@defaultActiveFirstOption"
|
||||
AllowClear>
|
||||
<SelectOptions>
|
||||
@foreach(var item in dataSource)
|
||||
{
|
||||
<SelectOption TItemValue="string?" TItem="string" Value="@item.Id" Label="@item.Name" />
|
||||
}
|
||||
</SelectOptions>
|
||||
</AntDesign.Select>);
|
||||
//Act
|
||||
|
||||
//normally blazor would rerender and in Select.OnParametersSet()
|
||||
//would load newly set value into the SelectContent, but bUnit does
|
||||
//not rerender, so it has to be forced. This could probably be fixed
|
||||
//by forcing StateHasChanged on the Select component, but requires
|
||||
//investigation if it won't cause multiple re-renders.
|
||||
cut.Render();
|
||||
cut.Find("span.ant-select-clear").Click();
|
||||
if (expectedLabel == string.Empty)
|
||||
{
|
||||
cut.Invoking(c => c.Find("span.ant-select-selection-item"))
|
||||
.Should().Throw<Bunit.ElementNotFoundException>();
|
||||
}
|
||||
else
|
||||
{
|
||||
var selectContent = cut.Find("span.ant-select-selection-item");
|
||||
selectContent.TextContent.Trim().Should().Be(expectedLabel);
|
||||
}
|
||||
|
||||
value.Should().Be(expectedValue);
|
||||
cut.Instance.Value.Should().Be(expectedValue);
|
||||
}
|
||||
|
||||
public static List<PersonNullable> GetAllowClearDataSource(string? id = null, string? title = null)
|
||||
{
|
||||
var baseDatasource = new List<PersonNullable>()
|
||||
@ -235,94 +149,6 @@
|
||||
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(AllowClearWithValueOnClearTheory))]
|
||||
public void AllowClear_button_behavior_with_ValueOnClear_set_with_DataSource(List<PersonNullable> dataSource,
|
||||
string? initialValue, bool defaultActiveFirstOption, string? valueOnClear, string? expectedValue, string? expectedLabel)
|
||||
{
|
||||
string? value = "-1"; //set initial value to a not-possible value
|
||||
Action<string?> ValueChanged = (v) =>value = v;
|
||||
var cut = Render<AntDesign.Select<string?, PersonNullable>>(
|
||||
@<AntDesign.Select DataSource="@dataSource"
|
||||
LabelName="@nameof(PersonNullable.Name)"
|
||||
ValueName="@nameof(PersonNullable.Id)"
|
||||
Value="@initialValue"
|
||||
ValueChanged="@ValueChanged"
|
||||
DefaultActiveFirstOption="@defaultActiveFirstOption"
|
||||
ValueOnClear="@valueOnClear"
|
||||
AllowClear>
|
||||
</AntDesign.Select>);
|
||||
//Act
|
||||
|
||||
//normally blazor would rerender and in Select.OnParametersSet()
|
||||
//would load newly set value into the SelectContent, but bUnit does
|
||||
//not rerender, so it has to be forced. This could probably be fixed
|
||||
//by forcing StateHasChanged on the Select component, but requires
|
||||
//investigation if it won't cause multiple re-renders.
|
||||
cut.Render();
|
||||
cut.Find("span.ant-select-clear").Click();
|
||||
if (expectedLabel == string.Empty)
|
||||
{
|
||||
cut.Invoking(c => c.Find("span.ant-select-selection-item"))
|
||||
.Should().Throw<Bunit.ElementNotFoundException>();
|
||||
}
|
||||
else
|
||||
{
|
||||
var selectContent = cut.Find("span.ant-select-selection-item");
|
||||
selectContent.TextContent.Trim().Should().Be(expectedLabel);
|
||||
}
|
||||
|
||||
value.Should().Be(expectedValue);
|
||||
cut.Instance.Value.Should().Be(expectedValue);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(AllowClearWithValueOnClearTheory))]
|
||||
public void AllowClear_button_behavior_with_ValueOnClear_set_with_SelectOption(List<PersonNullable> dataSource,
|
||||
string? initialValue, bool defaultActiveFirstOption, string? valueOnClear, string? expectedValue, string? expectedLabel)
|
||||
{
|
||||
string? value = "-1"; //set initial value to a not-possible value
|
||||
Action<string?> ValueChanged = (v) =>value = v;
|
||||
var cut = Render<AntDesign.Select<string?, string>>(
|
||||
@<AntDesign.Select
|
||||
TItemValue="string?"
|
||||
TItem="string"
|
||||
Value="@initialValue"
|
||||
ValueChanged="@ValueChanged"
|
||||
DefaultActiveFirstOption="@defaultActiveFirstOption"
|
||||
ValueOnClear="@valueOnClear"
|
||||
AllowClear>
|
||||
<SelectOptions>
|
||||
@foreach(var item in dataSource)
|
||||
{
|
||||
<SelectOption TItemValue="string?" TItem="string" Value="@item.Id" Label="@item.Name" />
|
||||
}
|
||||
</SelectOptions>
|
||||
</AntDesign.Select>);
|
||||
//Act
|
||||
|
||||
//normally blazor would rerender and in Select.OnParametersSet()
|
||||
//would load newly set value into the SelectContent, but bUnit does
|
||||
//not rerender, so it has to be forced. This could probably be fixed
|
||||
//by forcing StateHasChanged on the Select component, but requires
|
||||
//investigation if it won't cause multiple re-renders.
|
||||
cut.Render();
|
||||
cut.Find("span.ant-select-clear").Click();
|
||||
if (expectedLabel == string.Empty)
|
||||
{
|
||||
cut.Invoking(c => c.Find("span.ant-select-selection-item"))
|
||||
.Should().Throw<Bunit.ElementNotFoundException>();
|
||||
}
|
||||
else
|
||||
{
|
||||
var selectContent = cut.Find("span.ant-select-selection-item");
|
||||
selectContent.TextContent.Trim().Should().Be(expectedLabel);
|
||||
}
|
||||
|
||||
value.Should().Be(expectedValue);
|
||||
cut.Instance.Value.Should().Be(expectedValue);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> AllowClearWithValueOnClearTheory()
|
||||
{
|
||||
return new List<object[]>
|
||||
|
Loading…
Reference in New Issue
Block a user