feat(module: select): add LabelProperty, ValueProperty and DisabledPredicate as expression-style api (#3569)
* fix(module: select):add parameter for SelectOption component which should be used when SelectOption was created directly and TItem is different with TItemValue * feat(module: select): add parameter that support use delegate to set option label and value * rename XXXGetter to OptionXXXExpression * rename OptionXXXExpression to OptionXXXProperty * refactor * fix tests * fix test --------- Co-authored-by: James Yeung <shunjiey@hotmail.com>
This commit is contained in:
Родитель
157cc3974d
Коммит
4451315af1
|
@ -18,6 +18,11 @@ using OneOf;
|
|||
|
||||
namespace AntDesign
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
[CascadingTypeParameter(nameof(TItem))]
|
||||
[CascadingTypeParameter(nameof(TItemValue))]
|
||||
#endif
|
||||
|
||||
public partial class Select<TItemValue, TItem> : SelectBase<TItemValue, TItem>
|
||||
{
|
||||
#region Parameters
|
||||
|
@ -309,6 +314,20 @@ namespace AntDesign
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the label property in the option object. If use this property, should not use <see cref="LabelName"/>
|
||||
/// </summary>
|
||||
[Parameter] public Func<TItem, string> LabelProperty { get => _getLabel; set => _getLabel = value; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the value property in the option object. If use this property, should not use <see cref="ValueName"/>
|
||||
/// </summary>
|
||||
[Parameter] public Func<TItem, TItemValue> ValueProperty { get => _getValue; set => _getValue = value; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies predicate for disabled options
|
||||
/// </summary>
|
||||
[Parameter] public Func<TItem, bool> DisabledPredicate { get => _getDisabled; set => _getDisabled = value; }
|
||||
/// <summary>
|
||||
/// Used when Mode = default - The value is used during initialization and when pressing the Reset button within Forms.
|
||||
/// </summary>
|
||||
|
@ -402,7 +421,7 @@ namespace AntDesign
|
|||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
if (SelectOptions == null && typeof(TItemValue) != typeof(TItem) && string.IsNullOrWhiteSpace(ValueName))
|
||||
if (SelectOptions == null && typeof(TItemValue) != typeof(TItem) && ValueProperty == null && string.IsNullOrWhiteSpace(ValueName))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(ValueName));
|
||||
}
|
||||
|
@ -688,7 +707,7 @@ namespace AntDesign
|
|||
isSelected = _selectedValues.Contains(value);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(DisabledName))
|
||||
if (_getDisabled != default)
|
||||
disabled = _getDisabled(item);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(GroupName))
|
||||
|
|
|
@ -65,7 +65,11 @@ namespace AntDesign
|
|||
/// The parameter should only be used if the SelectOption was created directly.
|
||||
/// </summary>
|
||||
[Parameter] public TItemValue Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Item of the SelectOption
|
||||
/// The parameter should only be used if the SelectOption was created directly.
|
||||
/// </summary>
|
||||
[Parameter] public TItem Item { get; set; }
|
||||
#endregion
|
||||
|
||||
# region Properties
|
||||
|
@ -196,7 +200,7 @@ namespace AntDesign
|
|||
IsDisabled = Disabled,
|
||||
GroupName = _groupName,
|
||||
Value = Value,
|
||||
Item = THelper.ChangeType<TItem>(Value, CultureInfo.CurrentCulture),
|
||||
Item = Item ?? THelper.ChangeType<TItem>(Value, CultureInfo.CurrentCulture),
|
||||
ChildComponent = this
|
||||
};
|
||||
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
<Select TItem="Person"
|
||||
TItemValue="string"
|
||||
DataSource="@_list"
|
||||
<Select DataSource="@_list"
|
||||
@bind-Value="@_selectedValue1"
|
||||
DefaultValue="@("lucy")"
|
||||
ValueName="@nameof(Person.Value)"
|
||||
LabelName="@nameof(Person.Name)"
|
||||
ValueProperty="c=>c.Value"
|
||||
LabelProperty="c=>c.Name"
|
||||
DisabledName="@nameof(Person.IsDisabled)"
|
||||
Style="width:120px"
|
||||
OnSelectedItemChanged="OnSelectedItemChangedHandler">
|
||||
Style="width:120px">
|
||||
</Select>
|
||||
<Select @bind-Value="@_selectedValue2"
|
||||
DefaultValue="@("lucy")"
|
||||
|
@ -16,7 +13,7 @@
|
|||
TItem="string"
|
||||
Disabled>
|
||||
<SelectOptions>
|
||||
<SelectOption TItemValue="string" TItem="string" Value="@("lucy")" Label="Lucy" />
|
||||
<SelectOption Value="@("lucy")" Label="Lucy" />
|
||||
</SelectOptions>
|
||||
</Select>
|
||||
<Select @bind-Value="@_selectedValue3"
|
||||
|
@ -26,7 +23,7 @@
|
|||
TItem="string"
|
||||
Loading>
|
||||
<SelectOptions>
|
||||
<SelectOption TItemValue="string" TItem="string" Value="@("lucy")" Label="Lucy" />
|
||||
<SelectOption Value="@("lucy")" Label="Lucy" />
|
||||
</SelectOptions>
|
||||
</Select>
|
||||
<Select DataSource="@_list"
|
||||
|
@ -46,13 +43,26 @@
|
|||
Placeholder="Choose"
|
||||
OnSelectedItemChanged="@((personName) => Console.WriteLine($"selectedItem:{personName},selectedValue:{_selectedValue5}"))">
|
||||
</Select>
|
||||
|
||||
<Select DataSource="@_dict"
|
||||
@bind-Value="@_selectedValue6"
|
||||
LabelProperty="c=>c.Key"
|
||||
ValueProperty="c=>c.Value"
|
||||
DisabledPredicate="@(c=>c.Key == "Disabled")"
|
||||
Style="width: 120px;"
|
||||
Placeholder="Dictionary options">
|
||||
</Select>
|
||||
|
||||
@code
|
||||
{
|
||||
List<Person> _list;
|
||||
List<string> _personNames;
|
||||
Dictionary<string, string> _dict;
|
||||
|
||||
string _selectedValue1, _selectedValue2, _selectedValue3;
|
||||
string _selectedValue4 = "lucy";
|
||||
string _selectedValue5 = "Lucy";
|
||||
string _selectedValue6 = "Lucy";
|
||||
class Person
|
||||
{
|
||||
public string Value { get; set; }
|
||||
|
@ -69,7 +79,10 @@
|
|||
new Person {Value = "disabled", Name = "Disabled", IsDisabled = true},
|
||||
new Person {Value = "yaoming", Name = "YaoMing"}
|
||||
};
|
||||
|
||||
_personNames = new List<string> { "Jack", "Lucy", "YaoMing" };
|
||||
|
||||
_dict = _list.ToDictionary(c => c.Name, c => c.Value);
|
||||
}
|
||||
|
||||
private void OnSelectedItemChangedHandler(Person value)
|
||||
|
|
|
@ -33,6 +33,7 @@ Select component to select value from options.
|
|||
| DefaultValues | When `Mode = multiple` \| `tags` - The values are used during initialization and when pressing the Reset button within Forms. | IEnumerable<TItemValues> | - | |
|
||||
| Disabled | Whether the Select component is disabled. | bool | false | |
|
||||
| DisabledName | The name of the property to be used as a disabled indicator. | string | | |
|
||||
| DisabledPredicate | Specifies predicate for disabled options | - | - |
|
||||
| DropdownMatchSelectWidth | Will match drowdown width: <br/>- for boolean: `true` - with widest item in the dropdown list <br/> - for string: with value (e.g.: `256px`). | OneOf<bool, string> | true | |
|
||||
| DropdownMaxWidth | Will not allow dropdown width to grow above stated in here value (eg. "768px"). | string | "auto" | |
|
||||
| DropdownRender | Customize dropdown content. | Renderfragment | - | |
|
||||
|
@ -45,6 +46,7 @@ Select component to select value from options.
|
|||
| LabelInValue | Whether to embed label in value, turn the format of value from `TItemValue` to string (JSON) e.g. { "value": `TItemValue`, "label": "`Label value`" } | bool | false | |
|
||||
| LabelName | The name of the property to be used for the label. | string | | |
|
||||
| LabelTemplate | Is used to customize the label style. | RenderFragment<TItem> | | |
|
||||
| LabelProperty | Specifies the label property in the option object. | Func<TItem, string> | - |
|
||||
| Loading | Show loading indicator. You have to write the loading logic on your own. | bool | false | |
|
||||
| ListboxStyle | custom listbox styles | string | display: flex; flex-direction: column; | |
|
||||
| MaxTagCount | Max tag count to show. responsive will cost render performance. | int | `ResponsiveTag.Responsive` | - | |
|
||||
|
@ -83,6 +85,7 @@ Select component to select value from options.
|
|||
| ValuesChanged | Used for the two-way binding. | EventCallback<IEnumerable<TItemValue>> | - | |
|
||||
| ValueName | The name of the property to be used for the value. | string | - | |
|
||||
| ValueOnClear | When Clear button is pressed, Value will be set to whatever is set in ValueOnClear. | TItemValue | - | 0.11 |
|
||||
| ValueProperty | Specifies the value property in the option object. | Func<TItem, TItemValue> | - |
|
||||
|
||||
### SelectOption props
|
||||
|
||||
|
@ -91,4 +94,5 @@ Select component to select value from options.
|
|||
| Class | The additional class to option | string | - | |
|
||||
| Disabled | Disable this option | Boolean | false | |
|
||||
| Label | Label of Select after selecting this Option | string | - | |
|
||||
| Value | Value of Select after selecting this Option | TItemValue | - | |
|
||||
| Value | Value of Select after selecting this Option | TItemValue | - | |
|
||||
| Item | Item of the option | TItem | - | |
|
|
@ -33,6 +33,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/_0XzgOis7/Select.svg
|
|||
| DefaultValues | 当`Mode = multiple` \| `tags` - 在初始化期间和在表单中按下重置按钮时使用这些值. | IEnumerable<TItemValues> | - | |
|
||||
| Disabled | 是否禁用 | bool | false | |
|
||||
| DisabledName | 用作禁用指示器的属性名称. | string | | |
|
||||
| DisabledPredicate | 指定禁用选项的判断条件 | - | - |
|
||||
| DropdownMatchSelectWidth | 将匹配下拉宽度: <br/>- for boolean: `true` - 下拉列表中最宽的项目 <br/> - for string: with value (e.g.: `256px`). | OneOf<bool, string> | true | |
|
||||
| DropdownMaxWidth | 不允许下拉菜单的宽度超过此处的值(例如“768px”). | string | "auto" | |
|
||||
| DropdownRender | 自定义下拉框内容 | Renderfragment | - | |
|
||||
|
@ -45,6 +46,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/_0XzgOis7/Select.svg
|
|||
| LabelInValue | 是否在 value 中嵌入标签,将 value 的格式从 `TItemValue` 转换为 string (JSON) e.g. { "value": `TItemValue`, "label": "`标签值`" } | bool | false | |
|
||||
| LabelName | 用于标签的属性名称. | string | | |
|
||||
| LabelTemplate | 用于自定义标签样式. | RenderFragment<TItem> | | |
|
||||
| LabelProperty | 指定 option 对象的 label属性 | `Func<TItem, string>` | - |
|
||||
| Loading | 显示加载指示器。 必须编写加载逻辑. | bool | false | |
|
||||
| ListboxStyle | 自定义下拉列表样式 | string | display: flex; flex-direction: column; | |
|
||||
| MaxTagCount | 最多显示多少个 tag,响应式模式会对性能产生损耗 | int | `ResponsiveTag.Responsive` | - | |
|
||||
|
@ -83,12 +85,14 @@ cover: https://gw.alipayobjects.com/zos/alicdn/_0XzgOis7/Select.svg
|
|||
| ValuesChanged | 用于双向绑定(多选). | EventCallback<IEnumerable<TItemValue>> | - | |
|
||||
| ValueName | 用于值的属性的名称. | string | - | |
|
||||
| ValueOnClear | 按下清除按钮时,值将设置为 ValueOnClear 中设置的值. | TItemValue | - | 0.11 |
|
||||
| ValueProperty | 指定 option 对象的 value 属性. | `Func<TItem, TItemValue>` | - |
|
||||
|
||||
### SelectOption props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Class | Option 器类名 | string | - | |
|
||||
| Disabled | 是否禁用 | Boolean | false | |
|
||||
| Label |选择此选项后选择的标签内容 | string | - | |
|
||||
| Value |选择此选项后的 Select 值| TItemValue | - | |
|
||||
| Class | Option 类名 | string | - | |
|
||||
| Disabled | 是否禁用 | Boolean | false | |
|
||||
| Label | 选择此选项后选择的标签内容 | string | - | |
|
||||
| Value | 选择此选项后的 Select 值 | TItemValue | - | |
|
||||
| Item | option 对象 | TItem | - | |
|
|
@ -1,5 +1,7 @@
|
|||
@inherits AntDesignTestBase
|
||||
@using AntDesign.Core.JsInterop.Modules.Components;
|
||||
@inherits AntDesignTestBase
|
||||
@code {
|
||||
|
||||
record Person(int Id, string Name);
|
||||
public record PersonNullable(string? Id, string Name);
|
||||
class PersonClass
|
||||
|
@ -149,6 +151,98 @@
|
|||
|
||||
}
|
||||
|
||||
/*
|
||||
[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[]>
|
||||
|
@ -165,4 +259,48 @@
|
|||
|
||||
}
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
[Fact]
|
||||
public async Task Work_With_ValueProperty_and_LabelProperty()
|
||||
{
|
||||
JSInterop.SetupVoid("AntDesign.interop.eventHelper.addPreventKeys", _ => true);
|
||||
JSInterop.SetupVoid("AntDesign.interop.domManipulationHelper.scrollTo", _ => true);
|
||||
JSInterop.SetupVoid("AntDesign.interop.domManipulationHelper.focus", _ => true);
|
||||
JSInterop
|
||||
.Setup<OverlayPosition>("AntDesign.interop.overlayHelper.addOverlayToContainer", _ => true)
|
||||
.SetResult(new OverlayPosition
|
||||
{
|
||||
Bottom = 10,
|
||||
Left = 10,
|
||||
Right = 10,
|
||||
Top = 10,
|
||||
Placement = Placement.TopLeft,
|
||||
ZIndex = 100
|
||||
});
|
||||
|
||||
Dictionary<string, int> dict = new()
|
||||
{
|
||||
["Hello"] = 1,
|
||||
["World"] = 2,
|
||||
["Disabled"] = 3,
|
||||
};
|
||||
int value = 1;
|
||||
var cut = Render<AntDesign.Select<int, KeyValuePair<string, int>>>(@<AntDesign.Select DataSource="@dict" LabelProperty="c=>c.Key" ValueProperty="c=>c.Value" @bind-Value="value" DisabledPredicate="@(c=> c.Key == "Disabled")" />);
|
||||
cut.Render();
|
||||
var items = cut.FindAll(".ant-select-item");
|
||||
var defaultSelection = cut.Find(".ant-select-selection-item").TextContent.Trim();
|
||||
|
||||
var overlay = cut.FindComponent<AntDesign.Internal.OverlayTrigger>();
|
||||
await cut.InvokeAsync(() => overlay.Instance.Show());
|
||||
|
||||
cut.FindAll(".ant-select-item-option")[1].Click();
|
||||
cut.WaitForState(() => value == 2);
|
||||
|
||||
defaultSelection.Should().Be("Hello");
|
||||
value.Should().Be(2);
|
||||
|
||||
items.Count.Should().Be(3);
|
||||
items[2].ClassList.Should().Contain("ant-select-item-option-disabled");
|
||||
}
|
||||
#endif
|
||||
}
|
Загрузка…
Ссылка в новой задаче