merge 0.8.1 to feature

This commit is contained in:
James Yeung 2021-05-16 19:58:03 +08:00
commit bfb5e4f396
147 changed files with 4798 additions and 1168 deletions

View File

@ -15,6 +15,61 @@ timeline: true
---
### 0.8.1
`2021-05-13`
- Overlay
- 🐞 Fixed positioning should take scroll into account. [#1511](https://github.com/ant-design-blazor/ant-design-blazor/pull/1511) [@ocoka](https://github.com/ocoka)
- 🐞 Fixed issues in boundaryAdjustMode. [#1420](https://github.com/ant-design-blazor/ant-design-blazor/pull/1420) [@mutouzdl](https://github.com/mutouzdl)
- Input
- 🐞 Fixed for Guid type. [#1510](https://github.com/ant-design-blazor/ant-design-blazor/pull/1510) [@anranruye](https://github.com/anranruye)
- 🐞 Added `CultureInfo` attribute to `Input` type components. [#1480](https://github.com/ant-design-blazor/ant-design-blazor/pull/1480) [@anddrzejb](https://github.com/anddrzejb)
- 🐞 Fixed binding data when the Enter key is pressed. [#1375](https://github.com/ant-design-blazor/ant-design-blazor/pull/1375) [@ElderJames](https://github.com/ElderJames)
- Table
- 🐞 Fixed built-in filter select option width. [#1500](https://github.com/ant-design-blazor/ant-design-blazor/pull/1500) [@anranruye](https://github.com/anranruye)
- 🐞 Fixed error for EndsWith filter operator. [#1434](https://github.com/ant-design-blazor/ant-design-blazor/pull/1434) [@anranruye](https://github.com/anranruye)
- 🐞 Fixed column header sorter not refresh after ClearSorter is called [#1385](https://github.com/ant-design-blazor/ant-design-blazor/pull/1385) [@anranruye](https://github.com/anranruye)
- 🐞 Fixed can not use DataIndex nullable mode with not nullable property [#1382](https://github.com/ant-design-blazor/ant-design-blazor/pull/1382) [@anranruye](https://github.com/anranruye)
- 🐞 Fixed Filter for DataIndex. Unify FieldName, add DisplayAttribute for DiplayName. [#1372](https://github.com/ant-design-blazor/ant-design-blazor/pull/1372) [@Zonciu](https://github.com/Zonciu)
- 🐞 Fixed ellipsis can't work. [#1376](https://github.com/ant-design-blazor/ant-design-blazor/pull/1376) [@ElderJames](https://github.com/ElderJames)
- Cascader
- 🐞 Fixed showSearch. [#1484](https://github.com/ant-design-blazor/ant-design-blazor/pull/1484) [@ElderJames](https://github.com/ElderJames)
- 🐞 Fixed invoking SelectedNodesChanged after clear selected. [#1437](https://github.com/ant-design-blazor/ant-design-blazor/pull/1437) [@ElderJames](https://github.com/ElderJames)
- 🐞 Fixed incorrect size. [#1432](https://github.com/ant-design-blazor/ant-design-blazor/pull/1432) [@ElderJames](https://github.com/ElderJames)
- DatePicker
- 🐞 Fixed panel click closing + some issues from #1431. [#1452](https://github.com/ant-design-blazor/ant-design-blazor/pull/1452) [@anddrzejb](https://github.com/anddrzejb)
- 🐞 Fixed validate manually entered date against format. [#1389](https://github.com/ant-design-blazor/ant-design-blazor/pull/1389) [@anddrzejb](https://github.com/anddrzejb)
- Modal
- 🐞 Fixed Delay time to DOM_ MIN_ TIMEOUT_ VALUE (4ms). [#1445](https://github.com/ant-design-blazor/ant-design-blazor/pull/1445) [@zxyao145](https://github.com/zxyao145)
- 🐞 Fixed add Dispose lifecycle function to Dialog. [#1379](https://github.com/ant-design-blazor/ant-design-blazor/pull/1379) [@zxyao145](https://github.com/zxyao145)
- 🆕 support define modal's style in ModalOptions [#1400](https://github.com/ant-design-blazor/ant-design-blazor/pull/1400) [@zxyao145](https://github.com/zxyao145)
- Form
- 🆕 Select mutliple/tags can be used in forms. [#1460](https://github.com/ant-design-blazor/ant-design-blazor/pull/1460) [@anddrzejb](https://github.com/anddrzejb)
- 🐞 Fixed validation message unique [#1391](https://github.com/ant-design-blazor/ant-design-blazor/pull/1391) [@ElderJames](https://github.com/ElderJames)
- Select
- 🐞 Fixed error for nullable TItem of SelectOption. [#1451](https://github.com/ant-design-blazor/ant-design-blazor/pull/1451) [@anranruye](https://github.com/anranruye)
- 🛠 Refactor: use ResizeObserver Api instead of window.resize. [#1392](https://github.com/ant-design-blazor/ant-design-blazor/pull/1392) [@anddrzejb](https://github.com/anddrzejb)
- 🐞 Fixed OnDataSourceChange called when expected. [#1419](https://github.com/ant-design-blazor/ant-design-blazor/pull/1419) [@anddrzejb](https://github.com/anddrzejb)
- 🐞 Fixed enum default value protection. [#1368](https://github.com/ant-design-blazor/ant-design-blazor/pull/1368) [@anddrzejb](https://github.com/anddrzejb)
- 🐞 Fixed checkbox remove Value initialization blocking. [#1459](https://github.com/ant-design-blazor/ant-design-blazor/pull/1459) [@anddrzejb](https://github.com/anddrzejb)
- 🐞 Fixed input number self changing. [#1490](https://github.com/ant-design-blazor/ant-design-blazor/pull/1490) [@CAPCHIK](https://github.com/CAPCHIK)
- 🐞 `Checkbox` and `Switch` allow now binding to `Changed` property. `Value` and `Changed` properties can be used interchangeably. [#1394](https://github.com/ant-design-blazor/ant-design-blazor/pull/1394) [@anddrzejb](https://github.com/anddrzejb)
- 🐞 Fixed Tag closable typo and delete mode [#1393](https://github.com/ant-design-blazor/ant-design-blazor/pull/1393) [@ElderJames](https://github.com/ElderJames)
- 🐞 Fixed PasswordInput for retrieving and setting the `selectionStart`; Override `onClick`. [#1377](https://github.com/ant-design-blazor/ant-design-blazor/pull/1377) [@MihailsKuzmins](https://github.com/MihailsKuzmins)
- 🆕 feat: add element component. [#1378](https://github.com/ant-design-blazor/ant-design-blazor/pull/1378) [@ElderJames](https://github.com/ElderJames)
- 🐞 Fixed Affix can't affix while OffsetTop is zero. [#1373](https://github.com/ant-design-blazor/ant-design-blazor/pull/1373) [@ElderJames](https://github.com/ElderJames)
- 🐞 Fixed js function getDom return null bug. [#1417](https://github.com/ant-design-blazor/ant-design-blazor/pull/1417) [@zxyao145](https://github.com/zxyao145)
- 🐞 Fixed dropdown width for IE. [#1469](https://github.com/ant-design-blazor/ant-design-blazor/pull/1469) [@anranruye](https://github.com/anranruye)
### 0.8.0
`2021-04-15`

View File

@ -15,24 +15,91 @@ timeline: true
---
### 0.8.1
`2021-05-13`
- Overlay
- 🐞 修复 计算高度时加上滚动高度[#1511](https://github.com/ant-design-blazor/ant-design-blazor/pull/1511) [@ocoka](https://github.com/ocoka)
- 🐞 修复 边界调整的问题[#1420](https://github.com/ant-design-blazor/ant-design-blazor/pull/1420) [@mutouzdl](https://github.com/mutouzdl)
- Input
- 🐞 修复 不能使用 Guid 类型的问题。[#1510](https://github.com/ant-design-blazor/ant-design-blazor/pull/1510) [@anranruye](https://github.com/anranruye)
- 🐞 修复 字符串与特定类型的转换问题,增加了 `CultureInfo` 属性。[#1480](https://github.com/ant-design-blazor/ant-design-blazor/pull/1480) [@anddrzejb](https://github.com/anddrzejb)
- 🐞 修复 按回车键的数据绑定问题。[#1375](https://github.com/ant-design-blazor/ant-design-blazor/pull/1375) [@ElderJames](https://github.com/ElderJames)
- Table
- 🐞 修复 内置筛选器选项菜单的宽度[#1500](https://github.com/ant-design-blazor/ant-design-blazor/pull/1500) [@anranruye](https://github.com/anranruye)
- 🐞 修复 使用“结尾是”过滤条件时的错误。[#1434](https://github.com/ant-design-blazor/ant-design-blazor/pull/1434) [@anranruye](https://github.com/anranruye)
- 🐞 修复 点击清除排序时不刷新的问题。[#1385](https://github.com/ant-design-blazor/ant-design-blazor/pull/1385) [@anranruye](https://github.com/anranruye)
- 🐞 修复 无法使用 DataIndex 绑定可空属性的问题[#1382](https://github.com/ant-design-blazor/ant-design-blazor/pull/1382) [@anranruye](https://github.com/anranruye)
- 🐞 修复 筛选器对 DataIndex 的支持,统一 FieldName 定义,添加列名 DisplayAttribute 支持。[#1372](https://github.com/ant-design-blazor/ant-design-blazor/pull/1372) [@Zonciu](https://github.com/Zonciu)
- 🐞 修复 ellipsis 无效的问题。[#1376](https://github.com/ant-design-blazor/ant-design-blazor/pull/1376) [@ElderJames](https://github.com/ElderJames)
- Cascader
- 🐞 修复 搜索功能。[#1484](https://github.com/ant-design-blazor/ant-design-blazor/pull/1484) [@ElderJames](https://github.com/ElderJames)
- 🐞 修复 当点击清楚按钮时触发 SelectedNodesChanged。[#1437](https://github.com/ant-design-blazor/ant-design-blazor/pull/1437) [@ElderJames](https://github.com/ElderJames)
- 🐞 修复 无法设置 Size 的问题。[#1432](https://github.com/ant-design-blazor/ant-design-blazor/pull/1432) [@ElderJames](https://github.com/ElderJames)
- DatePicker
- 🐞 修复 DatePicker 点击面板头部会关闭问题[#1452](https://github.com/ant-design-blazor/ant-design-blazor/pull/1452) [@anddrzejb](https://github.com/anddrzejb)
- 🐞 修复 验证手动输入格式的问题[#1389](https://github.com/ant-design-blazor/ant-design-blazor/pull/1389) [@anddrzejb](https://github.com/anddrzejb)
- Modal
- 🆕 可通过 ModalOptions 设置 Style。 [#1400](https://github.com/ant-design-blazor/ant-design-blazor/pull/1400) [@zxyao145](https://github.com/zxyao145)
- 🐞 修改 Dialog 组件中 Mask 点击判断 Task.Delay 的时间为 DOM* MIN* TIMEOUT\_ VALUE (4ms)。[#1445](https://github.com/ant-design-blazor/ant-design-blazor/pull/1445) [@zxyao145](https://github.com/zxyao145)
- 🐞 修复 Dialog 关闭时不恢复显示滚动条的问题,为 Dialog 添加 Dispose。[#1379](https://github.com/ant-design-blazor/ant-design-blazor/pull/1379) [@zxyao145](https://github.com/zxyao145)
- Form
- 🆕 使 Form 支持集合Select 可绑定 Values[#1460](https://github.com/ant-design-blazor/ant-design-blazor/pull/1460) [@anddrzejb](https://github.com/anddrzejb)
- 🐞 修复验证信息会重复的问题[#1391](https://github.com/ant-design-blazor/ant-design-blazor/pull/1391) [@ElderJames](https://github.com/ElderJames)
- Select
- 🐞 修复在 SelectOption 中使用可空值类型时的错误。[#1451](https://github.com/ant-design-blazor/ant-design-blazor/pull/1451) [@anranruye](https://github.com/anranruye)
- 🛠 使用 ResizeObserver 重构响应式时浏览器尺寸事件的订阅[#1392](https://github.com/ant-design-blazor/ant-design-blazor/pull/1392) [@anddrzejb](https://github.com/anddrzejb)
- 🐞 当 DataSource 改变时触发 OnDataSourceChanged[#1419](https://github.com/ant-design-blazor/ant-design-blazor/pull/1419) [@anddrzejb](https://github.com/anddrzejb)
- 🐞 修复值为枚举时的问题[#1368](https://github.com/ant-design-blazor/ant-design-blazor/pull/1368) [@anddrzejb](https://github.com/anddrzejb)
- 🆕 新增 Element 组件,用于动态渲染元素[#1378](https://github.com/ant-design-blazor/ant-design-blazor/pull/1378) [@ElderJames](https://github.com/ElderJames)
- 🐞 修复 Checkbox 的 Value 在初始化时是阻塞[#1459](https://github.com/ant-design-blazor/ant-design-blazor/pull/1459) [@anddrzejb](https://github.com/anddrzejb)
- 🐞 修复 InputNumber 按住时,离开组件还会自增的问题。[#1490](https://github.com/ant-design-blazor/ant-design-blazor/pull/1490) [@CAPCHIK](https://github.com/CAPCHIK)
- 🐞 修复 `Checkbox` and `Switch` 组件的 Value 和 Checked 绑定问题[#1394](https://github.com/ant-design-blazor/ant-design-blazor/pull/1394) [@anddrzejb](https://github.com/anddrzejb)
- 🐞 修复 Tag 的 closable 拼写错误,和删除 Mode 属性[#1393](https://github.com/ant-design-blazor/ant-design-blazor/pull/1393) [@ElderJames](https://github.com/ElderJames)
- 🐞 修复 InputPassword 切换明文时,保持焦点和光标位置。[#1377](https://github.com/ant-design-blazor/ant-design-blazor/pull/1377) [@MihailsKuzmins](https://github.com/MihailsKuzmins)
- 🐞 修复 Affix 当 OffsetTop 为 0 时不能钉住的问题。[#1373](https://github.com/ant-design-blazor/ant-design-blazor/pull/1373) [@ElderJames](https://github.com/ElderJames)
- 🐞 修复 getDom 函数可能返回 null 的 bug。[#1417](https://github.com/ant-design-blazor/ant-design-blazor/pull/1417) [@zxyao145](https://github.com/zxyao145)
- 🐞 修复 IE 浏览器下拉选项宽度为 0 的问题。[#1469](https://github.com/ant-design-blazor/ant-design-blazor/pull/1469) [@anranruye](https://github.com/anranruye)
### 0.8.0
`2021-04-15`
- 主题和国际化
- 🔥 文档支持主题色的动态切换[#1332](https://github.com/ant-design-blazor/ant-design-blazor/pull/1332) [@ElderJames](https://github.com/ElderJames)
- 🔥 增加 RTL 切换。[#1238](https://github.com/ant-design-blazor/ant-design-blazor/pull/1238) [@ElderJames](https://github.com/ElderJames)
- 🔥 加入内置主题样式。[#1286](https://github.com/ant-design-blazor/ant-design-blazor/pull/1286) [@ElderJames](https://github.com/ElderJames)
- Form
- 📖 修改 IsModified 的实例。[#1344](https://github.com/ant-design-blazor/ant-design-blazor/pull/1344) [@anddrzejb](https://github.com/anddrzejb)
- 🆕 增加 LabelAlign 属性,可使 Label 左对齐[#1292](https://github.com/ant-design-blazor/ant-design-blazor/pull/1292) [@unsung189](https://github.com/unsung189)
- Select
- 🆕 增加 `MaxCountTag`, `MaxTagPlaceholder``MaxTagTextLenght`以支持Tag模式的响应式处理。[#1338](https://github.com/ant-design-blazor/ant-design-blazor/pull/1338) [@anddrzejb](https://github.com/anddrzejb)
- 🆕 增加 `PopupContainerGrowToMatchWidestItem` 和`PopupContainerMaxWidth` 使下拉列表的宽度适应内容或Input的宽度[#1309](https://github.com/ant-design-blazor/ant-design-blazor/pull/1309) [@anddrzejb](https://github.com/anddrzejb)
- 🆕 增加 `MaxCountTag`, `MaxTagPlaceholder``MaxTagTextLenght`,以支持 Tag 模式的响应式处理。[#1338](https://github.com/ant-design-blazor/ant-design-blazor/pull/1338) [@anddrzejb](https://github.com/anddrzejb)
- 🆕 增加 `PopupContainerGrowToMatchWidestItem` 和`PopupContainerMaxWidth` ,使下拉列表的宽度适应内容或 Input 的宽度[#1309](https://github.com/ant-design-blazor/ant-design-blazor/pull/1309) [@anddrzejb](https://github.com/anddrzejb)
- Table
- 🔥 增加内置筛选器[#1267](https://github.com/ant-design-blazor/ant-design-blazor/pull/1267) [@YMohd](https://github.com/YMohd)
- 🆕 支持 DisplayAttribute 特性指定列名[#1310](https://github.com/ant-design-blazor/ant-design-blazor/pull/1310) [@anranruye](https://github.com/anranruye)
- 🆕 增加总结行。[#1218](https://github.com/ant-design-blazor/ant-design-blazor/pull/1218) [@ElderJames](https://github.com/ElderJames)
@ -40,8 +107,9 @@ timeline: true
- 🆕 增加 `GetQueryModel` 方法。[#1202](https://github.com/ant-design-blazor/ant-design-blazor/pull/1202) [@ElderJames](https://github.com/ElderJames)
- Date Picker
- 🐞 修复在输入后触发 OnChange 事件[#1347](https://github.com/ant-design-blazor/ant-design-blazor/pull/1347) [@anddrzejb](https://github.com/anddrzejb)
- 🐞 修复resize事件处理方法[#1322](https://github.com/ant-design-blazor/ant-design-blazor/pull/1322) [@anddrzejb](https://github.com/anddrzejb)
- 🐞 修复 resize 事件处理方法[#1322](https://github.com/ant-design-blazor/ant-design-blazor/pull/1322) [@anddrzejb](https://github.com/anddrzejb)
- 🆕 Space 增加 Wrap、Split 以及 Size 支持数组。[#1314](https://github.com/ant-design-blazor/ant-design-blazor/pull/1314) [@ElderJames](https://github.com/ElderJames)
- 🆕 Alert 增加 Message 模板,增加轮播公告示例[#1250](https://github.com/ant-design-blazor/ant-design-blazor/pull/1250) [@MutatePat](https://github.com/MutatePat)
@ -63,15 +131,16 @@ timeline: true
`2021-04-08`
- Table
- 🐞 修复设置ScrollX时表格不重新渲染的问题。[#1311](https://github.com/ant-design-blazor/ant-design-blazor/pull/1311) [@Zonciu](https://github.com/Zonciu)
- 🐞 修改修改DataSource会抛出异常的问题。[5b0dbfb](https://github.com/ant-design-blazor/ant-design-blazor/commit/5b0dbfb) [@Andrzej Bakun](https://github.com/Andrzej Bakun)
- 🐞 修复DataIndex列过滤器无效的问题, 修复DataIndex列不刷新的问题。[#1295](https://github.com/ant-design-blazor/ant-design-blazor/pull/1295) [@Zonciu](https://github.com/Zonciu)
- 🐞 修复设置 ScrollX 时表格不重新渲染的问题。[#1311](https://github.com/ant-design-blazor/ant-design-blazor/pull/1311) [@Zonciu](https://github.com/Zonciu)
- 🐞 修改修改 DataSource 会抛出异常的问题。[5b0dbfb](https://github.com/ant-design-blazor/ant-design-blazor/commit/5b0dbfb) [@Andrzej Bakun](https://github.com/Andrzej Bakun)
- 🐞 修复 DataIndex 列过滤器无效的问题, 修复 DataIndex 列不刷新的问题。[#1295](https://github.com/ant-design-blazor/ant-design-blazor/pull/1295) [@Zonciu](https://github.com/Zonciu)
- 🐞 ExpandIconColumnIndex 指定到 ActionColumn 时无效的问题。[#1285](https://github.com/ant-design-blazor/ant-design-blazor/pull/1285) [@Magehernan](https://github.com/Magehernan)
- 🐞 优化性能并修复 DataSource 更新问题 [#1304](https://github.com/ant-design-blazor/ant-design-blazor/pull/1304) [@anddrzejb](https://github.com/anddrzejb)
- Select
- 🐞 修复多选时点击关闭选项时,会触发下拉菜单的问题。[#1308](https://github.com/ant-design-blazor/ant-design-blazor/pull/1308) [@anddrzejb](https://github.com/anddrzejb)
- 🐞 修复 Tag模式的 Loading 图标问题。[12ca2f7](https://github.com/ant-design-blazor/ant-design-blazor/commit/12ca2f7) [@Andrzej Bakun](https://github.com/Andrzej Bakun)
- 🐞 修复 Tag 模式的 Loading 图标问题。[12ca2f7](https://github.com/ant-design-blazor/ant-design-blazor/commit/12ca2f7) [@Andrzej Bakun](https://github.com/Andrzej Bakun)
- 💄 修复 flex 和 wrap 的样式。[#1296](https://github.com/ant-design-blazor/ant-design-blazor/pull/1296) [@ElderJames](https://github.com/ElderJames)
- 🐞 使默认值为空字符串。[6944c13](https://github.com/ant-design-blazor/ant-design-blazor/commit/6944c13) [@Andrzej Bakun](https://github.com/Andrzej Bakun)
- 🐞 修复文件列表。[53c1285](https://github.com/ant-design-blazor/ant-design-blazor/commit/53c1285) [@Andrzej Bakun](https://github.com/Andrzej Bakun)

View File

@ -4,8 +4,7 @@
<div @ref="Ref">
@if (_affixed)
{
<div aria-hidden="true" style="@_hiddenStyle">
</div>
<div aria-hidden="true" style="@_hiddenStyle"></div>
}
<div class="@ClassMapper.Class" @ref="_childRef" style="@_affixStyle">
@ChildContent

View File

@ -23,7 +23,7 @@ namespace AntDesign
if (_affixed != value)
{
_affixed = value;
StateHasChanged();
if (OnChange.HasDelegate)
{
OnChange.InvokeAsync(_affixed);
@ -45,13 +45,13 @@ namespace AntDesign
/// Offset from the bottom of the viewport (in pixels)
/// </summary>
[Parameter]
public uint? OffsetBottom { get; set; }
public int OffsetBottom { get; set; }
/// <summary>
/// Offset from the top of the viewport (in pixels)
/// </summary>
[Parameter]
public uint? OffsetTop { get; set; } = 0;
public int OffsetTop { get; set; }
[Parameter]
public string TargetSelector { get; set; }
@ -68,7 +68,8 @@ namespace AntDesign
{
base.OnInitialized();
SetClasses();
ClassMapper
.If(PrefixCls, () => _affixed);
}
public async override Task SetParametersAsync(ParameterView parameters)
@ -80,14 +81,20 @@ namespace AntDesign
{
await base.OnFirstAfterRenderAsync();
var domRect = await JsInvokeAsync<DomRect>(JSInteropConstants.GetBoundingClientRect, _childRef);
_hiddenStyle = $"width: {domRect.width}px; height: {domRect.height}px;";
if (ChildContent == null)
{
return;
}
var domRect = await JsInvokeAsync<DomRect>(JSInteropConstants.GetBoundingClientRect, _childRef);
_hiddenStyle = $"width: {domRect.Width}px; height: {domRect.Height}px;";
DomEventService.AddEventListener(RootScollSelector, "scroll", OnWindowScroll, false);
DomEventService.AddEventListener(RootScollSelector, "resize", OnWindowResize, false);
await RenderAffixAsync();
if (!_rootListened && string.IsNullOrEmpty(TargetSelector))
{
DomEventService.AddEventListener(RootScollSelector, "scroll", OnWindowScroll, false);
DomEventService.AddEventListener(RootScollSelector, "resize", OnWindowResize, false);
_rootListened = true;
}
else if (!string.IsNullOrEmpty(TargetSelector))
@ -98,46 +105,17 @@ namespace AntDesign
}
}
private async void OnWindowScroll(JsonElement obj)
{
await RenderAffixAsync(true);
}
private async void OnWindowScroll(JsonElement obj) => await RenderAffixAsync();
private async void OnWindowResize(JsonElement obj)
{
await RenderAffixAsync(true);
}
private async void OnWindowResize(JsonElement obj) => await RenderAffixAsync();
private async void OnTargetScroll(JsonElement obj)
{
await RenderAffixAsync();
}
private async void OnTargetScroll(JsonElement obj) => await RenderAffixAsync();
private async void OnTargetResize(JsonElement obj)
{
await RenderAffixAsync();
}
private async void OnTargetResize(JsonElement obj) => await RenderAffixAsync();
private void SetClasses()
private async Task RenderAffixAsync()
{
ClassMapper.Clear()
.If(PrefixCls, () => _affixed);
}
private async Task RenderAffixAsync(bool windowscrolled = false)
{
if (windowscrolled && !string.IsNullOrEmpty(TargetSelector))
{
if (!Affixed)
{
return;
}
_affixStyle = string.Empty;
Affixed = false;
StateHasChanged();
return;
}
var originalAffixStyle = _affixStyle;
DomRect childRect = null;
DomRect domRect = null;
Window window = null;
@ -163,30 +141,31 @@ namespace AntDesign
return;
}
_hiddenStyle = $"width: {childRect.width}px; height: {childRect.height}px;";
_hiddenStyle = $"width: {childRect.Width}px; height: {childRect.Height}px;";
DomRect containerRect;
if (string.IsNullOrEmpty(TargetSelector))
{
containerRect = new DomRect()
{
top = 0,
bottom = window.innerHeight,
height = window.innerHeight,
Top = 0,
Bottom = window.innerHeight,
Height = window.innerHeight,
};
}
else
{
containerRect = await JsInvokeAsync<DomRect>(JSInteropConstants.GetBoundingClientRect, TargetSelector);
}
// become affixed
if (OffsetBottom.HasValue)
var topDist = containerRect.Top + OffsetTop;
var bottomDist = containerRect.Bottom - OffsetBottom;
if (OffsetBottom > 0) // only affix bottom
{
// domRect.bottom / domRect.top have the identical value here.
var bottom = containerRect.bottom - OffsetBottom;
if (domRect.bottom > bottom)
if (domRect.Bottom > bottomDist)
{
_affixStyle = _hiddenStyle + $"bottom: { window.innerHeight - bottom}px; position: fixed;";
_affixStyle = _hiddenStyle + $"bottom: { window.innerHeight - bottomDist}px; position: fixed;";
Affixed = true;
}
else
@ -195,29 +174,37 @@ namespace AntDesign
Affixed = false;
}
}
else if (OffsetTop.HasValue)
else if (domRect.Top < topDist)
{
var top = containerRect.top + OffsetTop;
if (domRect.top < top && top > 0)
{
_affixStyle = _hiddenStyle + $"top: {top}px; position: fixed;";
Affixed = true;
}
else
{
_affixStyle = string.Empty;
Affixed = false;
}
_affixStyle = _hiddenStyle + $"top: {topDist}px; position: fixed;";
Affixed = true;
}
else
{
_affixStyle = string.Empty;
Affixed = false;
}
StateHasChanged();
if (originalAffixStyle != _affixStyle)
{
StateHasChanged();
}
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
DomEventService.RemoveEventListerner<JsonElement>(RootScollSelector, "scroll", OnWindowScroll);
DomEventService.RemoveEventListerner<JsonElement>(RootScollSelector, "resize", OnWindowResize);
if (string.IsNullOrEmpty(TargetSelector))
{
DomEventService.RemoveEventListerner<JsonElement>(RootScollSelector, "scroll", OnWindowScroll);
DomEventService.RemoveEventListerner<JsonElement>(RootScollSelector, "resize", OnWindowResize);
}
else
{
DomEventService.RemoveEventListerner<JsonElement>(TargetSelector, "scroll", OnWindowScroll);
DomEventService.RemoveEventListerner<JsonElement>(TargetSelector, "resize", OnWindowResize);
}
}
}
}

View File

@ -148,8 +148,8 @@ namespace AntDesign
if (firstRender)
{
Element element = await JsInvokeAsync<Element>(JSInteropConstants.GetDomInfo, Ref);
_height = element.clientHeight;
HtmlElement element = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, Ref);
_height = element.ClientHeight;
}
}

View File

@ -158,7 +158,7 @@ namespace AntDesign
_activatedByClick = false;
await ActivateAsync(link, true);
// the offset does not matter, since the dictionary's value will not change any more in case user set up GetCurrentAnchor
_linkTops[link.Href] = hrefDom.top;
_linkTops[link.Href] = hrefDom.Top;
StateHasChanged();
}
}
@ -223,7 +223,7 @@ namespace AntDesign
DomRect hrefDom = await link.GetHrefDom();
if (hrefDom != null)
{
_linkTops[link.Href] = hrefDom.top + offset;
_linkTops[link.Href] = hrefDom.Top + offset;
}
}
catch (Exception ex)
@ -242,7 +242,7 @@ namespace AntDesign
if (Affix && _activeLink != null)
{
_ballClass = "ant-anchor-ink-ball visible";
decimal top = (_activeLink.LinkDom.top - _selfDom.top) + _activeLink.LinkDom.height / 2 - 2;
decimal top = (_activeLink.LinkDom.Top - _selfDom.Top) + _activeLink.LinkDom.Height / 2 - 2;
_ballStyle = $"top: {top}px;";
}
else

View File

@ -23,6 +23,7 @@ namespace AntDesign
[Parameter]
public bool DefaultActiveFirstOption { get; set; } = true;
[Parameter]
public bool Backfill { get; set; } = false;
@ -40,6 +41,7 @@ namespace AntDesign
/// 列表绑定数据源集合
/// </summary>
private IEnumerable<TOption> _options;
[Parameter]
public IEnumerable<TOption> Options
{
@ -72,6 +74,7 @@ namespace AntDesign
[Parameter]
public EventCallback<AutoCompleteOption> OnSelectionChange { get; set; }
[Parameter]
public EventCallback<AutoCompleteOption> OnActiveChange { get; set; }
@ -138,6 +141,7 @@ namespace AntDesign
private OverlayTrigger _overlayTrigger;
public object SelectedValue { get; set; }
/// <summary>
/// 选择的项
/// </summary>
@ -149,7 +153,6 @@ namespace AntDesign
/// </summary>
public object ActiveValue { get; set; }
[Parameter]
public bool ShowPanel { get; set; } = false;
@ -163,6 +166,7 @@ namespace AntDesign
}
#region
public async Task InputFocus(FocusEventArgs e)
{
if (!_isOptionsZero)
@ -208,7 +212,7 @@ namespace AntDesign
}
}
#endregion
#endregion
protected override void OnParametersSet()
{
@ -222,8 +226,6 @@ namespace AntDesign
await base.OnFirstAfterRenderAsync();
}
public void AddOption(AutoCompleteOption option)
{
AutoCompleteOptions.Add(option);
@ -371,7 +373,7 @@ namespace AntDesign
this.ClosePanel();
}
bool _parPanelVisible = false;
private bool _parPanelVisible = false;
private async void OnOverlayTriggerVisibleChange(bool visible)
{
@ -388,6 +390,7 @@ namespace AntDesign
}
private string _minWidth = "";
private async Task SetOverlayWidth()
{
string newWidth;
@ -398,7 +401,7 @@ namespace AntDesign
}
else
{
Element element = await JsInvokeAsync<Element>(JSInteropConstants.GetDomInfo, _overlayTrigger.RefBack.Current); ;
HtmlElement element = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, _overlayTrigger.RefBack.Current); ;
//Element element;
//if (_divRef.Id != null)
//{
@ -408,7 +411,7 @@ namespace AntDesign
//{
// element = await JsInvokeAsync<Element>(JSInteropConstants.GetDomInfo, _overlayTrigger.RefBack.Current);
//}
newWidth = $"min-width:{element.clientWidth}px";
newWidth = $"min-width:{element.ClientWidth}px";
}
if (newWidth != _minWidth) _minWidth = newWidth;
}
@ -416,7 +419,9 @@ namespace AntDesign
public class AutoCompleteDataItem<TOption>
{
public AutoCompleteDataItem() { }
public AutoCompleteDataItem()
{
}
public AutoCompleteDataItem(TOption value, string label)
{
@ -430,4 +435,3 @@ namespace AntDesign
public bool IsDisabled { get; set; }
}
}

View File

@ -161,8 +161,8 @@ namespace AntDesign
return;
}
var childrenWidth = (await JsInvokeAsync<Element>(JSInteropConstants.GetDomInfo, TextEl))?.offsetWidth ?? 0;
var avatarWidth = (await JsInvokeAsync<DomRect>(JSInteropConstants.GetBoundingClientRect, Ref))?.width ?? 0;
var childrenWidth = (await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, TextEl))?.OffsetWidth ?? 0;
var avatarWidth = (await JsInvokeAsync<DomRect>(JSInteropConstants.GetBoundingClientRect, Ref))?.Width ?? 0;
var scale = childrenWidth != 0 && avatarWidth - 8 < childrenWidth ? (avatarWidth - 8) / childrenWidth : 1;
_textStyles = $"transform: scale({new CssSizeLength(scale, true)}) translateX(-50%);";
if (decimal.TryParse(Size, out var pxSize))

View File

@ -137,13 +137,13 @@ namespace AntDesign
private async void Resize(JsonElement e = default)
{
DomRect listRect = await JsInvokeAsync<DomRect>(JSInteropConstants.GetBoundingClientRect, Ref);
if ((SlickWidth != (int)listRect.width && IsHorizontal)
|| (SlickHeight != (int)listRect.height && !IsHorizontal)
if ((SlickWidth != (int)listRect.Width && IsHorizontal)
|| (SlickHeight != (int)listRect.Height && !IsHorizontal)
|| IsHorizontal && !string.IsNullOrEmpty(SlickListStyle)
|| !IsHorizontal && string.IsNullOrEmpty(SlickListStyle))
{
SlickWidth = (int)listRect.width;
SlickHeight = (int)listRect.height;
SlickWidth = (int)listRect.Width;
SlickHeight = (int)listRect.Height;
StateHasChanged();
}
}

View File

@ -3,73 +3,116 @@
@inherits AntInputComponentBase<string>
<CascadingValue Value=@("ant-cascader-menus") Name="PrefixCls">
<OverlayTrigger Visible="@(ToggleState && _nodelist != null && _nodelist.Any())"
ComplexAutoCloseAndVisible="true"
Trigger="new[] { TriggerType.Click }"
PopupContainerSelector="@PopupContainerSelector"
OverlayEnterCls="ant-slide-up-enter ant-slide-up-enter-active ant-slide-up"
OverlayLeaveCls="ant-slide-up-leave ant-slide-up-leave-active ant-slide-up">
<Unbound>
<span @ref="context.Current" style="@Style" id="@Id" class="@ClassMapper.Class" tabindex="1" @onclick="InputOnToggle" @onblur="CascaderOnBlur" @onmouseover="InputOnMouseOver" @onmouseout="InputOnMouseOut">
<span class="ant-cascader-picker-label"></span>
<input autocomplete="off" tabindex="-1" placeholder="@PlaceHolder" class="@_inputClassMapper.Class" readonly="@Readonly" type="text" value="@_displayText">
@if (string.IsNullOrWhiteSpace(Value) || !ShowClearIcon)
<OverlayTrigger Visible="@_dropdownOpened"
OnMaskClick="CascaderOnBlur"
Trigger="new TriggerType[] { }"
PopupContainerSelector="@PopupContainerSelector"
OverlayEnterCls="ant-slide-up-enter ant-slide-up-enter-active ant-slide-up"
OverlayLeaveCls="ant-slide-up-leave ant-slide-up-leave-active ant-slide-up">
<Unbound>
<span @ref="context.Current" style="@Style" id="@Id" class="@ClassMapper.Class" tabindex="1"
@onmouseover="InputOnMouseOver"
@onmouseout="InputOnMouseOut"
@onclick="InputOnToggle">
<span class="ant-cascader-picker-label">@_displayText</span>
<input @ref="_inputRef"
autocomplete="off"
tabindex="-1"
placeholder="@(string.IsNullOrWhiteSpace(_displayText)?_placeHolder:"")"
class="@_inputClassMapper.Class"
readonly="@(!ShowSearch)"
type="text"
value="@_searchValue"
@oninput="OnSearchInput"
@onkeyup="OnSearchKeyUp">
@if (!_showClearIcon)
{
<Icon Type="down" Class="@($"ant-cascader-picker-arrow {(_dropdownOpened ? "ant-cascader-picker-arrow-expand" : string.Empty)}")" StopPropagation />
}
else
{
<Icon Type="close-circle" Theme="fill" Class="ant-cascader-picker-clear" OnClick="ClearSelected" StopPropagation />
}
</span>
</Unbound>
<Overlay>
<div class="ant-cascader-menus ant-cascader-menus-placement-bottomLeft" tabindex="-1">
<div @onmouseover="NodesOnMouseOver" @onmouseout="NodesOnMouseOut">
@if (!ShowSearch || string.IsNullOrWhiteSpace(_searchValue))
{
<Icon Type="down" Class="@($"ant-cascader-picker-arrow {(ToggleState ? "ant-cascader-picker-arrow-expand" : string.Empty)}")" />
}
else
{
<Icon Type="close-circle" Class="ant-cascader-picker-clear" OnClick="ClearSelected" StopPropagation />
}
</span>
</Unbound>
<Overlay>
<div class="ant-cascader-menus ant-cascader-menus-placement-bottomLeft" tabindex="-1" @onblur="CascaderOnBlur">
<div @onmouseover="NodesOnMouseOver" @onmouseout="NodesOnMouseOut">
<ul class="@_menuClassMapper.Class">
@foreach (CascaderNode nd in _nodelist)
{
bool isActive = _renderNodes.Where(n => n == nd).Any();
string activeClass = isActive ? "ant-cascader-menu-item-active" : string.Empty;
<li class="ant-cascader-menu-item ant-cascader-menu-item-expand @activeClass @(nd.Disabled ? "ant-cascader-menu-item-disabled" : string.Empty)" title="@nd.Label" role="menuitem"
@onclick="@(e => NodeOnClick(nd))" @onmouseover="@(e => NodeOnMouseOver(nd))">
@nd.Label
@if (nd.HasChildren)
{
<span class="ant-cascader-menu-item-expand-icon">
<Icon Type="right" />
</span>
}
</li>
}
@menuList((_nodelist, this))
</ul>
@foreach (CascaderNode node in _renderNodes)
{
if (node.HasChildren)
{
<ul class="ant-cascader-menu">
@foreach (CascaderNode m in node.Children)
{
bool isActive = _renderNodes.Where(n => n == m).Any();
string activeClass = isActive ? "ant-cascader-menu-item-active" : string.Empty;
<li class="ant-cascader-menu-item ant-cascader-menu-item-expand @activeClass @(m.Disabled ? "ant-cascader-menu-item-disabled" : string.Empty)" title="@m.Label" role="menuitem"
@onclick="@(e => NodeOnClick(m))" @onmouseover="@(e => NodeOnMouseOver(m))">
@m.Label
@if (m.HasChildren)
{
<span class="ant-cascader-menu-item-expand-icon">
<Icon Type="right" />
</span>
}
</li>
}
@menuList((node.Children, this))
</ul>
}
}
</div>
}
else
{
<ul class="@_menuClassMapper.Class" style="@_menuStyle">
@searchList(this)
</ul>
}
</div>
</Overlay>
</OverlayTrigger>
</CascadingValue>
</div>
</Overlay>
</OverlayTrigger>
</CascadingValue>
@code{
RenderFragment<(IEnumerable<CascaderNode> nodes, Cascader cascader)> menuList = context =>@<Template>
@foreach (CascaderNode node in context.nodes)
{
var cascader = context.cascader;
bool isActive = cascader._renderNodes.Where(n => n == node).Any();
string activeClass = isActive ? "ant-cascader-menu-item-active" : string.Empty;
string disabledClass = node.Disabled ? "ant-cascader-menu-item-disabled" : string.Empty;
var events = new Dictionary<string, object>
{
["onclick"] = cascader._callbackFactory.Create(cascader, e => cascader.NodeOnClick(node)),
["onmouseover"] = cascader._callbackFactory.Create(cascader, e => cascader.NodeOnMouseOver(node))
};
<li class="ant-cascader-menu-item ant-cascader-menu-item-expand @activeClass @disabledClass" title="@node.Label" role="menuitem" @attributes="events">
@node.Label
@if (node.HasChildren)
{
<span class="ant-cascader-menu-item-expand-icon">
<Icon Type="right" />
</span>
}
</li>
}
</Template>;
RenderFragment<Cascader> searchList = cascader =>@<Template>
@if (cascader._matchList?.Any() != true)
{
<div class="ant-empty ant-empty-normal ant-empty-small">
<div class="ant-empty-image">
<svg class="ant-empty-img-simple" width="64" height="41" viewBox="0 0 64 41" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 1)" fill="none" fill-rule="evenodd"><ellipse class="ant-empty-img-simple-ellipse" cx="32" cy="33" rx="32" ry="7"></ellipse><g class="ant-empty-img-simple-g" fill-rule="nonzero"><path d="M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z"></path><path d="M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z" class="ant-empty-img-simple-path"></path></g></g></svg>
</div>
<div class="ant-empty-description">@cascader.NotFoundContent</div>
</div>
return;
}
@foreach (var node in cascader._matchList)
{
bool isActive = cascader._renderNodes.Where(n => n == node).Any();
string activeClass = isActive ? "ant-cascader-menu-item-active" : string.Empty;
string disabledClass = node.Disabled ? "ant-cascader-menu-item-disabled" : string.Empty;
var events = new Dictionary<string, object>
{
["onclick"] = cascader._callbackFactory.Create(cascader, e => cascader.NodeOnClick(node)),
};
var label = node.Label.Replace(cascader._searchValue, $"<span class=\"ant-cascader-menu-item-keyword\">{cascader._searchValue}</span>");
<li class="ant-cascader-menu-item @activeClass @disabledClass" title="@node.Label" role="menuitem" @attributes="events">@((MarkupString)label)</li>
}
</Template>;
}

View File

@ -2,64 +2,41 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
using System.Linq;
using System.Collections;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
using AntDesign.JsInterop;
namespace AntDesign
{
public partial class Cascader : AntInputComponentBase<string>
{
[Parameter] public bool Readonly { get; set; } = true;
/// <summary>
/// 是否支持清除
/// </summary>
[Parameter] public bool AllowClear { get; set; } = true;
/// <summary>
/// 是否显示关闭图标
/// </summary>
private bool ShowClearIcon { get; set; }
/// <summary>
/// 当此项为 true 时,点选每级菜单选项值都会发生变化
/// </summary>
[Parameter] public bool ChangeOnSelect { get; set; }
/// <summary>
/// 默认的选中项
/// </summary>
[Parameter] public string DefaultValue { get; set; }
/// <summary>
/// 次级菜单的展开方式,可选 'click' 和 'hover'
/// </summary>
[Parameter] public string ExpandTrigger { get; set; }
/// <summary>
/// 当下拉列表为空时显示的内容
/// </summary>
[Parameter] public string NotFoundContent { get; set; } = "Not Found";
[Parameter] public string NotFoundContent { get; set; } = LocaleProvider.CurrentLocale.Empty.Description;
/// <summary>
/// 输入框占位文本
/// </summary>
[Parameter] public string PlaceHolder { get; set; } = "请选择";
[Parameter] public string Placeholder { get => _placeHolder; set => _placeHolder = value; }
[Parameter] public string PopupContainerSelector { get; set; } = "body";
/// <summary>
/// 在选择框中显示搜索框
/// </summary>
[Parameter] public bool ShowSearch { get; set; }
/// <summary>
/// 选择完成后的回调(参数为选中的节点集合及选中值)
/// Please use SelectedNodesChanged instead.
/// </summary>
[Obsolete("Instead use SelectedNodesChanged.")]
[Parameter] public Action<List<CascaderNode>, string, string> OnChange { get; set; }
[Parameter] public EventCallback<CascaderNode[]> SelectedNodesChanged { get; set; }
[Parameter]
public IReadOnlyCollection<CascaderNode> Options
public IEnumerable<CascaderNode> Options
{
get
{
@ -69,7 +46,7 @@ namespace AntDesign
}
set
{
if (value == null || value.Count == 0)
if (value?.Any() != true)
{
_nodelist = null;
return;
@ -83,42 +60,30 @@ namespace AntDesign
}
private List<CascaderNode> _nodelist;
/// <summary>
/// 选中节点集合(click)
/// </summary>
internal List<CascaderNode> _selectedNodes = new List<CascaderNode>();
/// <summary>
/// 选中节点集合(hover)
/// </summary>
internal List<CascaderNode> _hoverSelectedNodes = new List<CascaderNode>();
/// <summary>
/// 用于渲染下拉节点集合(一级节点除外)
/// </summary>
internal List<CascaderNode> _renderNodes = new List<CascaderNode>();
private List<CascaderNode> _selectedNodes = new List<CascaderNode>();
private List<CascaderNode> _hoverSelectedNodes = new List<CascaderNode>();
private List<CascaderNode> _renderNodes = new List<CascaderNode>();
private List<CascaderNode> _searchList = new List<CascaderNode>();
private IEnumerable<CascaderNode> _matchList;
private ClassMapper _menuClassMapper = new ClassMapper();
private ClassMapper _inputClassMapper = new ClassMapper();
/// <summary>
/// 浮层 展开/折叠状态
/// </summary>
private bool ToggleState { get; set; }
private EventCallbackFactory _callbackFactory = new EventCallbackFactory();
/// <summary>
/// 鼠标是否处于 浮层 之上
/// </summary>
private bool IsOnCascader { get; set; }
/// <summary>
/// 选择节点类型
/// Click: 点击选中节点, Hover: 鼠标移入选中节点
/// </summary>
private SelectedTypeEnum SelectedType { get; set; }
private bool _dropdownOpened;
private bool _isOnCascader;
private SelectedTypeEnum _selectedType;
private bool _showClearIcon;
private string _displayText;
private bool _initialized;
private string _searchValue;
private ElementReference _inputRef;
private string _placeHolder = LocaleProvider.CurrentLocale.Global.Placeholder;
private bool _focused;
private string _menuStyle;
private static Dictionary<string, string> _sizeMap = new Dictionary<string, string>()
{
@ -129,47 +94,45 @@ namespace AntDesign
protected override void OnInitialized()
{
base.OnInitialized();
string prefixCls = "ant-cascader";
ClassMapper
.Add("ant-cascader-picker")
.GetIf(() => $"ant-cascader-picker-{Size}", () => _sizeMap.ContainsKey(Size))
.If("ant-cascader-picker-rtl", () => RTL);
.Add($"{prefixCls}-picker")
.GetIf(() => $"{prefixCls}-picker-{Size}", () => _sizeMap.ContainsKey(Size))
.If($"{prefixCls}-picker-show-search", () => ShowSearch)
.If($"{prefixCls}-picker-with-value", () => !string.IsNullOrEmpty(_searchValue))
.If($"{prefixCls}-picker-rtl", () => RTL);
_inputClassMapper
.Add("ant-input")
.Add("ant-cascader-input")
.GetIf(() => $"ant-cascader-input-{_sizeMap[Size]}", () => _sizeMap.ContainsKey(Size))
.If("ant-cascader-input-rtl", () => RTL);
.GetIf(() => $"ant-input-{_sizeMap[Size]}", () => _sizeMap.ContainsKey(Size))
.Add($"{prefixCls}-input")
.If($"{prefixCls}-input-rtl", () => RTL);
_menuClassMapper
.Add("ant-cascader-menu")
.If($"ant-cascader-menu-rtl", () => RTL);
}
.Add($"{prefixCls}-menu")
.If($"{prefixCls}-menu-rtl", () => RTL);
protected override void OnParametersSet()
{
base.OnParametersSet();
ProcessParentAndDefault();
SetDefaultValue(Value ?? DefaultValue);
}
protected override void OnValueChange(string value)
{
base.OnValueChange(value);
RefreshNodeValue(value);
}
#region event
/// <summary>
/// 输入框单击(显示/隐藏浮层)
/// </summary>
private void InputOnToggle()
{
SelectedType = SelectedTypeEnum.Click;
_selectedType = SelectedTypeEnum.Click;
_hoverSelectedNodes.Clear();
ToggleState = !ToggleState;
if (!_dropdownOpened)
{
_dropdownOpened = true;
}
}
/// <summary>
@ -177,9 +140,9 @@ namespace AntDesign
/// </summary>
private void CascaderOnBlur()
{
if (!IsOnCascader)
if (!_isOnCascader)
{
ToggleState = false;
_dropdownOpened = false;
_renderNodes = _selectedNodes;
}
}
@ -191,7 +154,7 @@ namespace AntDesign
{
if (!AllowClear) return;
ShowClearIcon = true;
_showClearIcon = !string.IsNullOrWhiteSpace(Value) || !string.IsNullOrEmpty(_searchValue);
}
/// <summary>
@ -201,7 +164,7 @@ namespace AntDesign
{
if (!AllowClear) return;
ShowClearIcon = false;
_showClearIcon = false;
}
/// <summary>
@ -212,7 +175,8 @@ namespace AntDesign
_selectedNodes.Clear();
_hoverSelectedNodes.Clear();
_displayText = string.Empty;
CurrentValueAsString = string.Empty;
SetValue(string.Empty);
_dropdownOpened = false;
}
/// <summary>
@ -222,7 +186,7 @@ namespace AntDesign
{
if (!AllowClear) return;
IsOnCascader = true;
_isOnCascader = true;
}
/// <summary>
@ -232,7 +196,7 @@ namespace AntDesign
{
if (!AllowClear) return;
IsOnCascader = false;
_isOnCascader = false;
}
/// <summary>
@ -242,7 +206,7 @@ namespace AntDesign
private void NodeOnClick(CascaderNode node)
{
if (node.Disabled) return;
_searchValue = string.Empty;
SetSelectedNode(node, SelectedTypeEnum.Click);
}
@ -260,18 +224,39 @@ namespace AntDesign
SetSelectedNode(node, SelectedTypeEnum.Hover);
}
#endregion event
private void OnSearchInput(ChangeEventArgs e)
{
_searchValue = e.Value?.ToString();
}
private async Task OnSearchKeyUp(KeyboardEventArgs e)
{
if (string.IsNullOrEmpty(_searchValue))
{
_showClearIcon = false;
return;
}
var inputElemnet = await Js.InvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, _inputRef);
_menuStyle = $"width:{inputElemnet.ClientWidth}px;";
_matchList = _searchList.Where(x => x.Label.Contains(_searchValue, StringComparison.OrdinalIgnoreCase));
_showClearIcon = true;
if (!_matchList.Any())
{
_menuStyle += "height:auto;";
}
}
/// <summary>
/// 选中节点
/// Selected nodes
/// </summary>
/// <param name="cascaderNode"></param>
/// <param name="selectedType"></param>
internal void SetSelectedNode(CascaderNode cascaderNode, SelectedTypeEnum selectedType)
private void SetSelectedNode(CascaderNode cascaderNode, SelectedTypeEnum selectedType)
{
if (cascaderNode == null) return;
SelectedType = selectedType;
_selectedType = selectedType;
if (selectedType == SelectedTypeEnum.Click)
{
_selectedNodes.Clear();
@ -279,7 +264,9 @@ namespace AntDesign
_renderNodes = _selectedNodes;
if (ChangeOnSelect || !cascaderNode.HasChildren)
{
SetValue(cascaderNode.Value);
}
}
else
{
@ -291,13 +278,13 @@ namespace AntDesign
if (!cascaderNode.HasChildren)
{
ToggleState = false;
IsOnCascader = false;
_dropdownOpened = false;
_isOnCascader = false;
}
}
/// <summary>
/// 设置选中所有父节点
/// Set all parent nodes to be selected
/// </summary>
/// <param name="node"></param>
/// <param name="list"></param>
@ -310,7 +297,7 @@ namespace AntDesign
}
/// <summary>
/// Options 更新后处理父节点和默认值
/// handles parent nodes and defaults after Options updating
/// </summary>
private void ProcessParentAndDefault()
{
@ -319,12 +306,12 @@ namespace AntDesign
}
/// <summary>
/// 初始化节点属性(Level, ParentNode)
/// Initialize nodes (Level, ParentNode)
/// </summary>
/// <param name="list"></param>
/// <param name="parentNode"></param>
/// <param name="level"></param>
private void InitCascaderNodeState(List<CascaderNode> list, CascaderNode parentNode, int level)
private void InitCascaderNodeState(List<CascaderNode> list, CascaderNode parentNode, int level, bool recursive = false)
{
if (list == null) return;
@ -334,84 +321,100 @@ namespace AntDesign
node.ParentNode = parentNode;
if (node.HasChildren)
InitCascaderNodeState(node.Children.ToList(), node, level + 1);
{
InitCascaderNodeState(node.Children.ToList(), node, level + 1, true);
}
else
{
_searchList.Add(new CascaderNode
{
Label = node.Label,
ParentNode = node.ParentNode,
Value = node.Value,
Disabled = node.Disabled,
Level = node.Level
});
}
}
if (!recursive)
{
_searchList.ForEach(node =>
{
var pathList = new List<CascaderNode>();
SetSelectedNodeWithParent(node, ref pathList);
pathList.Reverse();
node.Label = string.Join(" / ", pathList.Select(x => x.Label));
});
}
}
/// <summary>
/// 刷新选中的内容
/// Refresh the selected value
/// </summary>
/// <param name="value"></param>
private void RefreshNodeValue(string value)
{
_selectedNodes.Clear();
var node = GetNodeByValue(_nodelist, value);
SetSelectedNodeWithParent(node, ref _selectedNodes);
_renderNodes = _selectedNodes;
RefreshDisplayValue();
OnChange?.Invoke(_selectedNodes, value, _displayText);
}
/// <summary>
/// 设置默认选中
/// Set the default value
/// </summary>
/// <param name="defaultValue"></param>
private void SetDefaultValue(string defaultValue)
{
if (string.IsNullOrWhiteSpace(defaultValue))
return;
if (!string.IsNullOrWhiteSpace(defaultValue))
{
RefreshNodeValue(defaultValue);
SetValue(defaultValue);
}
_selectedNodes.Clear();
var node = GetNodeByValue(_nodelist, defaultValue);
SetSelectedNodeWithParent(node, ref _selectedNodes);
_renderNodes = _selectedNodes;
SetValue(node?.Value);
_initialized = true;
}
/// <summary>
/// 设置输入框选中值
/// Set the binding value
/// </summary>
/// <param name="value"></param>
private void SetValue(string value)
{
RefreshDisplayValue();
RefreshDisplayText();
if (Value != value)
{
CurrentValueAsString = value;
}
}
private void RefreshDisplayValue()
{
_selectedNodes.Sort((x, y) => x.Level.CompareTo(y.Level)); //Level 升序排序
_displayText = string.Empty;
int count = 0;
foreach (var node in _selectedNodes)
{
if (node == null) continue;
if (_initialized && SelectedNodesChanged.HasDelegate)
{
SelectedNodesChanged.InvokeAsync(_selectedNodes.ToArray());
}
if (count < _selectedNodes.Count - 1)
_displayText += node.Label + " / ";
else
_displayText += node.Label;
count++;
OnChange?.Invoke(_selectedNodes, value, _displayText);
}
}
/// <summary>
/// 根据指定值获取节点
/// rebuild the display text
/// </summary>
private void RefreshDisplayText()
{
_selectedNodes.Sort((x, y) => x.Level.CompareTo(y.Level)); //Level 升序排序
_displayText = string.Join(" / ", _selectedNodes.Where(x => x != null).Select(x => x.Label));
}
/// <summary>
/// Get the node based on the specified value
/// </summary>
/// <param name="list"></param>
/// <param name="value"></param>
/// <returns></returns>
private CascaderNode GetNodeByValue(List<CascaderNode> list, string value)
private CascaderNode GetNodeByValue(IEnumerable<CascaderNode> list, string value)
{
if (list == null) return null;
CascaderNode result = null;
foreach (var node in list)
{
@ -420,12 +423,14 @@ namespace AntDesign
if (node.HasChildren)
{
var nd = GetNodeByValue(node.Children.ToList(), value);
var nd = GetNodeByValue(node.Children, value);
if (nd != null)
result = nd;
{
return nd;
}
}
}
return result;
return null;
}
}
}

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
namespace AntDesign
{
@ -14,9 +15,9 @@ namespace AntDesign
internal CascaderNode ParentNode { get; set; }
internal bool HasChildren { get { return Children?.Count > 0; } }
internal bool HasChildren { get { return Children?.Any() == true; } }
public IReadOnlyCollection<CascaderNode> Children { get; set; }
public IEnumerable<CascaderNode> Children { get; set; }
}
internal enum SelectedTypeEnum

View File

@ -1,13 +1,13 @@
@namespace AntDesign
@inherits AntInputComponentBase<bool>
@inherits AntInputBoolComponentBase
<label class="@ClassMapper.Class" style="@Style" id="@Id" @ref="Ref">
<span class="@ClassMapperSpan.Class">
<label class="@ClassMapperLabel.Class" style="@Style" id="@Id" @ref="Ref">
<span class="@ClassMapper.Class">
<input value="@Label"
checked="@Checked"
disabled="@Disabled"
@onchange="@InputCheckedChange"
@attributes="@InputAttributes"
autofocus=@AutoFocus
@onchange="@InputCheckedChange"
type="checkbox"
class="ant-checkbox-input" />
<span class="ant-checkbox-inner"></span>

View File

@ -1,58 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading.Tasks;
using AntDesign.core.JsInterop.EventArg;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
namespace AntDesign
{
public partial class Checkbox : AntInputComponentBase<bool>
public partial class Checkbox : AntInputBoolComponentBase
{
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public EventCallback<bool> CheckedChange { get; set; }
//[Obsolete] attribute does not work with [Parameter] for now. Tracking issue: https://github.com/dotnet/aspnetcore/issues/30967
[Obsolete("Instead use @bing-Checked or EventCallback<bool> CheckedChanged .")]
[Parameter]
public EventCallback<bool> CheckedChange { get; set; }
[Parameter] public Expression<Func<bool>> CheckedExpression { get; set; }
[Parameter] public bool AutoFocus { get; set; }
[Parameter] public bool Disabled { get; set; }
[Parameter] public bool Indeterminate { get; set; }
[Parameter] public string Label { get; set; }
[Parameter]
public bool Checked
{
get => CurrentValue;
set
{
if (CurrentValue != value)
{
CurrentValue = value;
}
}
}
[Parameter]
public string Label { get; set; }
[CascadingParameter]
public CheckboxGroup CheckboxGroup { get; set; }
protected Dictionary<string, object> InputAttributes { get; set; } = new Dictionary<string, object>();
protected override void OnParametersSet()
{
SetClass();
base.OnParametersSet();
}
[CascadingParameter] public CheckboxGroup CheckboxGroup { get; set; }
internal bool IsFromOptions { get; set; }
private bool _isInitalized;
protected override void OnInitialized()
{
UpdateAutoFocus();
base.OnInitialized();
SetClass();
CheckboxGroup?.AddItem(this);
_isInitalized = true;
}
protected override void Dispose(bool disposing)
@ -61,63 +37,37 @@ namespace AntDesign
base.Dispose(disposing);
}
protected ClassMapper ClassMapperSpan { get; } = new ClassMapper();
protected ClassMapper ClassMapperLabel { get; } = new ClassMapper();
private string _prefixCls = "ant-checkbox";
protected void SetClass()
{
string prefixName = "ant-checkbox";
ClassMapperLabel.Clear()
.Add(_prefixCls)
.Add($"{_prefixCls}-wrapper")
.If($"{_prefixCls}-wrapper-checked", () => Checked);
ClassMapper.Clear()
.Add(prefixName)
.Add($"{prefixName}-wrapper")
.If($"{prefixName}-wrapper-checked", () => Checked);
ClassMapperSpan.Clear()
.Add(prefixName)
.If($"{prefixName}-checked", () => Checked && !Indeterminate)
.If($"{prefixName}-disabled", () => Disabled)
.If($"{prefixName}-indeterminate", () => Indeterminate)
.If($"{prefixName}-rtl", () => RTL);
}
protected override void OnValueChange(bool value)
{
base.OnValueChange(value);
this.CurrentValue = value;
.Add(_prefixCls)
.If($"{_prefixCls}-checked", () => Checked && !Indeterminate)
.If($"{_prefixCls}-disabled", () => Disabled)
.If($"{_prefixCls}-indeterminate", () => Indeterminate)
.If($"{_prefixCls}-rtl", () => RTL);
}
protected async Task InputCheckedChange(ChangeEventArgs args)
{
if (args != null && args.Value is bool value)
{
CurrentValue = value;
await InnerCheckedChange(value);
}
}
await base.ChangeValue(value);
protected async Task InnerCheckedChange(bool @checked)
{
if (!this.Disabled)
{
if (this.CheckedChange.HasDelegate)
{
await this.CheckedChange.InvokeAsync(@checked);
}
if (CheckedChange.HasDelegate) //kept for compatibility reasons with previous versions
await CheckedChange.InvokeAsync(value);
CheckboxGroup?.OnCheckboxChange(this);
}
}
protected void UpdateAutoFocus()
{
if (this.AutoFocus)
{
if (InputAttributes.ContainsKey("autofocus") == false)
InputAttributes.Add("autofocus", "autofocus");
}
else
{
if (InputAttributes.ContainsKey("autofocus") == true)
InputAttributes.Remove("autofocus");
}
}
internal void SetValue(bool value) => Checked = value;
}
}

View File

@ -3,6 +3,8 @@
<CascadingValue Value="this" IsFixed="@true">
<div class="@ClassMapper.Class" style="@Style" id="@Id" @ref="Ref">
@if (MixedMode == CheckboxGroupMixedMode.ChildContentFirst)
@ChildContent
@if (Options.Value != null)
{
if (Options.IsT0)
@ -30,6 +32,7 @@
}
}
}
@ChildContent
@if (MixedMode == CheckboxGroupMixedMode.OptionsFirst)
@ChildContent
</div>
</CascadingValue>

View File

@ -12,14 +12,45 @@ namespace AntDesign
public RenderFragment ChildContent { get; set; }
[Parameter]
public OneOf<CheckboxOption[], string[]> Options { get; set; }
public OneOf<CheckboxOption[], string[]> Options
{
get { return _options; }
set
{
_options = value;
_isOptionDefined = true;
}
}
[Parameter]
public CheckboxGroupMixedMode MixedMode
{
get { return _mixedMode; }
set {
bool isChanged = _afterFirstRender && _mixedMode != value;
_mixedMode = value;
if (isChanged)
{
//were changed by RemoveItem
_indexConstructedOptionsOffset = -1; //force recalculation
_indexSetOptionsOffset = 0;
}
}
}
[Parameter]
public EventCallback<string[]> OnChange { get; set; }
private string[] _selectedValues;
private Func<string[]> _currentValue;
private IList<Checkbox> _checkboxItems;
private OneOf<CheckboxOption[], string[]> _options;
private OneOf<CheckboxOption[], string[]> _constructedOptions;
private bool _isOptionDefined;
private bool _afterFirstRender;
private int _indexConstructedOptionsOffset = -1;
private int _indexSetOptionsOffset = -1;
private CheckboxGroupMixedMode _mixedMode = CheckboxGroupMixedMode.ChildContentFirst;
[Parameter]
public bool Disabled { get; set; }
@ -35,15 +66,47 @@ namespace AntDesign
{
this._checkboxItems ??= new List<Checkbox>();
this._checkboxItems?.Add(checkbox);
checkbox.IsFromOptions = IsCheckboxFromOptions(checkbox);
if (!checkbox.IsFromOptions)
{
checkbox.SetValue(_selectedValues.Contains(checkbox.Label));
if (_indexConstructedOptionsOffset == -1)
_indexConstructedOptionsOffset = _checkboxItems.Count - 1;
}
else if (checkbox.IsFromOptions && _indexSetOptionsOffset == -1)
_indexSetOptionsOffset = _checkboxItems.Count - 1;
}
private bool IsCheckboxFromOptions(Checkbox checkbox)
{
if (Options.Value is not null)
{
if (ChildContent is not null)
{
return Options.Match(
opt => opt.Any(o => o.Label.Equals(checkbox.Label)),
arr => arr.Contains(checkbox.Label));
}
return true;
}
return false;
}
internal void RemoveItem(Checkbox checkbox)
{
this._checkboxItems?.Remove(checkbox);
if (!checkbox.IsFromOptions && _indexConstructedOptionsOffset >= 0)
_indexConstructedOptionsOffset--;
else if (checkbox.IsFromOptions && _indexSetOptionsOffset >= 0)
_indexSetOptionsOffset--;
}
protected override void OnInitialized()
{
if (ChildContent is null && MixedMode == CheckboxGroupMixedMode.ChildContentFirst)
MixedMode = CheckboxGroupMixedMode.OptionsFirst;
base.OnInitialized();
if (Value != null)
@ -58,32 +121,92 @@ namespace AntDesign
_selectedValues ??= Array.Empty<string>();
}
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
if (ChildContent is not null && _checkboxItems.Count > 0)
{
_constructedOptions = CreateConstructedOptions();
}
_currentValue = GetCurrentValueFunc();
_afterFirstRender = true;
}
base.OnAfterRender(firstRender);
}
private OneOf<CheckboxOption[], string[]> CreateConstructedOptions()
{
if (Options.IsT0)
{
return _checkboxItems
.Where(c => !c.IsFromOptions)
.Select(c => new CheckboxOption { Label = c.Label, Value = c.Label, Checked = c.Value })
.ToArray();
}
return _checkboxItems
.Where(c => !c.IsFromOptions)
.Select(c => c.Label).ToArray();
}
private Func<string[]> GetCurrentValueFunc()
{
if (ChildContent is not null && _isOptionDefined)
{
return Options.Match<Func<string[]>>(
opt => () => opt.Where(x => x.Checked).Select(x => x.Value)
.Union(_constructedOptions.AsT0.Where(x => x.Checked).Select(x => x.Value))
.ToArray(),
arr => () => _selectedValues);
}
var workWith = (_isOptionDefined ? Options : _constructedOptions);
return workWith.Match<Func<string[]>>(
opt => () => opt.Where(x => x.Checked).Select(x => x.Value).ToArray(),
arr => () => _selectedValues);
}
/// <summary>
/// Called when [checkbox change].
/// </summary>
/// <param name="checkbox">The checkbox.</param>
/// <returns></returns>
internal void OnCheckboxChange(Checkbox checkbox)
{
var index = _checkboxItems.IndexOf(checkbox);
int indexOffset;
OneOf<CheckboxOption[], string[]> workWith;
if (checkbox.IsFromOptions)
{
indexOffset = _indexSetOptionsOffset;
workWith = Options;
}
else
{
indexOffset = _indexConstructedOptionsOffset;
workWith = _constructedOptions;
}
Options.Switch(opts =>
workWith.Switch(opts =>
{
if (opts[index] != null)
{
opts[index].Checked = checkbox.Checked;
}
CurrentValue = Options.AsT0.Where(x => x.Checked).Select(x => x.Value).ToArray();
}, opts =>
{
if (checkbox.Checked && !opts[index].IsIn(_selectedValues))
if (checkbox.Checked && !opts[index - indexOffset].IsIn(_selectedValues))
{
_selectedValues = _selectedValues.Append(opts[index]);
_selectedValues = _selectedValues.Append(opts[index - indexOffset]);
}
else
{
_selectedValues = _selectedValues.Except(new[] { opts[index] }).ToArray();
_selectedValues = _selectedValues.Except(new[] { opts[index - indexOffset] }).ToArray();
}
CurrentValue = _selectedValues;
});
CurrentValue = _currentValue();
if (OnChange.HasDelegate)
{
OnChange.InvokeAsync(CurrentValue);

View File

@ -0,0 +1,8 @@
namespace AntDesign
{
public enum CheckboxGroupMixedMode
{
ChildContentFirst,
OptionsFirst
};
}

View File

@ -0,0 +1,54 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
namespace AntDesign
{
public abstract class AntInputBoolComponentBase : AntInputComponentBase<bool>
{
[Parameter] public bool AutoFocus { get; set; }
private bool _checked;
[Parameter]
public bool Checked
{
get { return _checked; }
set
{
_checked = value;
if (_checked != Value)
{
Value = _checked;
}
}
}
[Parameter]
public EventCallback<bool> OnChange { get; set; }
/// <summary>
/// Gets or sets a callback that updates the bound checked value.
/// </summary>
[Parameter]
public virtual EventCallback<bool> CheckedChanged { get; set; }
[Parameter]
public bool Disabled { get; set; }
internal virtual string PrefixCls { get; }
protected override void OnValueChange(bool value)
{
base.OnValueChange(value);
Checked = value;
CheckedChanged.InvokeAsync(value);
}
protected virtual async Task ChangeValue(bool value)
{
CurrentValue = value;
if (this.OnChange.HasDelegate)
await this.OnChange.InvokeAsync(value);
}
}
}

View File

@ -87,9 +87,15 @@ namespace AntDesign
[Parameter]
public Expression<Func<TValue>> ValueExpression { get; set; }
[Parameter]
public Expression<Func<IEnumerable<TValue>>> ValuesExpression { get; set; }
[Parameter]
public string Size { get; set; } = AntSizeLDSType.Default;
[Parameter]
public virtual CultureInfo CultureInfo { get; set; } = CultureInfo.CurrentCulture;
/// <summary>
/// Gets the associated <see cref="EditContext"/>.
/// </summary>
@ -176,7 +182,9 @@ namespace AntDesign
}
private TValue _firstValue;
private bool _isNotifyFieldChanged = true;
protected bool _isNotifyFieldChanged = true;
private bool _isValueGuid;
/// <summary>
/// Constructs an instance of <see cref="InputBase{TValue}"/>.
@ -211,8 +219,20 @@ namespace AntDesign
return true;
}
var success = BindConverter.TryConvertTo<TValue>(
value, CultureInfo.CurrentCulture, out var parsedValue);
TValue parsedValue = default;
bool success;
// BindConverter.TryConvertTo<Guid> doesn't work for a incomplete Guid fragment. Remove this when the BCL bug is fixed.
if (_isValueGuid)
{
success = Guid.TryParse(value, out Guid parsedGuidValue);
if (success)
parsedValue = THelper.ChangeType<TValue>(parsedGuidValue);
}
else
{
success = BindConverter.TryConvertTo(value, CultureInfo, out parsedValue);
}
if (success)
{
@ -230,12 +250,17 @@ namespace AntDesign
}
}
/// <summary>
/// When this method is called, Value is only has been modified, but the ValueChanged is not triggered, so the outside bound Value is not changed.
/// </summary>
/// <param name="value"></param>
protected virtual void OnValueChange(TValue value)
{
}
protected override void OnInitialized()
{
_isValueGuid = THelper.GetUnderlyingType<TValue>() == typeof(Guid);
base.OnInitialized();
FormItem?.AddControl(this);
@ -259,13 +284,16 @@ namespace AntDesign
return base.SetParametersAsync(ParameterView.Empty);
}
if (ValueExpression == null)
if (ValueExpression == null && ValuesExpression == null)
{
return base.SetParametersAsync(ParameterView.Empty);
}
EditContext = Form?.EditContext;
FieldIdentifier = FieldIdentifier.Create(ValueExpression);
if (ValuesExpression == null)
FieldIdentifier = FieldIdentifier.Create(ValueExpression);
else
FieldIdentifier = FieldIdentifier.Create(ValuesExpression);
_nullableUnderlyingType = Nullable.GetUnderlyingType(typeof(TValue));
EditContext.OnValidationStateChanged += _validationStateChangedHandler;

View File

@ -0,0 +1,42 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.Web;
namespace AntDesign.Internal
{
public class Element : AntDomComponentBase
{
[Parameter] public string HtmlTag { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public EventCallback<ElementReference> RefChanged { get; set; }
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object> Attributes { get; set; } = new Dictionary<string, object>();
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
base.BuildRenderTree(builder);
builder.OpenElement(0, HtmlTag);
builder.AddMultipleAttributes(1, Attributes);
builder.AddAttribute(2, "class", Class);
builder.AddAttribute(3, "style", Style);
if (HtmlTag == "button")
builder.AddEventStopPropagationAttribute(5, "onclick", true);
builder.AddElementReferenceCapture(6, async capturedRef =>
{
Ref = capturedRef;
if (RefChanged.HasDelegate)
await RefChanged.InvokeAsync(Ref);
});
builder.AddContent(10, ChildContent);
builder.CloseElement();
}
}
}

View File

@ -85,6 +85,12 @@ namespace AntDesign.Internal
private int _overlayClientWidth = 0;
protected override void OnInitialized()
{
_overlayCls = Trigger.GetOverlayHiddenClass();
base.OnInitialized();
}
protected override async Task OnParametersSetAsync()
{
if (!_isOverlayShow && Trigger.Visible && !_preVisible)
@ -126,7 +132,6 @@ namespace AntDesign.Internal
if (_isWaitForOverlayFirstRender && _isOverlayFirstRender)
{
_isOverlayFirstRender = false;
await Show(_overlayLeft, _overlayTop);
_isWaitForOverlayFirstRender = false;
@ -156,23 +161,23 @@ namespace AntDesign.Internal
{
return;
}
Element trigger;
if (Trigger.ChildContent != null)
await Task.Yield();
HtmlElement trigger;
if (Trigger.ChildContent is not null)
{
trigger = await JsInvokeAsync<Element>(JSInteropConstants.GetFirstChildDomInfo, Trigger.Ref);
trigger = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetFirstChildDomInfo, Trigger.Ref);
// fix bug in submenu: Overlay show when OvelayTrigger is not rendered complete.
// I try to render Overlay when OvelayTriggers OnAfterRender is called, but is still get negative absoluteLeft
// This may be a bad solution, but for now I can only do it this way.
while (trigger.absoluteLeft <= 0 && trigger.clientWidth <= 0)
while (trigger.AbsoluteLeft <= 0 && trigger.ClientWidth <= 0)
{
await Task.Delay(50);
trigger = await JsInvokeAsync<Element>(JSInteropConstants.GetFirstChildDomInfo, Trigger.Ref);
trigger = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetFirstChildDomInfo, Trigger.Ref);
}
}
else //(Trigger.Unbound != null)
{
trigger = await JsInvokeAsync<Element>(JSInteropConstants.GetDomInfo, Trigger.Ref);
trigger = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, Trigger.Ref);
}
_overlayLeft = overlayLeft;
@ -193,8 +198,8 @@ namespace AntDesign.Internal
await AddOverlayToBody();
Element overlayElement = await JsInvokeAsync<Element>(JSInteropConstants.GetDomInfo, Ref);
Element containerElement = await JsInvokeAsync<Element>(JSInteropConstants.GetDomInfo, Trigger.PopupContainerSelector);
HtmlElement overlayElement = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, Ref);
HtmlElement containerElement = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, Trigger.PopupContainerSelector);
int left = await GetOverlayLeftWithBoundaryAdjust(trigger, overlayElement, containerElement);
int top = await GetOverlayTopWithBoundaryAdjust(trigger, overlayElement, containerElement);
@ -281,9 +286,10 @@ namespace AntDesign.Internal
}
/// <summary>
/// Indicates that a page is being refreshed
/// Indicates that a page is being refreshed
/// </summary>
private bool _isReloading;
private void Reloading(JsonElement jsonElement) => _isReloading = true;
private async Task AddOverlayToBody()
@ -295,34 +301,35 @@ namespace AntDesign.Internal
_hasAddOverlayToBody = true;
}
}
private async Task<int> GetOverlayTopWithBoundaryAdjust(Element trigger, Element overlay, Element containerElement)
private async Task<int> GetOverlayTopWithBoundaryAdjust(HtmlElement trigger, HtmlElement overlay, HtmlElement containerElement)
{
int top = GetOverlayTop(trigger, overlay, containerElement);
/*
/*
*
* boundary detection and orientation adjustment
*/
return await BoundaryAdjust(trigger, overlay, containerElement, top, "top");
}
private async Task<int> GetOverlayLeftWithBoundaryAdjust(Element trigger, Element overlay, Element containerElement)
private async Task<int> GetOverlayLeftWithBoundaryAdjust(HtmlElement trigger, HtmlElement overlay, HtmlElement containerElement)
{
int left = GetOverlayLeft(trigger, overlay, containerElement);
/*
/*
*
* boundary detection and orientation adjustment
*/
return await BoundaryAdjust(trigger, overlay, containerElement, left, "left");
}
private int GetOverlayTop(Element trigger, Element overlay, Element containerElement)
private int GetOverlayTop(HtmlElement trigger, HtmlElement overlay, HtmlElement containerElement)
{
int top = 0;
int triggerTop = trigger.absoluteTop - containerElement.absoluteTop;
int triggerHeight = trigger.clientHeight != 0 ? trigger.clientHeight : trigger.offsetHeight;
int triggerTop = (int)(containerElement.ScrollTop + trigger.AbsoluteTop - containerElement.AbsoluteTop);
int triggerHeight = trigger.ClientHeight != 0 ? trigger.ClientHeight : trigger.OffsetHeight;
// contextMenu
if (_overlayTop != null)
@ -333,7 +340,7 @@ namespace AntDesign.Internal
if (Trigger.Placement.IsIn(PlacementType.Left, PlacementType.Right))
{
top = triggerTop + triggerHeight / 2 - overlay.clientHeight / 2;
top = triggerTop + triggerHeight / 2 - overlay.ClientHeight / 2;
}
else if (Trigger.Placement.IsIn(PlacementType.LeftTop, PlacementType.RightTop))
{
@ -346,7 +353,7 @@ namespace AntDesign.Internal
}
else if (Trigger.Placement.IsIn(PlacementType.LeftBottom, PlacementType.RightBottom))
{
top = triggerTop - overlay.clientHeight + triggerHeight;
top = triggerTop - overlay.ClientHeight + triggerHeight;
if (ArrowPointAtCenter)
{
@ -359,22 +366,22 @@ namespace AntDesign.Internal
}
else if (Trigger.Placement.IsIn(PlacementType.TopLeft, PlacementType.TopCenter, PlacementType.Top, PlacementType.TopRight))
{
top = triggerTop - overlay.clientHeight - VerticalOffset;
top = triggerTop - overlay.ClientHeight - VerticalOffset;
}
return top;
}
private int GetOverlayLeft(Element trigger, Element overlay, Element containerElement)
private int GetOverlayLeft(HtmlElement trigger, HtmlElement overlay, HtmlElement containerElement)
{
int left = 0;
int triggerLeft = trigger.absoluteLeft - containerElement.absoluteLeft;
int triggerWidth = trigger.clientWidth != 0 ? trigger.clientWidth : trigger.offsetWidth;
int triggerLeft = trigger.AbsoluteLeft - containerElement.AbsoluteLeft;
int triggerWidth = trigger.ClientWidth != 0 ? trigger.ClientWidth : trigger.OffsetWidth;
if (overlay.clientWidth > 0)
if (overlay.ClientWidth > 0)
{
_overlayClientWidth = overlay.clientWidth;
_overlayClientWidth = overlay.ClientWidth;
}
// contextMenu
@ -418,12 +425,13 @@ namespace AntDesign.Internal
return left;
}
/*
/*
*
* boundary detection and orientation adjustment
*/
private async Task<int> BoundaryAdjust(
Element trigger, Element overlay, Element containerElement,
HtmlElement trigger, HtmlElement overlay, HtmlElement containerElement,
int curPos, string direction)
{
if (Trigger.BoundaryAdjustMode == TriggerBoundaryAdjustMode.None)
@ -431,9 +439,12 @@ namespace AntDesign.Internal
return curPos;
}
int overlaySize = direction == "top" ? overlay.clientHeight : overlay.clientWidth;
int overlaySize = direction == "top" ? overlay.ClientHeight : overlay.ClientWidth;
int boundarySize = await GetWindowBoundarySize(direction, containerElement);
// 距离边界的长度或宽度/distance from top or left boundry
int distanceFromBoundry = await GetOverlayDistanceFromBoundary(direction, curPos);
if (Trigger.Trigger.Contains(TriggerType.ContextMenu))
{
if (overlaySize + curPos > boundarySize)
@ -450,7 +461,8 @@ namespace AntDesign.Internal
int overlayPosWithSize = GetOverlayPosWithSize(curPos, overlaySize);
if (overlayPosWithSize > boundarySize || overlayPosWithSize < 0)
if ((overlayPosWithSize > boundarySize || overlayPosWithSize < 0)
&& distanceFromBoundry >= overlaySize) // check if still outof boundary after reverse placement
{
// 翻转位置/reverse placement
Trigger.ChangePlacementForShow(Trigger.Placement.GetReverseType());
@ -464,20 +476,7 @@ namespace AntDesign.Internal
curPos = reversePos;
}
/*
TODO /still outof boundary range after reverse placement
*/
//overlayPosWithSize = GetOverlayPosWithSize(curPos, overlaySize);
//if (overlayPosWithSize > boundarySize)
//{
// curPos = GetOverlayBoundaryPos(overlaySize, boundarySize);
//}
//else if (overlayPosWithSize < 0)
//{
// curPos -= overlayPosWithSize;
//}
return curPos;
return curPos < 0 ? 0 : curPos;
}
private int GetOverlayPosWithSize(int overlayPos, int overlaySize)
@ -508,14 +507,32 @@ namespace AntDesign.Internal
}
}
private async Task<int> GetWindowBoundarySize(string direction, Element containerElement)
private async Task<int> GetOverlayDistanceFromBoundary(string direction, int overlayPos)
{
if (Trigger.BoundaryAdjustMode == TriggerBoundaryAdjustMode.InScroll)
{
return overlayPos;
}
JsonElement scrollInfo = await JsInvokeAsync<JsonElement>(JSInteropConstants.GetScroll);
int windowScrollX = (int)scrollInfo.GetProperty("x").GetDouble();
int windowScrollY = (int)scrollInfo.GetProperty("y").GetDouble();
return direction switch
{
"top" => overlayPos - windowScrollY,
_ => overlayPos - windowScrollX
};
}
private async Task<int> GetWindowBoundarySize(string direction, HtmlElement containerElement)
{
if (Trigger.BoundaryAdjustMode == TriggerBoundaryAdjustMode.InScroll)
{
return direction switch
{
"top" => containerElement.scrollHeight,
_ => containerElement.scrollWidth
"top" => containerElement.ScrollHeight,
_ => containerElement.ScrollWidth
};
}
@ -571,6 +588,7 @@ namespace AntDesign.Internal
if (!_isOverlayShow && !_isWaitForOverlayFirstRender)
{
overlayCls = Trigger.GetOverlayHiddenClass();
_overlayCls = Trigger.GetOverlayEnterClass();
}
else
{
@ -582,33 +600,36 @@ namespace AntDesign.Internal
private string GetDisplayStyle()
{
string display = _isOverlayShow ? "display: inline-flex;" : "visibility: hidden;";
if (!_isOverlayShow && !_isWaitForOverlayFirstRender)
{
display = "";
}
return "";
return display;
if (_isOverlayShow && _hasAddOverlayToBody)
return "display: inline-flex;";
if (_hasAddOverlayToBody)
return "visibility: hidden;";
return "display: inline-flex; visibility: hidden;";
}
internal async Task UpdatePosition(int? overlayLeft = null, int? overlayTop = null)
{
Element trigger;
HtmlElement trigger;
if (Trigger.ChildContent != null)
{
trigger = await JsInvokeAsync<Element>(JSInteropConstants.GetFirstChildDomInfo, Trigger.Ref);
trigger = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetFirstChildDomInfo, Trigger.Ref);
}
else //(Trigger.Unbound != null)
{
trigger = await JsInvokeAsync<Element>(JSInteropConstants.GetDomInfo, Trigger.Ref);
trigger = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, Trigger.Ref);
}
_overlayLeft = overlayLeft;
_overlayTop = overlayTop;
Element overlayElement = await JsInvokeAsync<Element>(JSInteropConstants.GetDomInfo, Ref);
Element containerElement = await JsInvokeAsync<Element>(JSInteropConstants.GetDomInfo, Trigger.PopupContainerSelector);
HtmlElement overlayElement = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, Ref);
HtmlElement containerElement = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, Trigger.PopupContainerSelector);
int left = await GetOverlayLeftWithBoundaryAdjust(trigger, overlayElement, containerElement);
int top = await GetOverlayTopWithBoundaryAdjust(trigger, overlayElement, containerElement);

View File

@ -134,12 +134,8 @@ namespace AntDesign.Internal
[Parameter]
public ElementReference TriggerReference
{
get => _triggerReference;
set
{
_triggerReference = value;
RefBack.Set(value);
}
get => Ref;
set => Ref = value;
}
[Inject]
@ -149,7 +145,6 @@ namespace AntDesign.Internal
private bool _mouseInOverlay = false;
protected Overlay _overlay = null;
private ElementReference _triggerReference;
protected override void OnAfterRender(bool firstRender)
{
@ -190,7 +185,6 @@ namespace AntDesign.Internal
{
var eventArgs = JsonSerializer.Deserialize<MouseEventArgs>(jsonElement.ToString(),
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
await OnClickDiv(eventArgs);
}
@ -459,9 +453,9 @@ namespace AntDesign.Internal
return _overlay;
}
internal async Task<Element> GetTriggerDomInfo()
internal async Task<HtmlElement> GetTriggerDomInfo()
{
return await JsInvokeAsync<Element>(JSInteropConstants.GetFirstChildDomInfo, Ref);
return await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetFirstChildDomInfo, Ref);
}
public async Task Close()

View File

@ -26,5 +26,10 @@ namespace AntDesign.Core.Extensions
}
#endif
}
public static ValueTask SetSelectionStartAsync(this IJSRuntime jSRuntime, ElementReference target, int selectionStart) =>
jSRuntime.InvokeVoidAsync(JSInteropConstants.SetSelectionStart, target, selectionStart);
public static ValueTask<bool> IsResizeObserverSupported(this IJSRuntime jSRuntime) => jSRuntime.InvokeAsync<bool>(JSInteropConstants.IsResizeObserverSupported);
}
}

View File

@ -9,38 +9,50 @@ namespace AntDesign
private const int DECADE_YEAR_COUNT = 10;
private const int QUARTER_MONTH_COUNT = 3;
public static bool IsSameDate(DateTime date, DateTime compareDate)
public static bool IsSameDate(DateTime? date, DateTime? compareDate)
{
if (date is null || compareDate is null)
return false;
return date == compareDate;
}
public static bool IsSameYear(DateTime date, DateTime compareDate)
public static bool IsSameYear(DateTime? date, DateTime? compareDate)
{
return date.Year == compareDate.Year;
if (date is null || compareDate is null)
return false;
return date.Value.Year == compareDate.Value.Year;
}
public static bool IsSameMonth(DateTime date, DateTime compareDate)
public static bool IsSameMonth(DateTime? date, DateTime? compareDate)
{
if (date is null || compareDate is null)
return false;
return IsSameYear(date, compareDate)
&& date.Month == compareDate.Month;
&& date.Value.Month == compareDate.Value.Month;
}
public static bool IsSameDay(DateTime date, DateTime compareDate)
public static bool IsSameDay(DateTime? date, DateTime? compareDate)
{
if (date is null || compareDate is null)
return false;
return IsSameYear(date, compareDate)
&& _calendar.GetDayOfYear(date) == _calendar.GetDayOfYear(compareDate);
&& _calendar.GetDayOfYear(date.Value) == _calendar.GetDayOfYear(compareDate.Value);
}
public static bool IsSameWeak(DateTime date, DateTime compareDate)
public static bool IsSameWeak(DateTime? date, DateTime? compareDate, DayOfWeek firstDayOfWeek)
{
if (date is null || compareDate is null)
return false;
return IsSameYear(date, compareDate)
&& GetWeekOfYear(date) == GetWeekOfYear(compareDate);
&& GetWeekOfYear(date.Value, firstDayOfWeek) == GetWeekOfYear(compareDate.Value, firstDayOfWeek);
}
public static bool IsSameQuarter(DateTime date, DateTime compareDate)
public static bool IsSameQuarter(DateTime? date, DateTime? compareDate)
{
if (date is null || compareDate is null)
return false;
return IsSameYear(date, compareDate)
&& GetDayOfQuarter(date) == GetDayOfQuarter(compareDate);
&& GetDayOfQuarter(date.Value) == GetDayOfQuarter(compareDate.Value);
}
public static string GetDayOfQuarter(DateTime date)
@ -51,9 +63,7 @@ namespace AntDesign
public static int GetQuarter(DateTime date)
{
int offset = date.Month % QUARTER_MONTH_COUNT > 0 ? 1 : 0;
int quarter = date.Month / QUARTER_MONTH_COUNT + offset;
return quarter;
return date.Month / QUARTER_MONTH_COUNT + offset;
}
public static DateTime GetStartDateOfQuarter(DateTime date)
@ -62,9 +72,9 @@ namespace AntDesign
return new DateTime(date.Year, 1 + ((quarter - 1) * QUARTER_MONTH_COUNT), 1);
}
public static int GetWeekOfYear(DateTime date)
public static int GetWeekOfYear(DateTime date, DayOfWeek firstDayOfWeek)
{
return _calendar.GetWeekOfYear(date, CalendarWeekRule.FirstDay, DayOfWeek.Monday);
return _calendar.GetWeekOfYear(date, CalendarWeekRule.FirstDay, firstDayOfWeek);
}
/// <summary>

View File

@ -0,0 +1,805 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace AntDesign.Core.Helpers
{
public static class PropertyAccessHelper
{
public const string DefaultPathSeparator = ".";
private const string Nullable_HasValue = "HasValue";
private const string Nullable_Value = "Value";
private const string CountPropertyName = "Count";
private const string GetItemMethodName = "get_Item";
#region Build not Nullable delegate
public static LambdaExpression BuildAccessPropertyLambdaExpression([NotNull] this Type type, [NotNull] string propertyPath)
{
return BuildAccessPropertyLambdaExpression(type, propertyPath.Split(DefaultPathSeparator));
}
public static LambdaExpression BuildAccessPropertyLambdaExpression([NotNull] this Type type, [NotNull] string[] properties)
{
ArgumentNotNull(type, nameof(type));
ArgumentNotEmpty(properties, nameof(properties));
var parameterExpression = Expression.Parameter(type);
var expression = AccessProperty(parameterExpression, properties);
return Expression.Lambda(expression, parameterExpression);
}
public static Expression<Func<TParam, TReturn>> BuildAccessPropertyLambdaExpression<TParam, TReturn>([NotNull] this Type type, [NotNull] string propertyPath)
{
return BuildAccessPropertyLambdaExpression<TParam, TReturn>(type, propertyPath.Split(DefaultPathSeparator));
}
public static Expression<Func<TParam, TReturn>> BuildAccessPropertyLambdaExpression<TParam, TReturn>([NotNull] this Type type, [NotNull] string[] properties)
{
ArgumentNotNull(type, nameof(type));
ArgumentNotEmpty(properties, nameof(properties));
var parameterExpression = Expression.Parameter(type);
var expression = AccessProperty(parameterExpression, properties);
return Expression.Lambda<Func<TParam, TReturn>>(expression, parameterExpression);
}
public static Expression<Func<TParam, TReturn>> BuildAccessPropertyLambdaExpression<TParam, TReturn>(
[NotNull] this ParameterExpression parameterExpression,
[NotNull] string[] properties)
{
ArgumentNotNull(parameterExpression, nameof(parameterExpression));
var expression = AccessProperty(parameterExpression, properties);
return Expression.Lambda<Func<TParam, TReturn>>(expression, parameterExpression);
}
#endregion Build not Nullable delegate
#region Build Nullable delegate
public static LambdaExpression BuildAccessNullablePropertyLambdaExpression([NotNull] this Type type, [NotNull] string propertyPath)
{
return BuildAccessNullablePropertyLambdaExpression(type, propertyPath.Split(DefaultPathSeparator));
}
public static LambdaExpression BuildAccessNullablePropertyLambdaExpression([NotNull] this Type type, [NotNull] string[] properties)
{
ArgumentNotNull(type, nameof(type));
ArgumentNotEmpty(properties, nameof(properties));
var parameterExpression = Expression.Parameter(type);
var expression = AccessNullableProperty(parameterExpression, properties);
return Expression.Lambda(expression, parameterExpression);
}
public static Expression<Func<TParam, TReturn>> BuildAccessNullablePropertyLambdaExpression<TParam, TReturn>([NotNull] this Type type, [NotNull] string propertyPath)
{
return BuildAccessNullablePropertyLambdaExpression<TParam, TReturn>(type, propertyPath.Split(DefaultPathSeparator));
}
public static Expression<Func<TParam, TReturn>> BuildAccessNullablePropertyLambdaExpression<TParam, TReturn>([NotNull] this Type type, [NotNull] string[] properties)
{
ArgumentNotNull(type, nameof(type));
ArgumentNotEmpty(properties, nameof(properties));
var parameterExpression = Expression.Parameter(type);
var expression = AccessNullableProperty(parameterExpression, properties);
return Expression.Lambda<Func<TParam, TReturn>>(expression, parameterExpression);
}
public static Expression<Func<TParam, TReturn>> BuildAccessNullablePropertyLambdaExpression<TParam, TReturn>(
[NotNull] this ParameterExpression parameterExpression,
[NotNull] string[] properties)
{
ArgumentNotNull(parameterExpression, nameof(parameterExpression));
var expression = AccessNullableProperty(parameterExpression, properties);
return Expression.Lambda<Func<TParam, TReturn>>(expression, parameterExpression);
}
#endregion Build Nullable delegate
#region Extension method
public static Delegate ToDelegate([NotNull] this Expression expression)
{
ArgumentNotNull(expression, nameof(expression));
return ToLambdaExpression(expression).Compile();
}
public static LambdaExpression ToLambdaExpression([NotNull] this Expression expression)
{
ArgumentNotNull(expression, nameof(expression));
return Expression.Lambda(expression, GetRootParameterExpression(expression));
}
public static Expression<Func<TItem, TProp>> ToFuncExpression<TItem, TProp>([NotNull] this Expression expression)
{
ArgumentNotNull(expression, nameof(expression));
return Expression.Lambda<Func<TItem, TProp>>(expression, GetRootParameterExpression(expression));
}
#endregion Extension method
#region DefaultValue handle
public static Expression AccessPropertyDefaultIfNull<TValue>([NotNull] this Type type, [NotNull] string properties, [NotNull] string separator, TValue defaultValue)
{
return AccessPropertyDefaultIfNull(type, properties.Split(separator), defaultValue);
}
public static Expression AccessPropertyDefaultIfNull<TValue>([NotNull] this Type type, [NotNull] string properties, TValue defaultValue)
{
return AccessPropertyDefaultIfNull(type, properties.Split(DefaultPathSeparator), defaultValue);
}
public static Expression AccessPropertyDefaultIfNull<TValue>([NotNull] this Type type, [NotNull] string[] properties, TValue defaultValue)
{
ArgumentNotNull(type, nameof(type));
ArgumentNotEmpty(properties, nameof(properties));
var valueType = typeof(TValue);
var propertyExp = AccessNullableProperty(type, properties); // will get Nullable<T> or class
if (propertyExp.Type.IsValueType)
{
// Nullable Value Type
var defaultValueUnderlyingType = Nullable.GetUnderlyingType(valueType);
var defaultValueTypeIsNullable = defaultValueUnderlyingType != null;
if (defaultValueTypeIsNullable)
{
var propertyUnderlyingType = Nullable.GetUnderlyingType(propertyExp.Type);
if (propertyUnderlyingType != defaultValueUnderlyingType)
{
throw new InvalidOperationException($"default value type doesn't match the property type: property type '{propertyUnderlyingType?.Name}', default value type '{defaultValueUnderlyingType?.Name}'");
}
}
if (propertyExp is UnaryExpression ue)
{
var test = Expression.IsTrue(Expression.Property(ue, Nullable_HasValue));
Expression trueResult = defaultValueTypeIsNullable ? ue : Expression.Property(ue, Nullable_Value);
var falseResult = Expression.Constant(defaultValue, valueType);
return Expression.Condition(test, trueResult, falseResult);
}
else if (propertyExp is ConditionalExpression ce)
{
var test = Expression.IsTrue(Expression.Property(ce, Nullable_HasValue));
var trueResult = defaultValueTypeIsNullable ? ce.IfTrue : Expression.Property(ce.IfTrue, Nullable_Value);
var falseResult = defaultValueTypeIsNullable ? ce.IfFalse : Expression.Constant(defaultValue, valueType);
return Expression.Condition(test, trueResult, falseResult);
}
else if (propertyExp is MemberExpression me)
{
var test = Expression.IsTrue(Expression.Property(me, Nullable_HasValue));
Expression trueResult = defaultValueTypeIsNullable ? me : Expression.Property(me, Nullable_Value);
var falseResult = Expression.Constant(defaultValue, valueType);
return Expression.Condition(test, trueResult, falseResult);
}
else
{
throw new NotImplementedException($"Unexpected expression type {propertyExp.GetType().Name}");
}
}
else
{
// Class
var defaultValueType = typeof(TValue);
var propertyType = propertyExp.Type;
if (defaultValueType != propertyType)
{
throw new InvalidOperationException($"default value type doesn't match the property type: property type '{propertyType?.Name}', default value type '{defaultValueType.Name}'");
}
var test = Expression.NotEqual(propertyExp, Expression.Constant(null, propertyType));
var trueResult = propertyExp;
var falseResult = Expression.Constant(defaultValue, defaultValueType);
return Expression.Condition(test, trueResult, falseResult);
}
}
#endregion DefaultValue handle
#region Access nullable property
public static Expression AccessNullableProperty([NotNull] this Type type, [NotNull] string propertyPath)
{
return AccessNullableProperty(type, propertyPath.Split(DefaultPathSeparator));
}
public static Expression AccessNullableProperty([NotNull] this Type type, [NotNull] string propertyPath, [NotNull] string separator)
{
return AccessNullableProperty(type, propertyPath.Split(separator));
}
public static Expression AccessNullableProperty([NotNull] this Type type, [NotNull] string[] properties)
{
ArgumentNotNull(type, nameof(type));
ArgumentNotEmpty(properties, nameof(properties));
var paramExp = Expression.Parameter(type);
return AccessNullableProperty(paramExp, properties);
}
public static Expression AccessNullableProperty([NotNull] this Expression parameterExpression, [NotNull] string propertyPath)
{
return AccessNullableProperty(parameterExpression, propertyPath.Split(DefaultPathSeparator));
}
public static Expression AccessNullableProperty([NotNull] this Expression parameterExpression, [NotNull] string propertyPath, [NotNull] string separator)
{
return AccessNullableProperty(parameterExpression, propertyPath.Split(separator));
}
public static Expression AccessNullableProperty([NotNull] this Expression parameterExpression, [NotNull] string[] properties)
{
ArgumentNotNull(parameterExpression, nameof(parameterExpression));
ArgumentNotEmpty(properties, nameof(properties));
Expression access = parameterExpression;
foreach (var property in properties)
{
var indexAccess = ParseIndexAccess(property);
access = indexAccess.HasValue
? AccessIndex(access, indexAccess.Value.propertyName, indexAccess.Value.indexes)
: AccessNext(access, property);
}
static Expression AccessIndex(Expression member, string propertyName, Expression[] indexes)
{
switch (propertyName)
{
case { Length: 0 }:
{
foreach (var index in indexes)
{
member = member.IndexableGetNullableItem(index);
}
return member;
}
default:
{
member = AccessNext(member, propertyName);
foreach (var index in indexes)
{
member = member.IndexableGetNullableItem(index);
}
return member;
}
}
}
static Expression AccessNext(Expression member, string property)
{
if (member.Type.IsValueType)
{
if (Nullable.GetUnderlyingType(member.Type) == null)
{
// Value Type
return member.ValueTypeGetProperty(property);
}
else
{
// Nullable Value Type
return member.NullableTypeGetPropOrNull(property);
}
}
else
{
// Class
return member.ClassGetPropertyOrNull(property);
}
}
return TryConvertToNullable(access);
}
#endregion Access nullable property
#region Access not null property
public static Expression AccessProperty([NotNull] this Type type, [NotNull] string propertyPath)
{
return AccessProperty(type, propertyPath.Split(DefaultPathSeparator));
}
public static Expression AccessProperty([NotNull] this Type type, [NotNull] string propertyPath, [NotNull] string separator)
{
return AccessProperty(type, propertyPath.Split(separator));
}
public static Expression AccessProperty([NotNull] this Type type, [NotNull] string[] properties)
{
ArgumentNotNull(type, nameof(type));
ArgumentNotEmpty(properties, nameof(properties));
var paramExp = Expression.Parameter(type);
return AccessProperty(paramExp, properties);
}
public static Expression AccessProperty([NotNull] this Expression parameterExpression, [NotNull] string propertyPath)
{
return AccessProperty(parameterExpression, propertyPath.Split(DefaultPathSeparator));
}
public static Expression AccessProperty([NotNull] this Expression parameterExpression, [NotNull] string propertyPath, [NotNull] string separator)
{
return AccessProperty(parameterExpression, propertyPath.Split(separator));
}
public static Expression AccessProperty(this Expression parameterExpression, string[] properties)
{
ArgumentNotNull(parameterExpression, nameof(parameterExpression));
ArgumentNotEmpty(properties, nameof(properties));
Expression access = parameterExpression;
foreach (var property in properties)
{
var indexAccess = ParseIndexAccess(property);
if (!indexAccess.HasValue)
{
access = AccessNext(access, property);
}
else
{
access = AccessIndex(access, indexAccess.Value.propertyName, indexAccess.Value.indexes);
}
}
static Expression AccessIndex(Expression member, string propertyName, Expression[] indexes)
{
switch (propertyName)
{
case { Length: 0 }:
{
return indexes.Aggregate(member, (current, index) => current.IndexableGetItem(index));
}
default:
{
member = AccessNext(member, propertyName);
return indexes.Aggregate(member, (current, index) => current.IndexableGetItem(index));
}
}
}
static Expression AccessNext(Expression member, string property)
{
// Not index access
if (member.Type.IsValueType)
{
return Nullable.GetUnderlyingType(member.Type) == null
? member.ValueTypeGetProperty(property) // Not Nullable
: member.NullableTypeGetProperty(property); // Is Nullable
}
else
{
// Class
return member.ClassGetProperty(property);
}
}
return access;
}
#endregion Access not null property
//
// Branch
// 1. class : C?
// 2. not null value type : V
// 3. nullable value type : N?
//
// C!.Prop
// C?.Prop
//
// V.Prop
//
// N!.Value
// N!.Value.Prop
// N?.Value
//
#region Property Access
/// <summary>
/// C.Prop
/// </summary>
/// <param name="expression"></param>
/// <param name="property"></param>
/// <exception cref="InvalidOperationException"></exception>
private static MemberExpression ClassGetProperty(this Expression expression, string property)
{
IsClass(expression);
if (!expression.Type.IsClass)
{
throw new InvalidOperationException($"{nameof(expression)} {expression.Type.Name} must be class");
}
var exp = Expression.Property(expression, property); // C.Prop
return exp;
}
/// <summary>
/// C?.Prop
/// </summary>
/// <param name="expression"></param>
/// <param name="property"></param>
/// <returns></returns>
private static ConditionalExpression ClassGetPropertyOrNull([NotNull] this Expression expression, string property)
{
ArgumentNotNull(expression, nameof(expression));
IsClass(expression);
var test = Expression.NotEqual(expression, Expression.Constant(null, expression.Type)); // E: C == null
var propExp = Expression.Property(expression, property); // C.Prop
var propIsValueType = propExp.Type.IsValueType && Nullable.GetUnderlyingType(propExp.Type) == null;
Expression trueResult = propIsValueType
? Expression.Convert(propExp, typeof(Nullable<>).MakeGenericType(propExp.Type)) // T: Prop is VT: (Nullable<Prop>)C.Prop
: propExp; // T: Prop is class: C.Prop
var falseResult = Expression.Constant(null, trueResult.Type); // F: null
var exp = Expression.Condition(test, trueResult, falseResult); // E ? T : F;
return exp;
}
/// <summary>
/// V.Prop
/// </summary>
/// <param name="expression"></param>
/// <param name="property"></param>
/// <returns></returns>
private static MemberExpression ValueTypeGetProperty([NotNull] this Expression expression, string property)
{
ArgumentNotNull(expression, nameof(expression));
IsValueType(expression);
var exp = Expression.Property(expression, property); // V.Prop
return exp;
}
/// <summary>
/// NV?.Value
/// </summary>
/// <param name="expression"></param>
/// <returns></returns>
private static ConditionalExpression NullableTypeGetValueOrNull([NotNull] this Expression expression)
{
ArgumentNotNull(expression, nameof(expression));
IsNullableTypeOrThrow(expression);
var test = Expression.IsTrue(Expression.Property(expression, Nullable_HasValue)); // E: NV.HasValue == true
var trueResult = Expression.Convert(Expression.Property(expression, Nullable_Value), expression.Type); // T: (Nullable<T>)NV.Value
var falseResult = Expression.Constant(null, expression.Type); // F: (Nullable<T>)null
var exp = Expression.Condition(test, trueResult, falseResult); // E ? T : F
return exp;
}
/// <summary>
/// NV!.Value, maybe InvalidOperationException for no value
/// </summary>
/// <param name="expression"></param>
/// <returns></returns>
private static MemberExpression NullableTypeGetValue([NotNull] this Expression expression)
{
ArgumentNotNull(expression, nameof(expression));
IsNullableTypeOrThrow(expression);
var exp = Expression.Property(expression, nameof(Nullable<bool>.Value)); // NV!.Value
return exp;
}
/// <summary>
/// NV?.Value.Prop
/// </summary>
/// <param name="expression"></param>
/// <param name="property"></param>
/// <returns></returns>
private static ConditionalExpression NullableTypeGetPropOrNull([NotNull] this Expression expression, string property)
{
ArgumentNotNull(expression, nameof(expression));
IsNullableTypeOrThrow(expression);
var valueExp = NullableTypeGetValueOrNull(expression); // NV?.Value
var test = Expression.NotEqual(valueExp, Expression.Constant(null, valueExp.Type)); // E: NV?.Value != null
var propExp = NullableTypeGetProperty(valueExp.IfTrue, property); // Expression.Property(Expression.Property(valueExp.IfTrue, VTValue), property); // NV!.Value.Prop
var propIsValueType = propExp.Type.IsValueType && Nullable.GetUnderlyingType(propExp.Type) == null;
Expression trueResult = propIsValueType
? Expression.Convert(propExp, typeof(Nullable<>).MakeGenericType(propExp.Type)) // T: Prop is VT: (Nullable<Prop>)NV!.Value.Prop
: propExp; // T: Prop is class: NV!.Value.Prop
var falseResult = Expression.Constant(null, trueResult.Type); // F: (Nullable<Prop>)null
var exp = Expression.Condition(test, trueResult, falseResult); // NV?.Value != null ? NV!.Value.Prop : null
return exp;
}
/// <summary>
/// NV!.Value.Prop, maybe InvalidOperationException for no value
/// </summary>
/// <param name="expression"></param>
/// <param name="property"></param>
/// <returns></returns>
private static MemberExpression NullableTypeGetProperty([NotNull] this Expression expression, string property)
{
ArgumentNotNull(expression, nameof(expression));
IsNullableTypeOrThrow(expression);
var nvValue = expression.NullableTypeGetValue(); // NV!.Value
var exp = Expression.Property(nvValue, property); // NV!.Value.Prop
return exp;
}
private static Expression IndexableGetItem([NotNull] this Expression expression, Expression index)
{
var getItemMethod = expression.Type.GetMethod(GetItemMethodName, BindingFlags.Public | BindingFlags.Instance);
if (getItemMethod != null)
{
var getItemMethodCall = Expression.Call(expression, getItemMethod, index);
return getItemMethodCall;
}
else if (expression.Type.IsArray)
{
var indexAccess = Expression.ArrayIndex(expression, index);
return indexAccess;
}
throw new InvalidOperationException($"Not supported type '{expression.Type.Name}' for index access");
}
private static Expression IndexableGetNullableItem([NotNull] this Expression expression, [NotNull] Expression index)
{
// Array
if (expression.Type.IsArray)
{
if (index.Type != typeof(int))
{
throw new InvalidOperationException($"Array must be indexed by 'int', but get '{expression.Type.Name}'");
}
var lengthAccess = Expression.ArrayLength(expression);
var test = Expression.AndAlso(Expression.LessThan(index, lengthAccess), Expression.GreaterThanOrEqual(index, Expression.Constant(0, typeof(int))));
var trueResult = Expression.ArrayIndex(expression, index);
var falseResult = Expression.Constant(null, trueResult.Type);
return Expression.Condition(test, trueResult, falseResult);
}
// Dictionary<,> like, types that have the 'ContainsKey' method and the 'get_Item' method.
{
var getItemMethod = expression.Type.GetMethod(GetItemMethodName, BindingFlags.Public | BindingFlags.Instance);
var containsKeyMethod = expression.Type.GetMethod(nameof(IDictionary<int, int>.ContainsKey), BindingFlags.Public | BindingFlags.Instance);
if (getItemMethod != null && containsKeyMethod != null)
{
var containsKeyCall = Expression.Call(expression, containsKeyMethod, index);
var test = Expression.IsTrue(containsKeyCall);
var trueResult = Expression.Call(expression, getItemMethod, index);
var falseResult = Expression.Constant(null, trueResult.Type);
return Expression.Condition(test, trueResult, falseResult);
}
}
// Lisk like, types that have the 'get_Item' method and the 'Count' property.
{
var getItemMethod = expression.Type.GetMethod(GetItemMethodName, BindingFlags.Public | BindingFlags.Instance);
var countProperty = expression.Type.GetProperty(CountPropertyName, BindingFlags.Public | BindingFlags.Instance);
if (getItemMethod != null && countProperty != null)
{
if (index.Type != typeof(int))
{
throw new InvalidOperationException($"{expression.Type.Name} must be indexable by 'int', but get '{index.Type.Name}'");
}
var countAccess = Expression.Property(expression, countProperty);
var test = Expression.AndAlso(Expression.LessThan(index, countAccess), Expression.GreaterThanOrEqual(index, Expression.Constant(0, typeof(int))));
var trueResult = Expression.Call(expression, getItemMethod, index);
var falseResult = Expression.Constant(null, trueResult.Type);
return Expression.Condition(test, trueResult, falseResult);
}
}
throw new InvalidOperationException($"Not supported type '{expression.Type.Name}' for index accessing");
}
#endregion Property Access
#region Type Validate
/// <summary>
/// Check if expression.Type is class, otherwise throw and exception
/// </summary>
/// <param name="expression"></param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="InvalidOperationException"></exception>
private static void IsClass([NotNull] Expression expression)
{
ArgumentNotNull(expression, nameof(expression));
if (!expression.Type.IsClass)
{
throw new InvalidOperationException($"{nameof(expression)} {expression.Type.Name} must be class");
}
}
/// <summary>
/// Check if expression.Type is ValueType and not Nullable&lt;T&gt;, otherwise throw and exception
/// </summary>
/// <param name="expression"></param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="InvalidOperationException"></exception>
private static void IsValueType([NotNull] Expression expression)
{
ArgumentNotNull(expression, nameof(expression));
if (expression.Type.IsValueType && Nullable.GetUnderlyingType(expression.Type) != null)
{
throw new InvalidOperationException($"{nameof(expression)} {expression.Type.Name} must be value type");
}
}
/// <summary>
/// Check if expression.Type is Nullable&lt;T&gt;, otherwise throw and exception
/// </summary>
/// <param name="expression"></param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="InvalidOperationException"></exception>
private static void IsNullableTypeOrThrow([NotNull] Expression expression)
{
ArgumentNotNull(expression, nameof(expression));
if (Nullable.GetUnderlyingType(expression.Type) == null)
{
throw new InvalidOperationException($"{nameof(expression)} {expression.Type.Name} must be typeof Nullable<T>");
}
}
#endregion Type Validate
#region Utils
private static void ArgumentNotNull<T>(in T arg, string argName)
where T : class
{
if (arg == default)
{
throw new ArgumentNullException(argName);
}
}
private static void ArgumentNotEmpty<T>(in T[] arg, string argName)
where T : class
{
if (arg == default || arg is { Length: 0 })
{
throw new ArgumentException("Value cannot be an empty collection or null.", argName);
}
}
/// <summary>
/// expression should be like: ParameterExpression->MemberExpression1->MemberExpression2... ,
/// if the root for 'expression' is not ParameterExpression, this will return null.
/// </summary>
/// <param name="expression"></param>
/// <returns></returns>
private static ParameterExpression GetRootParameterExpression([NotNull] Expression expression)
{
return expression switch
{
MemberExpression { Expression: { } } memberExp => GetRootParameterExpression(memberExp.Expression),
ConditionalExpression conditionalExp => GetRootParameterExpression(conditionalExp.IfTrue),
UnaryExpression unaryExp => GetRootParameterExpression(unaryExp.Operand),
ParameterExpression paramExp => paramExp,
_ => throw new NullReferenceException()
};
}
/// <summary>
/// Try convert Expression type to Nullable type, only Non-Nullable ValueType can be converted
/// </summary>
/// <param name="expression"></param>
/// <returns></returns>
private static Expression TryConvertToNullable([NotNull] Expression expression)
{
if (expression.Type.IsValueType && Nullable.GetUnderlyingType(expression.Type) == null)
{
return Expression.Convert(expression, typeof(Nullable<>).MakeGenericType(expression.Type));
}
return expression;
}
/// <summary>
/// Check if property string has index operation and parse to Expression
/// </summary>
/// <param name="property"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
private static (string propertyName, Expression[] indexes)? ParseIndexAccess(string property)
{
const string IndexAccessErrorTemplate = "Invalid index property: {0}, index must be like 'prop[key]' or 'prop[key][key]...'";
if (!property.EndsWith(']'))
{
return null;
}
property = property.Replace(' ', '\0');
var sp = property.AsSpan();
var begin = 0;
var end = sp.IndexOf('[');
if (end == -1)
{
throw new InvalidOperationException(string.Format(IndexAccessErrorTemplate, property));
}
var propertyName = sp.Slice(begin, end).ToString(); // from 0 to index of first '[' treat as property name
sp = sp.Slice(end);
var leftCount = 0;
var rightCount = 0;
for (int i = 0; i < sp.Length; i++)
{
if (sp[i] == '[')
{
leftCount++;
}
else if (sp[i] == ']')
{
rightCount++;
}
}
if (leftCount != rightCount)
{
throw new InvalidOperationException(string.Format(IndexAccessErrorTemplate, property));
}
var indexes = new Expression[leftCount];
var count = 0;
while (sp.Length != 0)
{
var indexBegin = sp.IndexOf('[');
var indexEnd = sp.IndexOf(']');
if (indexBegin == -1 || indexEnd == -1 || indexBegin + 1 == indexEnd) // ']' not found or found "[]" which is incorrect
{
throw new InvalidOperationException();
}
var idxSpan = sp.Slice(indexBegin + 1, indexEnd - indexBegin - 1);
Expression indexExp;
if (idxSpan[0] == '"' && idxSpan[^1] == '"')
{
// is string key
var key = idxSpan.Slice(1, idxSpan.Length - 2).ToString();
indexExp = Expression.Constant(key, typeof(string));
}
else
{
// is int key
if (!int.TryParse(idxSpan, out var indexNum))
{
throw new InvalidOperationException(string.Format(IndexAccessErrorTemplate, property));
}
indexExp = Expression.Constant(indexNum, typeof(int));
}
indexes[count] = indexExp;
count++;
sp = sp.Slice(indexEnd + 1);
}
return (propertyName, indexes);
}
#endregion Utils
}
}

View File

@ -12,6 +12,11 @@ namespace AntDesign
public static class THelper
{
public static T ChangeType<T>(object value)
{
return ChangeType<T>(value, null);
}
public static T ChangeType<T>(object value, IFormatProvider provider)
{
var t = typeof(T);
@ -25,7 +30,8 @@ namespace AntDesign
t = Nullable.GetUnderlyingType(t);
}
return (T)Convert.ChangeType(value, t);
if (provider == null) return (T)Convert.ChangeType(value, t);
return (T)Convert.ChangeType(value, t, provider);
}
public static bool IsTypeNullable<T>()

View File

@ -3,6 +3,10 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using AntDesign.Core.Extensions;
using AntDesign.Core.JsInterop.ObservableApi;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace AntDesign.JsInterop
@ -12,6 +16,7 @@ namespace AntDesign.JsInterop
private ConcurrentDictionary<string, List<DomEventSubscription>> _domEventListeners = new ConcurrentDictionary<string, List<DomEventSubscription>>();
private readonly IJSRuntime _jsRuntime;
private bool? _isResizeObserverSupported = null;
public DomEventService(IJSRuntime jsRuntime)
{
@ -82,6 +87,70 @@ namespace AntDesign.JsInterop
});
}
public async ValueTask AddResizeObserver(ElementReference dom, Action<List<ResizeObserverEntry>> callback)
{
string key = FormatKey(dom.Id, nameof(JSInteropConstants.ObserverConstants.Resize));
if (!(await IsResizeObserverSupported()))
{
Action<JsonElement> action = (je) => callback.Invoke(new List<ResizeObserverEntry> { new ResizeObserverEntry() });
AddEventListener("window", "resize", action, false);
}
else
{
if (!_domEventListeners.ContainsKey(key))
{
_domEventListeners[key] = new List<DomEventSubscription>();
await _jsRuntime.InvokeVoidAsync(JSInteropConstants.ObserverConstants.Resize.Create, key, DotNetObjectReference.Create(new Invoker<string>((p) =>
{
for (var i = 0; i < _domEventListeners[key].Count; i++)
{
var subscription = _domEventListeners[key][i];
object tP = JsonSerializer.Deserialize(p, subscription.Type);
subscription.Delegate.DynamicInvoke(tP);
}
})));
await _jsRuntime.InvokeVoidAsync(JSInteropConstants.ObserverConstants.Resize.Observe, key, dom);
}
_domEventListeners[key].Add(new DomEventSubscription(callback, typeof(List<ResizeObserverEntry>)));
}
}
public async ValueTask RemoveResizeObserver(ElementReference dom, Action<List<ResizeObserverEntry>> callback)
{
string key = FormatKey(dom.Id, nameof(JSInteropConstants.ObserverConstants.Resize));
if (_domEventListeners.ContainsKey(key))
{
var subscription = _domEventListeners[key].SingleOrDefault(s => s.Delegate == (Delegate)callback);
if (subscription != null)
{
_domEventListeners[key].Remove(subscription);
}
}
}
public async ValueTask DisposeResizeObserver(ElementReference dom)
{
string key = FormatKey(dom.Id, nameof(JSInteropConstants.ObserverConstants.Resize));
if (await IsResizeObserverSupported())
{
await _jsRuntime.InvokeVoidAsync(JSInteropConstants.ObserverConstants.Resize.Dispose, key);
}
_domEventListeners.TryRemove(key, out _);
}
public async ValueTask DisconnectResizeObserver(ElementReference dom)
{
string key = FormatKey(dom.Id, nameof(JSInteropConstants.ObserverConstants.Resize));
if (await IsResizeObserverSupported())
{
await _jsRuntime.InvokeVoidAsync(JSInteropConstants.ObserverConstants.Resize.Disconnect, key);
}
if (_domEventListeners.ContainsKey(key))
{
_domEventListeners[key].Clear();
}
}
private static string FormatKey(object dom, string eventName) => $"{dom}-{eventName}";
public void RemoveEventListerner<T>(object dom, string eventName, Action<T> callback)
@ -96,6 +165,8 @@ namespace AntDesign.JsInterop
}
}
}
private async ValueTask<bool> IsResizeObserverSupported() => _isResizeObserverSupported ??= await _jsRuntime.IsResizeObserverSupported();
}
public class Invoker<T>

View File

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AntDesign.JsInterop
{

View File

@ -1,18 +1,31 @@
namespace AntDesign.JsInterop
using System.Text.Json.Serialization;
namespace AntDesign.JsInterop
{
public class DomRect
{
public decimal x { get; set; }
[JsonPropertyName("x")]
public decimal X { get; set; }
public decimal y { get; set; }
[JsonPropertyName("y")]
public decimal Y { get; set; }
public decimal width { get; set; }
[JsonPropertyName("width")]
public decimal Width { get; set; }
public decimal height { get; set; }
[JsonPropertyName("height")]
public decimal Height { get; set; }
public decimal top { get; set; }
public decimal right { get; set; }
public decimal bottom { get; set; }
public decimal left { get; set; }
[JsonPropertyName("top")]
public decimal Top { get; set; }
[JsonPropertyName("right")]
public decimal Right { get; set; }
[JsonPropertyName("bottom")]
public decimal Bottom { get; set; }
[JsonPropertyName("left")]
public decimal Left { get; set; }
}
}
}

View File

@ -1,33 +0,0 @@
namespace AntDesign.JsInterop
{
public class Element
{
public int absoluteTop { get; set; }
public int absoluteLeft { get; set; }
public int offsetTop { get; set; }
public int offsetLeft { get; set; }
public int offsetWidth { get; set; }
public int offsetHeight { get; set; }
public int scrollHeight { get; set; }
public int scrollWidth { get; set; }
public double scrollLeft { get; set; }
public double scrollTop { get; set; }
public int clientTop { get; set; }
public int clientLeft { get; set; }
public int clientHeight { get; set; }
public int clientWidth { get; set; }
}
}

View File

@ -0,0 +1,52 @@
using System.Text.Json.Serialization;
namespace AntDesign.JsInterop
{
public class HtmlElement
{
[JsonPropertyName("absoluteTop")]
public int AbsoluteTop { get; set; }
[JsonPropertyName("absoluteLeft")]
public int AbsoluteLeft { get; set; }
[JsonPropertyName("offsetTop")]
public int OffsetTop { get; set; }
[JsonPropertyName("offsetLeft")]
public int OffsetLeft { get; set; }
[JsonPropertyName("offsetWidth")]
public int OffsetWidth { get; set; }
[JsonPropertyName("offsetHeight")]
public int OffsetHeight { get; set; }
[JsonPropertyName("scrollHeight")]
public int ScrollHeight { get; set; }
[JsonPropertyName("scrollWidth")]
public int ScrollWidth { get; set; }
[JsonPropertyName("scrollLeft")]
public double ScrollLeft { get; set; }
[JsonPropertyName("scrollTop")]
public double ScrollTop { get; set; }
[JsonPropertyName("clientTop")]
public int ClientTop { get; set; }
[JsonPropertyName("clientLeft")]
public int ClientLeft { get; set; }
[JsonPropertyName("clientHeight")]
public int ClientHeight { get; set; }
[JsonPropertyName("clientWidth")]
public int ClientWidth { get; set; }
[JsonPropertyName("selectionStart")]
public int SelectionStart { get; set; }
}
}

View File

@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace AntDesign
{
@ -9,6 +6,7 @@ namespace AntDesign
{
private const string FUNC_PREFIX = "AntDesign.interop.";
public static string IsResizeObserverSupported => $"{FUNC_PREFIX}isResizeObserverSupported";
public static string GetDomInfo => $"{FUNC_PREFIX}getDomInfo";
public static string TriggerEvent => $"{FUNC_PREFIX}triggerEvent";
@ -35,7 +33,11 @@ namespace AntDesign
public static string Log => $"{FUNC_PREFIX}log";
#if NET5_0_OR_GREATER
[Obsolete("It will be removed in the future, because Blazor already has a native implementation.")]
#endif
public static string Focus => $"{FUNC_PREFIX}focus";
public static string HasFocus => $"{FUNC_PREFIX}hasFocus";
public static string Blur => $"{FUNC_PREFIX}blur";
@ -106,6 +108,9 @@ namespace AntDesign
public static string SetDomAttribute => $"{FUNC_PREFIX}setDomAttribute";
public static string SetSelectionStart => $"{FUNC_PREFIX}setSelectionStart";
public static string InvokeTabKey => $"{FUNC_PREFIX}invokeTabKey";
#region Draggable Modal
public static string EnableDraggable => $"{FUNC_PREFIX}enableDraggable";
@ -115,5 +120,21 @@ namespace AntDesign
public static string ResetModalPosition => $"{FUNC_PREFIX}resetModalPosition";
#endregion Draggable Modal
public static class ObserverConstants
{
private const string FUNC_PREFIX = JSInteropConstants.FUNC_PREFIX + "observable.";
public static class Resize
{
private const string FUNC_PREFIX = ObserverConstants.FUNC_PREFIX + "resize.";
public static string Create = $"{FUNC_PREFIX}create";
public static string Observe = $"{FUNC_PREFIX}observe";
public static string Unobserve = $"{FUNC_PREFIX}unobserve";
public static string Disconnect = $"{FUNC_PREFIX}disconnect";
public static string Dispose = $"{FUNC_PREFIX}dispose";
}
}
}
}

View File

@ -0,0 +1,13 @@
using System.Text.Json.Serialization;
namespace AntDesign.Core.JsInterop.ObservableApi
{
public class BoxSize
{
[JsonPropertyName("blockSize")]
public decimal BlockSize { get; set; }
[JsonPropertyName("inlineSize")]
public decimal InlineSize { get; set; }
}
}

View File

@ -0,0 +1,22 @@
using System.Text.Json.Serialization;
using AntDesign.JsInterop;
using Microsoft.AspNetCore.Components;
namespace AntDesign.Core.JsInterop.ObservableApi
{
public class ResizeObserverEntry
{
[JsonPropertyName("borderBoxSize")]
public BoxSize BorderBoxSize { get; set; }
[JsonPropertyName("contentBoxSize")]
public BoxSize ContentBoxSize { get; set; }
[JsonPropertyName("contentRect")]
public DomRect ContentRect { get; set; }
[JsonPropertyName("target")]
public ElementReference Target { get; set; }
}
}

View File

@ -0,0 +1,3 @@
import * as resize from './resizeObserver';
export { resize };

View File

@ -0,0 +1,101 @@
import { getDom } from '../interop';
const resizeObservers: Map<string, ResizeObserver> = new Map<string, ResizeObserver>();
export function create(key, invoker) {
const observer = new ResizeObserver((entries, observer) => observerCallBack(entries, observer, invoker));
resizeObservers.set(key, observer)
}
export function observe(key: string, element) {
const observer = resizeObservers.get(key);
if (observer) {
let dom = getDom(element);
observer.observe(dom);
}
}
export function disconnect(key: string): void {
const observer = this.resizeObservers.get(key)
if (observer) {
observer.disconnect()
}
}
export function unobserve(key: string, element: Element): void {
const observer = this.resizeObservers.get(key)
if (observer) {
let dom = getDom(element);
observer.unobserve(dom)
}
}
export function dispose(key: string): void {
console.log("dispose", key);
disconnect(key)
resizeObservers.delete(key)
}
class BoxSize {
public blockSize?: number
public inlineSize?: number
}
class DomRect {
x?: number
y?: number
width?: number
height?: number
top?: number
right?: number
bottom?: number
left?: number
}
class ResizeObserverEntry {
borderBoxSize?: BoxSize
contentBoxSize?: BoxSize
contentRect?: DomRect
target?: Element
}
function observerCallBack(entries, observer, invoker) {
if (invoker) {
const mappedEntries = new Array<ResizeObserverEntry>()
entries.forEach(entry => {
if (entry) {
const mEntry = new ResizeObserverEntry()
if (entry.borderBoxSize) {
mEntry.borderBoxSize = new BoxSize()
mEntry.borderBoxSize.blockSize = entry.borderBoxSize.blockSize
mEntry.borderBoxSize.inlineSize = entry.borderBoxSize.inlineSize
}
if (entry.contentBoxSize) {
mEntry.contentBoxSize = new BoxSize()
mEntry.contentBoxSize.blockSize = entry.contentBoxSize.blockSize
mEntry.contentBoxSize.inlineSize = entry.contentBoxSize.inlineSize
}
if (entry.contentRect) {
mEntry.contentRect = new DomRect()
mEntry.contentRect.x = entry.contentRect.x
mEntry.contentRect.y = entry.contentRect.y
mEntry.contentRect.width = entry.contentRect.width
mEntry.contentRect.height = entry.contentRect.height
mEntry.contentRect.top = entry.contentRect.top
mEntry.contentRect.right = entry.contentRect.right
mEntry.contentRect.bottom = entry.contentRect.bottom
mEntry.contentRect.left = entry.contentRect.left
}
mEntry.target = entry.target
mappedEntries.push(mEntry)
}
})
const entriesJson = JSON.stringify(mappedEntries)
invoker.invokeMethodAsync('Invoke', entriesJson)
}
}

View File

@ -1,3 +1,11 @@
import * as observable from './ObservableApi/observableApi';
export { observable };
export function isResizeObserverSupported(): boolean {
return "ResizeObserver" in window;
}
export function getDom(element) {
if (!element) {
element = document.body;
@ -12,9 +20,10 @@ export function getDom(element) {
export function getDomInfo(element) {
var result = {};
var dom = getDom(element);
if (!dom) {
dom = {};
}
result["offsetTop"] = dom.offsetTop || 0;
result["offsetLeft"] = dom.offsetLeft || 0;
result["offsetWidth"] = dom.offsetWidth || 0;
@ -27,6 +36,7 @@ export function getDomInfo(element) {
result["clientLeft"] = dom.clientLeft || 0;
result["clientHeight"] = dom.clientHeight || 0;
result["clientWidth"] = dom.clientWidth || 0;
result["selectionStart"] = dom.selectionStart || 0;
var absolutePosition = getElementAbsolutePos(dom);
result["absoluteTop"] = Math.round(absolutePosition.y);
result["absoluteLeft"] = Math.round(absolutePosition.x);
@ -147,7 +157,18 @@ export function triggerEvent(element, eventType, eventName) {
export function getBoundingClientRect(element) {
let dom = getDom(element);
if (dom && dom.getBoundingClientRect) {
return dom.getBoundingClientRect();
let domRect = dom.getBoundingClientRect();
// Fixes #1468. This wrapping is necessary for old browsers. Remove this when one day we no longer support them.
return {
width: domRect.width,
height: domRect.height,
top: domRect.top,
right: domRect.right,
bottom: domRect.bottom,
left: domRect.left,
x: domRect.x,
y: domRect.y
};
}
return null;
}
@ -164,7 +185,7 @@ export function addDomEventListener(element, eventName, preventDefault, invoker)
if (v instanceof Node) return 'Node';
if (v instanceof Window) return 'Window';
return v;
}, ' ');
}, ' ');
setTimeout(function () { invoker.invokeMethodAsync('Invoke', json) }, 0);
if (preventDefault === true) {
args.preventDefault();
@ -179,7 +200,9 @@ export function addDomEventListener(element, eventName, preventDefault, invoker)
}
} else {
let dom = getDom(element);
(dom as HTMLElement).addEventListener(eventName, callback);
if (dom) {
(dom as HTMLElement).addEventListener(eventName, callback);
}
}
}
@ -222,13 +245,13 @@ export function copy(text) {
});
}
export function focus(selector, noScroll: boolean=false) {
let dom = getDom(selector);
export function focus(selector, noScroll: boolean = false) {
let dom = getDom(selector);
if (!(dom instanceof HTMLElement))
throw new Error("Unable to focus an invalid element.");
dom.focus({
preventScroll: noScroll
})
});
}
export function hasFocus(selector) {
@ -238,7 +261,9 @@ export function hasFocus(selector) {
export function blur(selector) {
let dom = getDom(selector);
dom.blur();
if (dom) {
dom.blur();
}
}
export function log(text) {
@ -280,21 +305,26 @@ export function scrollTo(target) {
export function getFirstChildDomInfo(element) {
var dom = getDom(element);
if (dom.firstElementChild)
return getDomInfo(dom.firstElementChild);
return getDomInfo(dom);
if (dom) {
if (dom.firstElementChild) {
return getDomInfo(dom.firstElementChild);
} else {
return getDomInfo(dom);
}
}
return null;
}
export function addClsToFirstChild(element, className) {
var dom = getDom(element);
if (dom.firstElementChild) {
if (dom && dom.firstElementChild) {
dom.firstElementChild.classList.add(className);
}
}
export function removeClsFromFirstChild(element, className) {
var dom = getDom(element);
if (dom.firstElementChild) {
if (dom && dom.firstElementChild) {
dom.firstElementChild.classList.remove(className);
}
}
@ -302,7 +332,7 @@ export function removeClsFromFirstChild(element, className) {
export function addDomEventListenerToFirstChild(element, eventName, preventDefault, invoker) {
var dom = getDom(element);
if (dom.firstElementChild) {
if (dom && dom.firstElementChild) {
addDomEventListener(dom.firstElementChild, eventName, preventDefault, invoker);
}
}
@ -402,22 +432,25 @@ export function css(element: HTMLElement, name: string | object, value: string |
export function addCls(selector: Element | string, clsName: string | Array<string>) {
let element = getDom(selector);
if (typeof clsName === "string") {
element.classList.add(clsName);
} else {
element.classList.add(...clsName);
if (element) {
if (typeof clsName === "string") {
element.classList.add(clsName);
} else {
element.classList.add(...clsName);
}
}
}
export function removeCls(selector: Element | string, clsName: string | Array<string>) {
let element = getDom(selector);
if (typeof clsName === "string") {
element.classList.remove(clsName);
} else {
element.classList.remove(...clsName);
if (element) {
if (typeof clsName === "string") {
element.classList.remove(clsName);
} else {
element.classList.remove(...clsName);
}
}
}
export function elementScrollIntoView(selector: Element | string) {
@ -486,7 +519,8 @@ export function getScroll() {
export function getInnerText(element) {
let dom = getDom(element);
return dom.innerText;
if (dom) return dom.innerText;
return null;
}
export function getMaxZIndex() {
@ -503,6 +537,7 @@ export function getStyle(element, styleProp) {
export function getTextAreaInfo(element) {
var result = {};
var dom = getDom(element);
if (!dom) return null;
result["scrollHeight"] = dom.scrollHeight || 0;
if (element.currentStyle) {
@ -639,8 +674,10 @@ export function addPreventKeys(inputElement, keys: string[]) {
export function removePreventKeys(inputElement) {
if (inputElement) {
let dom = getDom(inputElement);
(dom as HTMLElement).removeEventListener("keydown", funcDict[inputElement.id + "keydown"]);
funcDict[inputElement.id + "keydown"] = null;
if(dom){
(dom as HTMLElement).removeEventListener("keydown", funcDict[inputElement.id + "keydown"]);
funcDict[inputElement.id + "keydown"] = null;
}
}
}
@ -654,22 +691,58 @@ function preventKeyOnCondition(e, key: string, check: () => boolean) {
export function addPreventEnterOnOverlayVisible(element, overlayElement) {
if (element && overlayElement) {
let dom = getDom(element);
funcDict[element.id + "keydown:Enter"] = (e) => preventKeyOnCondition(e, "enter", () => overlayElement.offsetParent !== null);
(dom as HTMLElement).addEventListener("keydown", funcDict[element.id + "keydown:Enter"], false);
if(dom){
funcDict[element.id + "keydown:Enter"] = (e) => preventKeyOnCondition(e, "enter", () => overlayElement.offsetParent !== null);
(dom as HTMLElement).addEventListener("keydown", funcDict[element.id + "keydown:Enter"], false);
}
}
}
export function removePreventEnterOnOverlayVisible(element) {
if (element) {
let dom = getDom(element);
(dom as HTMLElement).removeEventListener("keydown", funcDict[element.id + "keydown:Enter"]);
funcDict[element.id + "keydown:Enter"] = null;
if(dom){
(dom as HTMLElement).removeEventListener("keydown", funcDict[element.id + "keydown:Enter"]);
funcDict[element.id + "keydown:Enter"] = null;
}
}
}
export function setDomAttribute(element, attributes) {
let dom = getDom(element);
for (var key in attributes) {
(dom as HTMLElement).setAttribute(key, attributes[key]);
if(dom){
for (var key in attributes) {
(dom as HTMLElement).setAttribute(key, attributes[key]);
}
}
}
}
export function setSelectionStart(element, position) {
if (position >= 0) {
let dom = getDom(element);
if(dom){
if (position <= dom.value.length) {
dom.selectionStart = position;
dom.selectionEnd = position;
}
}
}
}
//copied from https://www.telerik.com/forums/trigger-tab-key-when-enter-key-is-pressed
export function invokeTabKey() {
var currInput = document.activeElement;
if (currInput.tagName.toLowerCase() == "input") {
var inputs = document.getElementsByTagName("input");
var currInput = document.activeElement;
for (var i = 0; i < inputs.length; i++) {
if (inputs[i] == currInput) {
var next = inputs[i + 1];
if (next && next.focus) {
next.focus();
}
break;
}
}
}
}

View File

@ -1,8 +1,9 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using AntDesign.Core.Extensions;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
namespace AntDesign
{
@ -51,6 +52,13 @@ namespace AntDesign
private async Task OnInputClick()
{
if (_duringManualInput)
{
return;
}
_openingOverlay = !_dropDown.IsOverlayShow();
AutoFocus = true;
//Reset Picker to default in case it the picker value was changed
//but no value was selected (for example when a user clicks next
//month but does not select any value)
@ -72,6 +80,8 @@ namespace AntDesign
}
}
private TValue _cacheDuringInput;
protected void OnInput(ChangeEventArgs args, int index = 0)
{
if (index != 0)
@ -82,65 +92,111 @@ namespace AntDesign
{
return;
}
if (BindConverter.TryConvertTo(args.Value.ToString(), CultureInfo, out TValue changeValue))
if (!_duringManualInput)
{
if (Picker == DatePickerType.Date)
{
if (IsDateStringFullDate(args.Value.ToString()))
CurrentValue = changeValue;
}
else
CurrentValue = changeValue;
_duringManualInput = true;
_cacheDuringInput = Value;
}
if (FormatAnalyzer.TryPickerStringConvert(args.Value.ToString(), out TValue changeValue, IsNullable))
{
Value = changeValue;
GetIfNotNull(changeValue, (notNullValue) =>
{
PickerValues[0] = notNullValue;
});
if (OnChange.HasDelegate)
{
OnChange.InvokeAsync(new DateTimeChangedEventArgs
{
Date = Convert.ToDateTime(changeValue, this.CultureInfo),
DateString = GetInputValue(index)
});
}
StateHasChanged();
}
UpdateCurrentValueAsString();
}
protected override Task OnBlur(int index)
{
if (_openingOverlay)
return Task.CompletedTask;
if (_duringManualInput)
{
if (!Value.Equals(_cacheDuringInput))
{
//reset picker to Value
Value = _cacheDuringInput;
_pickerStatus[0]._hadSelectValue = !(Value is null && (DefaultValue is not null || DefaultPickerValue is not null));
GetIfNotNull(Value ?? DefaultValue ?? DefaultPickerValue, (notNullValue) =>
{
PickerValues[0] = notNullValue;
});
}
_duringManualInput = false;
}
AutoFocus = false;
return Task.CompletedTask;
}
/// <summary>
/// Method is called via EventCallBack if the keyboard key is no longer pressed inside the Input element.
/// </summary>
/// <param name="e">Contains the key (combination) which was pressed inside the Input element</param>
protected async Task OnKeyUp(KeyboardEventArgs e)
protected async Task OnKeyDown(KeyboardEventArgs e)
{
if (e == null) throw new ArgumentNullException(nameof(e));
var key = e.Key.ToUpperInvariant();
if (key == "ENTER")
if (key == "ENTER" || key == "TAB" || key == "ESCAPE")
{
_duringManualInput = false;
if (string.IsNullOrWhiteSpace(_inputStart.Value))
ClearValue();
else
await TryApplyInputValue();
if (key == "ESCAPE" && _dropDown.IsOverlayShow())
{
if (BindConverter.TryConvertTo(_inputStart.Value, CultureInfo, out TValue changeValue))
Value = changeValue;
Close();
await Js.FocusAsync(_inputStart.Ref);
return;
}
if (key == "ENTER")
{
//needed only in wasm, details: https://github.com/dotnet/aspnetcore/issues/30070
await Task.Yield();
await Js.InvokeVoidAsync(JSInteropConstants.InvokeTabKey);
}
Close();
AutoFocus = false;
return;
}
if (key == "ARROWDOWN" && !_dropDown.IsOverlayShow())
{
await _dropDown.Show();
return;
}
if (key == "ARROWUP" && _dropDown.IsOverlayShow())
{
Close();
return;
}
}
private async Task TryApplyInputValue()
{
if (FormatAnalyzer.TryPickerStringConvert(_inputStart.Value, out TValue changeValue, IsNullable))
{
CurrentValue = changeValue;
GetIfNotNull(changeValue, (notNullValue) =>
{
PickerValues[0] = notNullValue;
});
if (OnChange.HasDelegate)
{
await OnChange.InvokeAsync(new DateTimeChangedEventArgs
{
Date = Convert.ToDateTime(changeValue, this.CultureInfo),
DateString = GetInputValue(0)
});
}
}
}
@ -191,14 +247,7 @@ namespace AntDesign
UpdateCurrentValueAsString();
if (IsRange && !IsShowTime && Picker != DatePickerType.Time)
{
if (_pickerStatus[0]._hadSelectValue && _pickerStatus[1]._hadSelectValue)
{
Close();
}
}
else if (!IsShowTime && Picker != DatePickerType.Time)
if (!IsShowTime && Picker != DatePickerType.Time)
{
Close();
}
@ -219,7 +268,7 @@ namespace AntDesign
_pickerStatus[0]._hadSelectValue = true;
}
public override void ClearValue(int index = 0)
public override void ClearValue(int index = 0, bool closeDropdown = true)
{
_isSetPicker = false;
@ -227,7 +276,8 @@ namespace AntDesign
CurrentValue = DefaultValue;
else
CurrentValue = default;
Close();
if (closeDropdown)
Close();
}
private void GetIfNotNull(TValue value, Action<DateTime> notNullAction)
@ -245,5 +295,11 @@ namespace AntDesign
notNullAction?.Invoke(Convert.ToDateTime(value, CultureInfo));
}
}
private void OverlayVisibleChange(bool visible)
{
OnOpenChange.InvokeAsync(visible);
_openingOverlay = false;
}
}
}

View File

@ -9,7 +9,7 @@
IsButton="@true"
Disabled="Disabled"
PopupContainerSelector="@PopupContainerSelector"
OnVisibleChange="visible => { AutoFocus = visible; OnOpenChange.InvokeAsync(visible); }"
OnVisibleChange="OverlayVisibleChange"
OverlayEnterCls="ant-slide-up-enter ant-slide-up-enter-active ant-slide-up"
OverlayLeaveCls="ant-slide-up-leave ant-slide-up-leave-active ant-slide-up"
Trigger="new TriggerType[] { TriggerType.Click }">
@ -34,8 +34,10 @@
ReadOnly="@InputReadOnly"
AutoFocus="@AutoFocus"
OnClick="async e => { await OnInputClick(); }"
OnKeyUp="@OnKeyUp"
OnKeyDown="@OnKeyDown"
OnInput="e => OnInput(e, 0)"
OnBlur="e => OnBlur(0)"
Onfocus="e => AutoFocus = true"
ShowTime="@(Picker == DatePickerType.Time)"
OnClickClear="e => ClearValue(0)"
AllowClear="@AllowClear" />

View File

@ -9,9 +9,10 @@
IsButton="@true"
Disabled="Disabled"
PopupContainerSelector="@PopupContainerSelector"
OnVisibleChange="visible => { AutoFocus = visible; OnOpenChange.InvokeAsync(visible); }"
OverlayEnterCls="ant-slide-up-enter ant-slide-up-enter-active ant-slide-up ant-picker-dropdown-range"
OverlayLeaveCls="ant-slide-up-leave ant-slide-up-leave-active ant-slide-up ant-picker-dropdown-range"
OnVisibleChange="visible => OnOpenChange.InvokeAsync(visible)"
OverlayClassName="ant-picker-dropdown-range"
OverlayEnterCls="ant-slide-up-enter ant-slide-up-enter-active ant-slide-up"
OverlayLeaveCls="ant-slide-up-leave ant-slide-up-leave-active ant-slide-up"
Trigger="new TriggerType[] { TriggerType.Click }">
<Overlay>
<div class="@(PrefixCls)-range-arrow" style="@_rangeArrowStyle"/>
@ -49,13 +50,14 @@
AutoFocus="@AutoFocus"
OnClick="async e => { await OnInputClick(0); }"
OnInput="e => OnInput(e, 0)"
OnKeyUp="e => OnKeyUp(e, 0)"
OnKeyDown="e => OnKeyDown(e, 0)"
Onfocus="() => OnFocus(0)"
OnBlur="e => OnBlur(0)"
ShowTime="@(Picker == DatePickerType.Time)"
ShowSuffixIcon="false"
IsRange="@IsRange"
AllowClear="@AllowClear"
OnClickClear="e => ClearValue(0)" />
OnClickClear="e => ClearValue(-1)" />
<div class="@(PrefixCls)-range-separator">
<span aria-label="to" class="@(PrefixCls)-separator">
@ -73,12 +75,13 @@
AutoFocus="@AutoFocus"
OnClick="async e => { await OnInputClick(1); }"
OnInput="e => OnInput(e, 1)"
OnKeyUp="e => OnKeyUp(e, 1)"
OnKeyDown="e => OnKeyDown(e, 1)"
Onfocus="() => OnFocus(1)"
OnBlur="e => OnBlur(1)"
ShowTime="@(Picker == DatePickerType.Time)"
IsRange="@IsRange"
AllowClear="@AllowClear"
OnClickClear="e => ClearValue(0)" />
OnClickClear="e => ClearValue(-1)" />
<div class="@(PrefixCls)-active-bar" style="@_activeBarStyle"></div>
</div>
</Unbound>

View File

@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using AntDesign.Core.Extensions;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
namespace AntDesign
{
@ -76,34 +78,77 @@ namespace AntDesign
};
}
private async Task OnInputClick(int index)
{
if (_duringManualInput)
{
return;
}
_openingOverlay = !_dropDown.IsOverlayShow();
//Reset Picker to default in case the picker value was changed
//but no value was selected (for example when a user clicks next
//month but does not select any value)
if (UseDefaultPickerValue[index] && DefaultPickerValue != null)
{
PickerValues[index] = _pickerValuesAfterInit[index];
}
await _dropDown.Show();
// clear status
_pickerStatus[0]._currentShowHadSelectValue = false;
_pickerStatus[1]._currentShowHadSelectValue = false;
if (index == 0)
{
// change start picker value
if (!_inputStart.IsOnFocused && _pickerStatus[index]._hadSelectValue && !UseDefaultPickerValue[index])
{
GetIfNotNull(Value, index, notNullValue =>
{
ChangePickerValue(notNullValue, index);
});
}
ChangeFocusTarget(true, false);
}
else
{
// change end picker value
if (!_inputEnd.IsOnFocused && _pickerStatus[index]._hadSelectValue && !UseDefaultPickerValue[index])
{
GetIfNotNull(Value, index, notNullValue =>
{
ChangePickerValue(notNullValue, index);
});
}
ChangeFocusTarget(false, true);
}
}
private DateTime? _cacheDuringInput;
private DateTime _pickerValueCache;
protected void OnInput(ChangeEventArgs args, int index = 0)
{
if (args == null)
{
return;
}
var array = Value as Array;
if (BindConverter.TryConvertTo(args.Value.ToString(), CultureInfo, out DateTime changeValue))
if (!_duringManualInput)
{
if (Picker == DatePickerType.Date)
{
if (IsDateStringFullDate(args.Value.ToString()))
array.SetValue(changeValue, index);
}
else
array.SetValue(changeValue, index);
_duringManualInput = true;
_cacheDuringInput = array.GetValue(index) as DateTime?;
_pickerValueCache = PickerValues[index];
}
if (FormatAnalyzer.TryPickerStringConvert(args.Value.ToString(), out DateTime changeValue, false))
{
array.SetValue(changeValue, index);
ChangePickerValue(changeValue, index);
if (OnChange.HasDelegate)
{
OnChange.InvokeAsync(new DateRangeChangedEventArgs
{
Dates = new DateTime?[] { array.GetValue(0) as DateTime?, array.GetValue(1) as DateTime? },
DateStrings = new string[] { GetInputValue(0), GetInputValue(1) }
});
}
StateHasChanged();
}
@ -116,42 +161,118 @@ namespace AntDesign
/// </summary>
/// <param name="e">Contains the key (combination) which was pressed inside the Input element</param>
/// <param name="index">Refers to picker index - 0 for starting date, 1 for ending date</param>
protected async Task OnKeyUp(KeyboardEventArgs e, int index)
protected async Task OnKeyDown(KeyboardEventArgs e, int index)
{
if (e == null) throw new ArgumentNullException(nameof(e));
var key = e.Key.ToUpperInvariant();
if (key == "ENTER")
if (key == "ENTER" || key == "TAB" || key == "ESCAPE")
{
_duringManualInput = false;
var input = (index == 0 ? _inputStart : _inputEnd);
if (string.IsNullOrWhiteSpace(input.Value))
ClearValue(index);
else
ClearValue(index, false);
else if (!await TryApplyInputValue(index, input.Value))
return;
if (key == "ESCAPE" && _dropDown.IsOverlayShow())
{
if (BindConverter.TryConvertTo(input.Value, CultureInfo, out DateTime changeValue))
Close();
await Js.FocusAsync(input.Ref);
return;
}
if (index == 1)
{
if (key != "TAB")
{
var array = Value as Array;
array.SetValue(changeValue, index);
//needed only in wasm, details: https://github.com/dotnet/aspnetcore/issues/30070
await Task.Yield();
await Js.InvokeVoidAsync(JSInteropConstants.InvokeTabKey);
Close();
}
if (index == 0)
else if (!e.ShiftKey)
{
Close();
AutoFocus = false;
}
}
if (index == 0)
{
if (key == "TAB" && e.ShiftKey)
{
Close();
AutoFocus = false;
}
else if (key != "TAB")
{
await Blur(0);
await Focus(1);
}
else
Close();
}
return;
}
if (key == "ARROWDOWN" && !_dropDown.IsOverlayShow())
{
await _dropDown.Show();
return;
}
if (key == "ARROWUP" && _dropDown.IsOverlayShow())
{
Close();
await Task.Yield();
AutoFocus = true;
return;
}
}
private async Task<bool> TryApplyInputValue(int index, string inputValue)
{
if (FormatAnalyzer.TryPickerStringConvert(inputValue, out DateTime changeValue, false))
{
var array = Value as Array;
array.SetValue(changeValue, index);
var validationSuccess = await ValidateRange(index, changeValue, array);
if (OnChange.HasDelegate)
{
await OnChange.InvokeAsync(new DateRangeChangedEventArgs
{
Dates = new DateTime?[] { array.GetValue(0) as DateTime?, array.GetValue(1) as DateTime? },
DateStrings = new string[] { GetInputValue(0), GetInputValue(1) }
});
}
return validationSuccess;
}
return false;
}
private async Task<bool> ValidateRange(int index, DateTime newDate, Array array)
{
if (index == 0 && array.GetValue(1) is not null && ((DateTime)array.GetValue(1)).CompareTo(newDate) < 0)
{
ClearValue(1, false);
await Blur(0);
await Focus(1);
return false;
}
else if (index == 1)
{
if (array.GetValue(0) is not null && newDate.CompareTo((DateTime)array.GetValue(0)) < 0)
{
ClearValue(0, false);
await Blur(1);
await Focus(0);
return false;
}
else if (array.GetValue(0) is null)
{
await Blur(1);
await Focus(0);
return false;
}
}
return true;
}
private async Task OnFocus(int index)
{
if (index == 0)
@ -170,6 +291,33 @@ namespace AntDesign
await Focus(1);
}
}
AutoFocus = true;
}
protected override Task OnBlur(int index)
{
if (_openingOverlay)
return Task.CompletedTask;
if (_duringManualInput)
{
var array = Value as Array;
if (!array.GetValue(index).Equals(_cacheDuringInput))
{
//reset picker to Value
if (IsNullable)
array.SetValue(_cacheDuringInput, index);
else
array.SetValue(_cacheDuringInput.GetValueOrDefault(), index);
_pickerStatus[index]._hadSelectValue = !(Value is null && (DefaultValue is not null || DefaultPickerValue is not null));
ChangePickerValue(_pickerValueCache, index);
}
_duringManualInput = false;
}
AutoFocus = false;
return Task.CompletedTask;
}
protected override void OnInitialized()
@ -279,74 +427,33 @@ namespace AntDesign
}
}
public override void ClearValue(int index = 0)
public override void ClearValue(int index = -1, bool closeDropdown = true)
{
_isSetPicker = false;
var array = CurrentValue as Array;
if (!IsNullable && DefaultValue != null)
int[] indexToClear = index == -1 ? new[] { 0, 1 } : new[] { index };
string[] pickerHolders = new string[2];
(pickerHolders[0], pickerHolders[1]) = DatePickerPlaceholder.GetRangePlaceHolderByType(_pickerStatus[0]._initPicker, Locale);
foreach (var i in indexToClear)
{
var defaults = DefaultValue as Array;
array.SetValue(defaults.GetValue(0), 0);
array.SetValue(defaults.GetValue(1), 1);
}
else
{
array.SetValue(default, 0);
array.SetValue(default, 1);
}
(string first, string second) = DatePickerPlaceholder.GetRangePlaceHolderByType(_pickerStatus[0]._initPicker, Locale);
_placeholders[0] = first;
_placeholders[1] = second;
_pickerStatus[0]._hadSelectValue = false;
_pickerStatus[1]._hadSelectValue = false;
Close();
}
private async Task OnInputClick(int index)
{
//Reset Picker to default in case the picker value was changed
//but no value was selected (for example when a user clicks next
//month but does not select any value)
if (UseDefaultPickerValue[index] && DefaultPickerValue != null)
{
PickerValues[index] = _pickerValuesAfterInit[index];
}
await _dropDown.Show();
// clear status
_pickerStatus[0]._currentShowHadSelectValue = false;
_pickerStatus[1]._currentShowHadSelectValue = false;
if (index == 0)
{
// change start picker value
if (!_inputStart.IsOnFocused && _pickerStatus[index]._hadSelectValue && !UseDefaultPickerValue[index])
if (!IsNullable && DefaultValue != null)
{
GetIfNotNull(Value, index, notNullValue =>
{
ChangePickerValue(notNullValue, index);
});
var defaults = DefaultValue as Array;
array.SetValue(defaults.GetValue(i), i);
}
ChangeFocusTarget(true, false);
}
else
{
// change end picker value
if (!_inputEnd.IsOnFocused && _pickerStatus[index]._hadSelectValue && !UseDefaultPickerValue[index])
else
{
GetIfNotNull(Value, index, notNullValue =>
{
ChangePickerValue(notNullValue, index);
});
array.SetValue(default, i);
}
ChangeFocusTarget(false, true);
_placeholders[i] = pickerHolders[i];
_pickerStatus[i]._hadSelectValue = false;
}
if (closeDropdown)
Close();
}
private void GetIfNotNull(TValue value, int index, Action<DateTime> notNullAction)
@ -427,5 +534,11 @@ namespace AntDesign
return false;
}
private void OverlayVisibleChange(bool visible)
{
OnOpenChange.InvokeAsync(visible);
_openingOverlay = false;
}
}
}

View File

@ -1,12 +1,13 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using AntDesign.core.Extensions;
using AntDesign.Datepicker.Locale;
using AntDesign.Internal;
using AntDesign.JsInterop;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using OneOf;
namespace AntDesign
@ -95,13 +96,19 @@ namespace AntDesign
}
[Parameter]
public CultureInfo CultureInfo
public override CultureInfo CultureInfo
{
get { return _cultureInfo; }
get
{
return base.CultureInfo;
}
set
{
_cultureInfo = value;
_isCultureSetOutside = true;
if (!_isLocaleSetOutside && base.CultureInfo != value && base.CultureInfo.Name != value.Name)
{
_locale = LocaleProvider.GetLocale(value.Name).DatePicker;
}
base.CultureInfo = value;
}
}
@ -109,6 +116,7 @@ namespace AntDesign
public string ShowTimeFormat { get; protected set; } = "HH:mm:ss";
protected OneOf<bool, string> _showTime = null;
private bool _timeFormatProvided;
[Parameter]
public OneOf<bool, string> ShowTime
{
@ -123,6 +131,7 @@ namespace AntDesign
}, strValue =>
{
IsShowTime = true;
_timeFormatProvided = true;
ShowTimeFormat = strValue;
});
}
@ -236,10 +245,10 @@ namespace AntDesign
protected Stack<string> _prePickerStack = new Stack<string>();
protected bool _isClose = true;
protected bool _needRefresh;
private bool _isCultureSetOutside;
protected bool _duringManualInput;
private bool _isLocaleSetOutside;
private CultureInfo _cultureInfo = LocaleProvider.CurrentLocale.CurrentCulture;
private DatePickerLocale _locale = LocaleProvider.CurrentLocale.DatePicker;
protected bool _openingOverlay;
protected ClassMapper _panelClassMapper = new ClassMapper();
@ -286,26 +295,31 @@ namespace AntDesign
{
await base.OnAfterRenderAsync(firstRender);
if (firstRender)
{
await Js.InvokeVoidAsync(JSInteropConstants.AddPreventKeys, _inputStart.Ref, new[] { "ArrowUp", "ArrowDown" });
}
if (_needRefresh && IsRange)
{
if (_inputStart.IsOnFocused)
{
Element element = await JsInvokeAsync<Element>(JSInteropConstants.GetDomInfo, _inputStart.Ref);
_activeBarStyle = $"width: {element.clientWidth - 10}px; position: absolute; transform: translate3d(0px, 0px, 0px);";
HtmlElement element = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, _inputStart.Ref);
_activeBarStyle = $"width: {element.ClientWidth - 10}px; position: absolute; transform: translate3d(0px, 0px, 0px);";
_rangeArrowStyle = $"left: 12px";
}
else if (_inputEnd.IsOnFocused)
{
Element element = await JsInvokeAsync<Element>(JSInteropConstants.GetDomInfo, _inputEnd.Ref);
int translateDistance = element.clientWidth + 16;
HtmlElement element = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, _inputEnd.Ref);
int translateDistance = element.ClientWidth + 16;
if (RTL)
{
translateDistance = -translateDistance;
}
_activeBarStyle = $"width: {element.clientWidth - 10}px; position: absolute; transform: translate3d({translateDistance}px, 0px, 0px);";
_rangeArrowStyle = $"left: {element.clientWidth + 30}px";
_activeBarStyle = $"width: {element.ClientWidth - 10}px; position: absolute; transform: translate3d({translateDistance}px, 0px, 0px);";
_rangeArrowStyle = $"left: {element.ClientWidth + 30}px";
}
else
{
@ -318,10 +332,18 @@ namespace AntDesign
_needRefresh = false;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
_ = InvokeAsync(async () =>
{
await Js.InvokeVoidAsync(JSInteropConstants.RemovePreventKeys, _inputStart.Ref);
});
}
protected string GetInputValue(int index = 0)
{
DateTime? tryGetValue = GetIndexValue(index);
if (tryGetValue == null)
{
return "";
@ -338,21 +360,16 @@ namespace AntDesign
{
return;
}
_duringManualInput = false;
_needRefresh = true;
_inputStart.IsOnFocused = inputStartFocus;
_inputEnd.IsOnFocused = inputEndFocus;
}
protected async Task OnSelect(DateTime date)
protected virtual async Task OnSelect(DateTime date)
{
int index = 0;
// change focused picker
if (IsRange && _inputEnd.IsOnFocused)
{
index = 1;
}
int index = GetOnFocusPickerIndex();
_duringManualInput = false;
// InitPicker is the finally value
if (_picker == _pickerStatus[index]._initPicker)
@ -373,6 +390,10 @@ namespace AntDesign
await Focus(0);
}
}
else
{
await Focus(index);
}
}
else
{
@ -382,11 +403,12 @@ namespace AntDesign
ChangePickerValue(date, index);
}
protected virtual async Task OnBlur(int index)
{
}
protected void InitPicker(string picker)
{
if (_isCultureSetOutside && !_isLocaleSetOutside)
Locale = LocaleProvider.GetLocale(_cultureInfo.Name).DatePicker;
if (string.IsNullOrEmpty(_pickerStatus[0]._initPicker))
{
_pickerStatus[0]._initPicker = picker;
@ -431,6 +453,7 @@ namespace AntDesign
public void Close()
{
_duringManualInput = false;
_dropDown?.Hide();
}
@ -458,7 +481,7 @@ namespace AntDesign
public async Task Blur(int index = 0)
{
DatePickerInput input = null;
_duringManualInput = false;
if (index == 0)
{
input = _inputStart;
@ -528,25 +551,60 @@ namespace AntDesign
StateHasChanged();
}
private string _internalFormat;
private string InternalFormat
{
get
{
if (string.IsNullOrEmpty(_internalFormat))
{
if (!string.IsNullOrEmpty(Format))
_internalFormat = Format;
else
_internalFormat = _pickerStatus[0]._initPicker switch
{
DatePickerType.Date => GetTimeFormat(),
DatePickerType.Month => Locale.Lang.YearMonthFormat,
DatePickerType.Year => Locale.Lang.YearFormat,
DatePickerType.Time => Locale.Lang.TimeFormat,
DatePickerType.Week => $"{Locale.Lang.YearFormat}-0{Locale.Lang.Week}",
DatePickerType.Quarter => $"{Locale.Lang.YearFormat}-Q0",
_ => Locale.Lang.DateFormat,
};
}
return _internalFormat;
}
}
private string GetTimeFormat()
{
if (IsShowTime)
{
if (_timeFormatProvided)
{
return $"{Locale.Lang.DateFormat} {ShowTimeFormat}";
}
return Locale.Lang.DateTimeFormat;
}
return Locale.Lang.DateFormat;
}
private FormatAnalyzer _formatAnalyzer;
public FormatAnalyzer FormatAnalyzer => _formatAnalyzer ??= new(InternalFormat, Picker, Locale);
public string GetFormatValue(DateTime value, int index)
{
if (!string.IsNullOrEmpty(Format))
{
return value.ToString(Format, CultureInfo);
}
string formater = _pickerStatus[index]._initPicker switch
{
DatePickerType.Date => IsShowTime ? $"yyyy-MM-dd {ShowTimeFormat}" : "yyyy-MM-dd",
DatePickerType.Week => $"{value.Year}-{DateHelper.GetWeekOfYear(value)}{Locale.Lang.Week}",
DatePickerType.Month => "yyyy-MM",
DatePickerType.Quarter => $"{value.Year}-{DateHelper.GetDayOfQuarter(value)}",
DatePickerType.Year => "yyyy",
DatePickerType.Time => "HH:mm:dd",
_ => "yyyy-MM-dd",
};
return value.ToString(formater, CultureInfo);
string format;
if (string.IsNullOrEmpty(Format))
format = _pickerStatus[index]._initPicker switch
{
DatePickerType.Week => $"{Locale.Lang.YearFormat}-{DateHelper.GetWeekOfYear(value, Locale.FirstDayOfWeek)}{Locale.Lang.Week}",
DatePickerType.Quarter => $"{Locale.Lang.YearFormat}-{DateHelper.GetDayOfQuarter(value)}",
_ => InternalFormat,
};
else
format = InternalFormat;
return value.ToString(format, CultureInfo.InvariantCulture);
}
/// <summary>
@ -615,7 +673,7 @@ namespace AntDesign
{
}
public virtual void ClearValue(int index = 0)
public virtual void ClearValue(int index = 0, bool closeDropdown = true)
{
}
@ -656,21 +714,5 @@ namespace AntDesign
}
return orderedValue;
}
protected bool IsDateStringFullDate(string possibleDate)
{
if (possibleDate is null)
return false;
char? separator = possibleDate.FirstOrDefault(c => !char.IsDigit(c));
if (separator is not null)
{
var dateParts = possibleDate.Split(separator.Value);
if (dateParts.Length >= 3 && dateParts[2].Length >= 2 && char.IsDigit(dateParts[2][0]))
{
return true;
}
}
return false;
}
}
}

View File

@ -5,7 +5,7 @@
@{
var calendar = CultureInfo.Calendar;
DateTime monthFirstDayDate = new DateTime(PickerValue.Year, PickerValue.Month, 1, 0, 0, 0);
DateTime monthFirstDayDate = new DateTime(PickerValue.Year, PickerValue.Month, 1, 0, 0, 0);
int monthFirstDayOfWeek = (int)calendar.GetDayOfWeek(monthFirstDayDate);
// sunday should be 7
@ -15,7 +15,7 @@
}
int diffDay = (int) Locale.FirstDayOfWeek - monthFirstDayOfWeek;
DateTime startDate = monthFirstDayDate.AddDays(diffDay);
DateTime startDate = monthFirstDayDate.AddDays(diffDay);
}
<div class='@($"{PrefixCls}-panel")' @ref="Ref">
@ -53,14 +53,14 @@
}
</tr>
</RenderTableHeader>
<RenderFisrtCol>
<RenderFirstCol>
@if (IsWeek)
{
{
<td class="@(PrefixCls)-cell @(PrefixCls)-cell-weak">
@DateHelper.GetWeekOfYear(context)
@DateHelper.GetWeekOfYear(context, Locale.FirstDayOfWeek)
</td>
}
</RenderFisrtCol>
</RenderFirstCol>
<RenderColValue Context="currentColDate">
@currentColDate.Day
</RenderColValue>
@ -96,7 +96,7 @@
private string GetRowClass(DateTime viewDate)
{
string selectedRowClass = IsWeek && DateHelper.IsSameWeak(viewDate, Value) ? $"{PrefixCls}-week-panel-row-selected" : "";
string selectedRowClass = IsWeek && DateHelper.IsSameWeak(viewDate, Value, Locale.FirstDayOfWeek) ? $"{PrefixCls}-week-panel-row-selected" : "";
string rowClass = IsWeek ? $"{PrefixCls}-week-panel-row" : "";
return $"{rowClass} {selectedRowClass}";

View File

@ -63,13 +63,26 @@
}
DatePickerDisabledTime disabledTime = GetDisabledTime();
bool isValueNull = Value is null;
Func<int, int?, string> selected;
string localValue;
if (Value is null)
{
localValue = "";
selected = (_, _) => "";
}
else
{
localValue = Value.Value.ToString(timeFormat);
selected = (viewTime, valueTime) => viewTime == valueTime ? $"{PrefixCls}-time-panel-cell-selected" : "";
}
}
<div class="@(PrefixCls)-time-panel">
@if(Picker == DatePickerType.Date)
{
<div class="@(PrefixCls)-header">
<div class="@(PrefixCls)-header-view">
@Value.ToString(timeFormat) <br />
@localValue <br />
</div>
</div>
}
@ -82,7 +95,7 @@
{
var viewTime = startTime;
bool disabled = disabledTime._disabledHours.Contains(hour);
string isSelectedCls = viewTime.Hour == Value.Hour ? $"{PrefixCls}-time-panel-cell-selected" : "";
string isSelectedCls = selected(viewTime.Hour, Value?.Hour);
string disabledCls = disabled ? $"{PrefixCls}-time-panel-cell-disabled" : "";
<li class="@(PrefixCls)-time-panel-cell @isSelectedCls @disabledCls">
@ -103,7 +116,7 @@
{
var viewTime = startTime;
bool disabled = disabledTime._disabledMinutes.Contains(minute);
string isSelectedCls = viewTime.Minute == Value.Minute ? $"{PrefixCls}-time-panel-cell-selected" : "";
string isSelectedCls = selected(viewTime.Minute, Value?.Minute);
string disabledCls = disabled ? $"{PrefixCls}-time-panel-cell-disabled" : "";
<li class="@(PrefixCls)-time-panel-cell @isSelectedCls @disabledCls">
@ -124,7 +137,7 @@
{
var viewTime = startTime;
bool disabled = disabledTime._disabledSeconds.Contains(second);
string isSelectedCls = viewTime.Second == Value.Second ? $"{PrefixCls}-time-panel-cell-selected" : "";
string isSelectedCls = selected(viewTime.Second, Value?.Second);
string disabledCls = disabled ? $"{PrefixCls}-time-panel-cell-disabled" : "";
<li class="@(PrefixCls)-time-panel-cell @isSelectedCls @disabledCls">

View File

@ -35,21 +35,23 @@ namespace AntDesign.Internal
List<int> disabledHours = new List<int>();
List<int> disabledMinutes = new List<int>();
List<int> disabledSeconds = new List<int>();
if (DisabledHours != null)
DatePickerDisabledTime userDisabledTime = null;
if (Value is not null)
{
disabledHours.AddRange(DisabledHours(Value));
if (DisabledHours is not null)
{
disabledHours.AddRange(DisabledHours(Value.Value));
}
if (DisabledMinutes is not null)
{
disabledMinutes.AddRange(DisabledMinutes(Value.Value));
}
if (DisabledSeconds is not null)
{
disabledSeconds.AddRange(DisabledSeconds(Value.Value));
}
userDisabledTime = DisabledTime?.Invoke(Value ?? DateTime.Now);
}
if (DisabledMinutes != null)
{
disabledMinutes.AddRange(DisabledMinutes(Value));
}
if (DisabledSeconds != null)
{
disabledSeconds.AddRange(DisabledSeconds(Value));
}
DatePickerDisabledTime userDisabledTime = DisabledTime?.Invoke(Value);
if (userDisabledTime != null)
{

View File

@ -5,20 +5,22 @@
{
<div class="@(PrefixCls)-input">
<input @ref="Ref"
@onclick="OnClick"
@onkeyup="OnKeyUp"
@oninput="OnInput"
@onfocus="Onfocus"
@bind-value="@Value"
disabled="@Disabled"
placeholder="@Placeholder"
readonly="@ReadOnly">
@onclick="OnClick"
@onkeyup="OnKeyUp"
@onkeydown="@OnKeyDown"
@oninput="OnInput"
@onfocus="Onfocus"
@onblur="OnBlur"
@bind-value="@Value"
disabled="@Disabled"
placeholder="@Placeholder"
readonly="@ReadOnly">
@if (ShowSuffixIcon)
{
@if (ShowClear())
{
<span class="@(PrefixCls)-clear" onclick="@OnClickClear">
<Icon type="close-circle" theme="fill"/>
<Icon type="close-circle" theme="fill" />
</span>
}
<span class="@(PrefixCls)-suffix">
@ -29,11 +31,11 @@
}
else if (ShowTime)
{
<Icon type="clock-circle"/>
<Icon type="clock-circle" />
}
else
{
<Icon type="calendar"/>
<Icon type="calendar" />
}
</span>
@ -44,21 +46,23 @@ else
{
<div class="@(PrefixCls)-input">
<input @ref="Ref"
@onclick="OnClick"
@onkeyup="OnKeyUp"
@oninput="OnInput"
@onfocus="Onfocus"
@bind-value="@Value"
disabled="@Disabled"
placeholder="@Placeholder"
readonly="@ReadOnly">
@onclick="OnClick"
@onkeyup="OnKeyUp"
@onkeydown="@OnKeyDown"
@oninput="OnInput"
@onfocus="Onfocus"
@onblur="OnBlur"
@bind-value="@Value"
disabled="@Disabled"
placeholder="@Placeholder"
readonly="@ReadOnly">
</div>
@if (ShowSuffixIcon)
{
@if (ShowClear())
{
<span class="@(PrefixCls)-clear" onclick="@OnClickClear">
<Icon type="close-circle" theme="fill"/>
<Icon type="close-circle" theme="fill" />
</span>
}
<span class="@(PrefixCls)-suffix">
@ -69,11 +73,11 @@ else
}
else if (ShowTime)
{
<Icon type="clock-circle"/>
<Icon type="clock-circle" />
}
else
{
<Icon type="calendar"/>
<Icon type="calendar" />
}
</span>
}
@ -120,12 +124,18 @@ else
[Parameter]
public EventCallback Onfocus { get; set; }
[Parameter]
public EventCallback OnBlur { get; set; }
[Parameter]
public EventCallback Onfocusout { get; set; }
[Parameter]
public EventCallback<KeyboardEventArgs> OnKeyUp { get; set; }
[Parameter]
public EventCallback<KeyboardEventArgs> OnKeyDown { get; set; }
[Parameter]
public EventCallback<ChangeEventArgs> OnInput { get; set; }
@ -137,7 +147,8 @@ else
public bool IsOnFocused { get; set; } = false;
public bool ShowClear() {
public bool ShowClear()
{
return !Disabled && !String.IsNullOrEmpty(Value) && AllowClear;
}
}

View File

@ -203,7 +203,7 @@ namespace AntDesign
protected DateTime PickerValue { get => GetIndexPickerValue(PickerIndex); }
protected DateTime Value { get => GetIndexValue(PickerIndex) ?? DateTime.Now; }
protected DateTime? Value { get => GetIndexValue(PickerIndex); }
public void PopUpPicker(string type)
{

View File

@ -14,9 +14,11 @@
@RenderTableHeader
</thead>
<tbody>
@{
@{
var startDate = ViewStartDate;
bool shouldStopRender = false;
DateTime monthFirstDayDate = new DateTime(PickerValue.Year, PickerValue.Month, 1, 0, 0, 0);
bool firstWeekNotInMonth = ViewStartDate.AddDays(7).Equals(monthFirstDayDate);
}
@for (int row = 1; row <= MaxRow; row++)
{
@ -24,13 +26,13 @@
{
break;
}
var startColDate = startDate;
<tr class="@(GetRowClass?.Invoke(startColDate))"
@onclick="e => OnRowSelect?.Invoke(startColDate)">
@onclick="e => OnRowSelect?.Invoke(startColDate.CompareTo(monthFirstDayDate) < 0 && !firstWeekNotInMonth ? monthFirstDayDate: startColDate)">
@RenderFirstCol?.Invoke(startColDate.CompareTo(monthFirstDayDate) < 0 && !firstWeekNotInMonth ? monthFirstDayDate: startColDate)
@RenderFisrtCol?.Invoke(startColDate)
@for (int col = 1; col <= MaxCol; col++)
{

View File

@ -17,7 +17,7 @@ namespace AntDesign.Internal
public RenderFragment RenderTableHeader { get; set; }
[Parameter]
public RenderFragment<DateTime> RenderFisrtCol { get; set; }
public RenderFragment<DateTime> RenderFirstCol { get; set; }
[Parameter]
public RenderFragment<DateTime> RenderColValue { get; set; }

View File

@ -4,7 +4,7 @@ namespace AntDesign
{
public class DatePickerLocale
{
public DayOfWeek FirstDayOfWeek { get; set; }
public DayOfWeek FirstDayOfWeek { get; set; } = DayOfWeek.Sunday;
public DateLocale Lang { get; set; } = new DateLocale();
@ -12,7 +12,7 @@ namespace AntDesign
}
public class DateLocale
{
{
public string Placeholder { get; set; } = "Select date";
public string YearPlaceholder { get; set; } = "Select year";
public string QuarterPlaceholder { get; set; } = "Select quarter";
@ -37,8 +37,10 @@ namespace AntDesign
public string YearSelect { get; set; } = "Choose a year";
public string DecadeSelect { get; set; } = "Choose a decade";
public string MonthFormat { get; set; } = "MMM";
public string YearMonthFormat { get; set; } = "yyyy-MM";
public string DateFormat { get; set; } = "yyyy-MM-dd";
public string DayFormat { get; set; } = "D";
public string TimeFormat { get; set; } = "HH:mm:ss";
public string DateTimeFormat { get; set; } = "yyyy-MM-dd HH:mm:ss";
public bool MonthBeforeYear { get; set; } = true;
public string PreviousMonth { get; set; } = "Previous month (PageUp)";

View File

@ -0,0 +1,357 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using AntDesign.core.Extensions;
using Microsoft.AspNetCore.Components;
namespace AntDesign.Datepicker.Locale
{
public class FormatAnalyzer
{
private int _yearLength;
private int _monthLength;
private int _dayLength;
private int _hourLength;
private int _minuteLength;
private int _secondLength;
private int _formatLength;
private List<string> _separators = new();
private List<DateTimePartialType> _partialsOrder = new();
private Dictionary<DateTimePartialType, int> _parsedMap;
private readonly string _analyzerType;
private bool _hasPrefix;
private int _startPosition;
private int _separatorPrefixOffset;
private readonly DatePickerLocale _locale;
public enum DateTimePartialType
{
Nothing,
Second,
Minute,
Hour,
Day,
Month,
Year
}
public FormatAnalyzer(string format, string analyzerType, DatePickerLocale locale)
{
_formatLength = format.Length;
_analyzerType = analyzerType;
_locale = locale;
//Quarter and Week have individual appoaches, so no need to analyze format
//if (!(_analyzerType == DatePickerType.Quarter || _analyzerType == DatePickerType.Week))
AnalyzeFormat(format);
_format = format;
}
private void AnalyzeFormat(string format)
{
_parsedMap = new();
bool? inDate = null;
bool isLastSeparator = false;
int partialOrder = 0;
for (var i = 0; i < format.Length; i++)
{
if (format[i].IsIn('d', 'M', 'y'))
{
inDate = true;
isLastSeparator = false;
_ = format[i] switch
{
'y' => Increment(ref _yearLength, ref partialOrder, DateTimePartialType.Year),
'M' => Increment(ref _monthLength, ref partialOrder, DateTimePartialType.Month),
'd' => Increment(ref _dayLength, ref partialOrder, DateTimePartialType.Day),
_ => throw new ArgumentException("Character not covered")
};
}
else if (format[i].IsIn('h', 'H', 'm', 's'))
{
inDate = false;
isLastSeparator = false;
_ = format[i] switch
{
'h' => Increment(ref _hourLength, ref partialOrder, DateTimePartialType.Hour),
'H' => Increment(ref _hourLength, ref partialOrder, DateTimePartialType.Hour),
'm' => Increment(ref _minuteLength, ref partialOrder, DateTimePartialType.Minute),
's' => Increment(ref _secondLength, ref partialOrder, DateTimePartialType.Second),
_ => throw new ArgumentException("Character not covered")
};
}
else //separators
{
if (!isLastSeparator)
{
_separators.Add(format[i].ToString());
if (inDate is null)
_hasPrefix = true;
}
else
{
_separators[_separators.Count-1] += format[i];
}
isLastSeparator = true;
}
}
if (_hasPrefix)
{
_startPosition = _separators[0].Length;
_separatorPrefixOffset = _hasPrefix ? 1 : 0;
}
}
private bool Increment(ref int lengthValue, ref int partialOrder, DateTimePartialType partialType)
{
if (lengthValue == 0)
{
_parsedMap.Add(partialType, 0);
_partialsOrder.Add(partialType);
partialOrder++;
}
lengthValue++;
return true;
}
public bool IsFullString(string forEvaluation)
{
if (forEvaluation.Length < _formatLength)
return false;
int startPosition = _startPosition, endingPosition, parsed;
for (int i = 0; i < _partialsOrder.Count; i++)
{
if (i < (_separators.Count - _separatorPrefixOffset))
endingPosition = forEvaluation.IndexOf(_separators[i + _separatorPrefixOffset], startPosition);
else
endingPosition = forEvaluation.Length;
//handles situation when separator was removed from date
if (endingPosition < 0)
return false;
string partial = forEvaluation.Substring(startPosition, endingPosition - startPosition);
(int minLen, int maxLen) borders = _partialsOrder[i] switch
{
DateTimePartialType.Year => (minLen: _yearLength, maxLen: 4),
DateTimePartialType.Month => (minLen: _monthLength, maxLen: 2),
DateTimePartialType.Day => (minLen: _dayLength, maxLen: 2),
DateTimePartialType.Hour => (minLen: _hourLength, maxLen: 2),
DateTimePartialType.Minute => (minLen: _minuteLength, maxLen: 2),
DateTimePartialType.Second => (minLen: _secondLength, maxLen: 2),
_ => throw new ArgumentException("Partial not covered")
};
//check width of the partial
if (!(borders.minLen <= partial.Length && partial.Length <= borders.maxLen))
return false;
//check if partial is pars-able and grater than 0
if (int.TryParse(partial, out parsed))
{
if ((parsed <= 0 && _partialsOrder[i] >= DateTimePartialType.Day)
|| (parsed < 0 && _partialsOrder[i] < DateTimePartialType.Day))
return false;
}
else
{
return false;
}
//check if all characters in partial are digits (exclude for example partial = "201 ")
if (partial.Count(c => char.IsDigit(c)) != partial.Length)
return false;
if (endingPosition < forEvaluation.Length)
startPosition = endingPosition + _separators[i + _separatorPrefixOffset].Length;
_parsedMap[_partialsOrder[i]] = parsed;
}
return true;
}
public (bool, DateTime) TryParseQuarterString(string forEvaluation,
string separator = "-", string quarterPrefix = "Q")
{
var arr = forEvaluation.Split(separator);
if (arr.Length != 2)
return (false, default);
if (!ExtractYearPartial(forEvaluation, arr[0], out int year))
return (false, default);
if (!arr[1].StartsWith(quarterPrefix.ToUpper()) && !arr[1].StartsWith(quarterPrefix.ToLower()))
return (false, default);
string quarterAsString = arr[1].Substring(quarterPrefix.Length).Trim();
if (quarterAsString.Length == 1
&& int.TryParse(quarterAsString, out int quarter)
&& quarter > 0 && quarter <= 4)
{
//pick first day/month of the quarter
return (true, new DateTime(year, quarter * 3 - 2, 1));
}
return (false, default);
}
public (bool, DateTime) TryParseWeekString(string forEvaluation, string separator = "-")
{
var arr = forEvaluation.Split(separator);
if (arr.Length != 2)
return (false, default);
if (!ExtractYearPartial(forEvaluation, arr[0], out int year))
return (false, default);
if (!arr[1].EndsWith(_locale.Lang.Week))
return (false, default);
string weekAsString = arr[1].Substring(0, arr[1].Length - _locale.Lang.Week.Length).Trim();
if (!(weekAsString.Length > 0 && weekAsString.Length <= 2
&& int.TryParse(weekAsString, out int week)
&& week > 0 && week < 55))
return (false, default);
//pick first day of the week
var resultDate = new DateTime(year, 1, 1).AddDays(week * 7 - 7);
if (week > 1)
{
int mondayOffset = (7 + (resultDate.DayOfWeek - DayOfWeek.Monday)) % 7;
resultDate = resultDate.AddDays(-1 * mondayOffset);
}
//cover scenario of 54 weeks when most of times years do not have 54 weeks
if (resultDate.Year == year)
return (true, resultDate);
return (false, default);
}
private bool ExtractYearPartial(string forEvaluation, string partial, out int year)
{
year = 0;
if (!(partial.Length >= _locale.Lang.YearFormat.Length))
return false;
int startPosition = _startPosition, endingPosition;
endingPosition = forEvaluation.IndexOf(_separators[_separatorPrefixOffset][0], startPosition);
string yearPartial = partial.Substring(startPosition, endingPosition - startPosition).Trim();
if (!(_yearLength <= yearPartial.Length && yearPartial.Length <= 4))
return false;
if (!int.TryParse(yearPartial, out year))
return false;
return true;
}
Func<string, (bool, DateTime)> _converter;
private readonly string _format;
private Func<string, (bool, DateTime)> Converter
{
get
{
if (_converter is null)
{
switch (_analyzerType)
{
case DatePickerType.Year:
_converter = (pickerString) => TryParseYear(pickerString);
break;
case DatePickerType.Quarter:
_converter = (pickerString) => TryParseQuarterString(pickerString);
break;
case DatePickerType.Week:
_converter = (pickerString) => TryParseWeekString(pickerString);
break;
default:
_converter = (pickerString) => TryParseDate(pickerString);
break;
}
}
return _converter;
}
}
public bool TryPickerStringConvert<TValue>(string pickerString, out TValue changeValue, bool isDateTypeNullable)
{
var resultTuple = Converter(pickerString);
if (resultTuple.Item1)
{
return GetParsedValue(out changeValue, resultTuple.Item2, isDateTypeNullable);
}
changeValue = default;
return false;
}
private (bool, DateTime) TryParseDate(string pickerString)
{
if (IsFullString(pickerString))
{
return (TryBuildDateFromPartials(out DateTime result), result);
}
return (false, default);
}
private bool TryBuildDateFromPartials(out DateTime result)
{
result = default;
int year = 1, month = 1, day = 1, hour = 0, minute = 0, second = 0;
foreach (var item in _parsedMap)
{
switch (item.Key)
{
case DateTimePartialType.Year:
if (item.Value < 0)
return false;
year = item.Value;
break;
case DateTimePartialType.Month:
if (item.Value < 1 || item.Value > 12)
return false;
month = item.Value;
break;
case DateTimePartialType.Day:
if (item.Value < 1 || item.Value > 31)
return false;
day = item.Value;
break;
case DateTimePartialType.Hour:
if (item.Value < 0 || item.Value > 23)
return false;
hour = item.Value;
break;
case DateTimePartialType.Minute:
if (item.Value < 0 || item.Value > 59)
return false;
minute = item.Value;
break;
case DateTimePartialType.Second:
if (item.Value < 0 || item.Value > 59)
return false;
second = item.Value;
break;
}
}
if (DateTime.DaysInMonth(year, month) < day)
return false;
result = new DateTime(year, month, day, hour, minute, second);
return true;
}
public (bool, DateTime) TryParseYear(string pickerString)
{
if (IsFullString(pickerString))
{
return (true, new DateTime(_parsedMap[DateTimePartialType.Year], 1, 1));
}
return (false, default);
}
private bool GetParsedValue<TValue>(out TValue changeValue, DateTime foundDate, bool isDateTypeNullable)
{
if (isDateTypeNullable)
changeValue = DataConvertionExtensions.Convert<DateTime?, TValue>(new DateTime?(foundDate));
else
changeValue = DataConvertionExtensions.Convert<DateTime, TValue>(foundDate);
return true;
}
}
}

View File

@ -176,8 +176,8 @@ namespace AntDesign
}
else
{
Element element = await JsInvokeAsync<Element>(JSInteropConstants.GetDomInfo, Ref);
var breakpointTuple = _descriptionsResponsiveMap.FirstOrDefault(x => x.PixelWidth > element.clientWidth);
HtmlElement element = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, Ref);
var breakpointTuple = _descriptionsResponsiveMap.FirstOrDefault(x => x.PixelWidth > element.ClientWidth);
var bp = breakpointTuple == default ? BreakpointEnum.xxl : breakpointTuple.Breakpoint;
_realColumn = Column.AsT1.ContainsKey(bp.ToString()) ? Column.AsT1[bp.ToString()] : _defaultColumnMap[bp.ToString()];
}

View File

@ -202,14 +202,14 @@ namespace AntDesign
{
if (control.FieldIdentifier.Model == null)
{
throw new InvalidOperationException($"Please use @bind-Value in the control with generic type `{typeof(TValue)}`.");
throw new InvalidOperationException($"Please use @bind-Value (or @bind-Values for selected components) in the control with generic type `{typeof(TValue)}`.");
}
this._control = control;
CurrentEditContext.OnValidationStateChanged += (s, e) =>
{
control.ValidationMessages = CurrentEditContext.GetValidationMessages(control.FieldIdentifier).ToArray();
control.ValidationMessages = CurrentEditContext.GetValidationMessages(control.FieldIdentifier).Distinct().ToArray();
this._isValid = !control.ValidationMessages.Any();
StateHasChanged();
@ -223,7 +223,10 @@ namespace AntDesign
builder.CloseComponent();
};
_propertyReflector = PropertyReflector.Create(control.ValueExpression);
if (control.ValueExpression is not null)
_propertyReflector = PropertyReflector.Create(control.ValueExpression);
else
_propertyReflector = PropertyReflector.Create(control.ValuesExpression);
if (_propertyReflector.RequiredAttribute != null)
{

View File

@ -4,10 +4,10 @@
<div class="@ClassMapper.Class" style="@Style" id="@Id">
<div class="ant-input-number-handler-wrap">
<span unselectable="unselectable" role="button" aria-label="Increase Value" class="@GetIconClass("up")" @onmousedown="IncreaseDown" @onmouseup="IncreaseUp">
<span unselectable="unselectable" role="button" aria-label="Increase Value" class="@GetIconClass("up")" @onmousedown="IncreaseDown" @onmouseup="IncreaseUp" @onmouseout="IncreaseUp">
<Icon Class="ant-input-number-handler-up-inner" Type="up" />
</span>
<span unselectable="unselectable" role="button" aria-label="Decrease Value" class="@GetIconClass("down")" @onmousedown="DecreaseDown" @onmouseup="DecreaseUp">
<span unselectable="unselectable" role="button" aria-label="Decrease Value" class="@GetIconClass("down")" @onmousedown="DecreaseDown" @onmouseup="DecreaseUp" @onmouseout="DecreaseUp">
<Icon Class="ant-input-number-handler-down-inner" Type="down" />
</span>
</div>

View File

@ -68,13 +68,14 @@ namespace AntDesign
private readonly bool _isNullable;
private readonly Func<TValue, TValue, TValue> _increaseFunc;
private readonly Func<TValue, TValue, TValue> _decreaseFunc;
private readonly Func<TValue, TValue, bool> _greaterThanFunc;
private readonly Func<TValue, TValue, bool> _equalToFunc;
private readonly Func<TValue, string, string> _toStringFunc;
private Func<TValue, string, string> _toStringFunc;
private readonly Func<TValue, int, TValue> _roundFunc;
private readonly Func<string, TValue, TValue> _parseFunc;
private Func<string, TValue, TValue> _parseFunc;
private static readonly Type _surfaceType = typeof(TValue);
@ -159,14 +160,7 @@ namespace AntDesign
throw new NotSupportedException("InputNumber supports only numeric types.");
}
// 数字解析
ParameterExpression input = Expression.Parameter(typeof(string), "input");
ParameterExpression defaultValue = Expression.Parameter(typeof(TValue), "defaultValue");
MethodCallExpression inputParse = Expression.Call(null, typeof(InputNumberMath).GetMethod(nameof(InputNumberMath.Parse), new Type[] { typeof(string), typeof(TValue) }), input, defaultValue);
var lambdaParse = Expression.Lambda<Func<string, TValue, TValue>>(inputParse, input, defaultValue);
_parseFunc = lambdaParse.Compile();
//递增与递减
//递增与递减 Increment and decrement
ParameterExpression piValue = Expression.Parameter(_surfaceType, "value");
ParameterExpression piStep = Expression.Parameter(_surfaceType, "step");
Expression<Func<TValue, TValue, TValue>> fexpAdd;
@ -184,7 +178,7 @@ namespace AntDesign
_increaseFunc = fexpAdd.Compile();
_decreaseFunc = fexpSubtract.Compile();
//数字比较
//数字比较 Digital comparison
ParameterExpression piLeft = Expression.Parameter(_surfaceType, "left");
ParameterExpression piRight = Expression.Parameter(_surfaceType, "right");
var fexpGreaterThan = Expression.Lambda<Func<TValue, TValue, bool>>(Expression.GreaterThan(piLeft, piRight), piLeft, piRight);
@ -192,19 +186,7 @@ namespace AntDesign
var fexpEqualTo = Expression.Lambda<Func<TValue, TValue, bool>>(Expression.Equal(piLeft, piRight), piLeft, piRight);
_equalToFunc = fexpEqualTo.Compile();
//格式化
ParameterExpression format = Expression.Parameter(typeof(string), "format");
ParameterExpression value = Expression.Parameter(_surfaceType, "value");
Expression expValue;
if (_isNullable)
expValue = Expression.Property(value, "Value");
else
expValue = value;
MethodCallExpression expToString = Expression.Call(expValue, expValue.Type.GetMethod("ToString", new Type[] { typeof(string), typeof(IFormatProvider) }), format, Expression.Constant(CultureInfo.InvariantCulture));
var lambdaToString = Expression.Lambda<Func<TValue, string, string>>(expToString, value, format);
_toStringFunc = lambdaToString.Compile();
//四舍五入
//四舍五入 rounding
if (_floatTypes.Contains(_surfaceType))
{
ParameterExpression num = Expression.Parameter(_surfaceType, "num");
@ -223,6 +205,27 @@ namespace AntDesign
protected override void OnInitialized()
{
base.OnInitialized();
// 数字解析 Digital analysis
ParameterExpression input = Expression.Parameter(typeof(string), "input");
ParameterExpression defaultValue = Expression.Parameter(typeof(TValue), "defaultValue");
MethodCallExpression inputParse = Expression.Call(null, typeof(InputNumberMath).GetMethod(nameof(InputNumberMath.Parse), new Type[] { typeof(string), typeof(TValue), typeof(CultureInfo) }), input, defaultValue, Expression.Constant(CultureInfo));
var lambdaParse = Expression.Lambda<Func<string, TValue, TValue>>(inputParse, input, defaultValue);
_parseFunc = lambdaParse.Compile();
//格式化 format
ParameterExpression format = Expression.Parameter(typeof(string), "format");
ParameterExpression value = Expression.Parameter(_surfaceType, "value");
Expression expValue;
if (_isNullable)
expValue = Expression.Property(value, "Value");
else
expValue = value;
MethodCallExpression expToString = Expression.Call(expValue, expValue.Type.GetMethod("ToString", new Type[] { typeof(string), typeof(IFormatProvider) }), format, Expression.Constant(CultureInfo));
var lambdaToString = Expression.Lambda<Func<TValue, string, string>>(expToString, value, format);
_toStringFunc = lambdaToString.Compile();
SetClass();
CurrentValue = Value ?? DefaultValue;
}
@ -237,7 +240,7 @@ namespace AntDesign
protected override bool TryParseValueFromString(string value, out TValue result, out string validationErrorMessage)
{
validationErrorMessage = null;
if (!Regex.IsMatch(value, @"^[+-]?\d*[.]?\d*$"))
if (!Regex.IsMatch(value, @"^[+-]?\d*[.,]?\d*$"))
{
result = Value;
return true;
@ -413,9 +416,9 @@ namespace AntDesign
return;
}
_inputString = Parser != null ? Parser(_inputString) : Regex.Replace(_inputString, @"[^\+\-\d.\d]", "");
if (!CurrentValueAsString.Equals(_inputString))
CurrentValueAsString = _inputString;
await ConvertNumberAsync(_inputString);
_inputString = null;
}

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace AntDesign
@ -86,114 +87,114 @@ namespace AntDesign
#endregion
#region Parse
public static sbyte Parse(string input, sbyte defaultValue)
public static sbyte Parse(string input, sbyte defaultValue, CultureInfo culture)
{
return sbyte.TryParse(input, out var number) ? number : defaultValue;
return sbyte.TryParse(input, NumberStyles.Any, culture, out var number) ? number : defaultValue;
}
public static sbyte? Parse(string input, sbyte? defaultValue)
public static sbyte? Parse(string input, sbyte? defaultValue, CultureInfo culture)
{
return sbyte.TryParse(input, out var number) ? number : defaultValue;
return sbyte.TryParse(input, NumberStyles.Any, culture, out var number) ? number : defaultValue;
}
public static byte Parse(string input, byte defaultValue)
public static byte Parse(string input, byte defaultValue, CultureInfo culture)
{
return byte.TryParse(input, out var number) ? number : defaultValue;
return byte.TryParse(input, NumberStyles.Any, culture, out var number) ? number : defaultValue;
}
public static byte? Parse(string input, byte? defaultValue)
public static byte? Parse(string input, byte? defaultValue, CultureInfo culture)
{
return byte.TryParse(input, out var number) ? number : defaultValue;
return byte.TryParse(input, NumberStyles.Any, culture, out var number) ? number : defaultValue;
}
public static short Parse(string input, short defaultValue)
public static short Parse(string input, short defaultValue, CultureInfo culture)
{
return short.TryParse(input, out var number) ? number : defaultValue;
return short.TryParse(input, NumberStyles.Any, culture, out var number) ? number : defaultValue;
}
public static short? Parse(string input, short? defaultValue)
public static short? Parse(string input, short? defaultValue, CultureInfo culture)
{
return short.TryParse(input, out var number) ? number : defaultValue;
return short.TryParse(input, NumberStyles.Any, culture, out var number) ? number : defaultValue;
}
public static ushort Parse(string input, ushort defaultValue)
public static ushort Parse(string input, ushort defaultValue, CultureInfo culture)
{
return ushort.TryParse(input, out var number) ? number : defaultValue;
return ushort.TryParse(input, NumberStyles.Any, culture, out var number) ? number : defaultValue;
}
public static ushort? Parse(string input, ushort? defaultValue)
public static ushort? Parse(string input, ushort? defaultValue, CultureInfo culture)
{
return ushort.TryParse(input, out var number) ? number : defaultValue;
return ushort.TryParse(input, NumberStyles.Any, culture, out var number) ? number : defaultValue;
}
public static int Parse(string input, int defaultValue)
public static int Parse(string input, int defaultValue, CultureInfo culture)
{
return int.TryParse(input, out var number) ? number : defaultValue;
return int.TryParse(input, NumberStyles.Any, culture, out var number) ? number : defaultValue;
}
public static int? Parse(string input, int? defaultValue)
public static int? Parse(string input, int? defaultValue, CultureInfo culture)
{
return int.TryParse(input, out var number) ? number : defaultValue;
return int.TryParse(input, NumberStyles.Any, culture, out var number) ? number : defaultValue;
}
public static uint Parse(string input, uint defaultValue)
public static uint Parse(string input, uint defaultValue, CultureInfo culture)
{
return uint.TryParse(input, out var number) ? number : defaultValue;
return uint.TryParse(input, NumberStyles.Any, culture, out var number) ? number : defaultValue;
}
public static uint? Parse(string input, uint? defaultValue)
public static uint? Parse(string input, uint? defaultValue, CultureInfo culture)
{
return uint.TryParse(input, out var number) ? number : defaultValue;
return uint.TryParse(input, NumberStyles.Any, culture, out var number) ? number : defaultValue;
}
public static long Parse(string input, long defaultValue)
public static long Parse(string input, long defaultValue, CultureInfo culture)
{
return long.TryParse(input, out var number) ? number : defaultValue;
return long.TryParse(input, NumberStyles.Any, culture, out var number) ? number : defaultValue;
}
public static long? Parse(string input, long? defaultValue)
public static long? Parse(string input, long? defaultValue, CultureInfo culture)
{
return long.TryParse(input, out var number) ? number : defaultValue;
return long.TryParse(input, NumberStyles.Any, culture, out var number) ? number : defaultValue;
}
public static ulong Parse(string input, ulong defaultValue)
public static ulong Parse(string input, ulong defaultValue, CultureInfo culture)
{
return ulong.TryParse(input, out var number) ? number : defaultValue;
return ulong.TryParse(input, NumberStyles.Any, culture, out var number) ? number : defaultValue;
}
public static ulong? Parse(string input, ulong? defaultValue)
public static ulong? Parse(string input, ulong? defaultValue, CultureInfo culture)
{
return ulong.TryParse(input, out var number) ? number : defaultValue;
return ulong.TryParse(input, NumberStyles.Any, culture, out var number) ? number : defaultValue;
}
public static float Parse(string input, float defaultValue)
public static float Parse(string input, float defaultValue, CultureInfo culture)
{
return float.TryParse(input, out var number) ? number : defaultValue;
return float.TryParse(input, NumberStyles.Any, culture, out var number) ? number : defaultValue;
}
public static float? Parse(string input, float? defaultValue)
public static float? Parse(string input, float? defaultValue, CultureInfo culture)
{
return float.TryParse(input, out var number) ? number : defaultValue;
return float.TryParse(input, NumberStyles.Any, culture, out var number) ? number : defaultValue;
}
public static double Parse(string input, double defaultValue)
public static double Parse(string input, double defaultValue, CultureInfo culture)
{
return double.TryParse(input, out var number) ? number : defaultValue;
return double.TryParse(input, NumberStyles.Any, culture, out var number) ? number : defaultValue;
}
public static double? Parse(string input, double? defaultValue)
public static double? Parse(string input, double? defaultValue, CultureInfo culture)
{
return double.TryParse(input, out var number) ? number : defaultValue;
return double.TryParse(input, NumberStyles.Any, culture, out var number) ? number : defaultValue;
}
public static decimal Parse(string input, decimal defaultValue)
public static decimal Parse(string input, decimal defaultValue, CultureInfo culture)
{
return decimal.TryParse(input, out var number) ? number : defaultValue;
return decimal.TryParse(input, NumberStyles.Any, culture, out var number) ? number : defaultValue;
}
public static decimal? Parse(string input, decimal? defaultValue)
public static decimal? Parse(string input, decimal? defaultValue, CultureInfo culture)
{
return decimal.TryParse(input, out var number) ? number : defaultValue;
return decimal.TryParse(input, NumberStyles.Any, culture, out var number) ? number : defaultValue;
}
}
#endregion

View File

@ -22,6 +22,8 @@ namespace AntDesign
private bool _hasAffixWrapper;
protected string GroupWrapperClass { get; set; } = $"{PrefixCls}-group-wrapper";
protected virtual string InputType => "input";
//protected string ClearIconClass { get; set; }
protected static readonly EventCallbackFactory CallbackFactory = new EventCallbackFactory();
@ -48,7 +50,8 @@ namespace AntDesign
public bool AutoFocus
{
get { return _autoFocus; }
set {
set
{
_autoFocus = value;
if (!_isInitialized && _autoFocus)
IsFocused = _autoFocus;
@ -204,11 +207,14 @@ namespace AntDesign
protected async Task OnKeyPressAsync(KeyboardEventArgs args)
{
if (args != null && args.Key == "Enter" && EnableOnPressEnter)
if (args?.Key == "Enter" && InputType != "textarea")
{
await ChangeValue(true);
await OnPressEnter.InvokeAsync(args);
await OnPressEnterAsync();
if (EnableOnPressEnter)
{
await OnPressEnter.InvokeAsync(args);
await OnPressEnterAsync();
}
}
}

View File

@ -1,4 +1,7 @@
using Microsoft.AspNetCore.Components;
using System;
using AntDesign.Core.Extensions;
using AntDesign.JsInterop;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
namespace AntDesign
@ -40,7 +43,18 @@ namespace AntDesign
builder.OpenComponent<Icon>(i++);
builder.AddAttribute(i++, "class", $"{PrefixCls}-password-icon");
builder.AddAttribute(i++, "type", _eyeIcon);
builder.AddAttribute(i++, "onclick", CallbackFactory.Create(this, ToggleVisibility));
builder.AddAttribute(i++, "onclick", CallbackFactory.Create<MouseEventArgs>(this, async args =>
{
var element = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, Ref);
IsFocused = true;
await this.FocusAsync(Ref);
ToggleVisibility(args);
if (element.SelectionStart != 0)
await Js.SetSelectionStartAsync(Ref, element.SelectionStart);
}));
builder.CloseComponent();
builder.CloseElement();
});

View File

@ -12,6 +12,8 @@ namespace AntDesign
{
private const uint DEFAULT_MIN_ROWS = 1;
protected override string InputType => "textarea";
/// <summary>
/// scrollHeight of 1 row
/// </summary>
@ -211,4 +213,4 @@ namespace AntDesign
public double BorderBottom { get; set; }
}
}
}
}

View File

@ -94,6 +94,7 @@
},
"timePickerLocale": { "placeholder": "Select time" }
},
"global": { "placeholder": "Please select" },
"Table": {
"filterTitle": "Filter menu",
"filterConfirm": "OK",

View File

@ -108,6 +108,7 @@
"placeholder": "Wybierz godzinę"
}
},
"global": { "placeholder": "Proszę wybrać" },
"Table": {
"filterTitle": "Menu filtra",
"filterConfirm": "OK",

View File

@ -39,6 +39,7 @@ else
TriggerClass="@ClassMapper.Class"
Visible="IsOpen"
ComplexAutoCloseAndVisible="true"
BoundaryAdjustMode="@TriggerBoundaryAdjustMode.None"
Disabled="Disabled"
Placement="Placement"
OnVisibleChange="OnOverlayVisibleChange"
@ -74,11 +75,6 @@ else
<style>
.ant-menu-vertical.ant-menu-sub,
.ant-menu-vertical.ant-menu-sub:not(.zoom-big-enter-active):not(.zoom-big-leave-active), .ant-menu-vertical-left.ant-menu-sub:not(.zoom-big-enter-active):not(.zoom-big-leave-active), .ant-menu-vertical-right.ant-menu-sub:not(.zoom-big-enter-active):not(.zoom-big-leave-active) {
overflow: initial;
}
.ant-menu.ant-menu-sub.ant-menu-vertical.ant-menu-submenu-popup {
top: 0;
left: 100%;

View File

@ -144,7 +144,7 @@ namespace AntDesign
if (RootMenu.InternalMode != MenuMode.Inline && _overlayTrigger != null && IsOpen)
{
var domInfo = await _overlayTrigger.GetTriggerDomInfo();
_popupMinWidthStyle = $"min-width: {domInfo.clientWidth}px";
_popupMinWidthStyle = $"min-width: {domInfo.ClientWidth}px";
}
await base.OnAfterRenderAsync(firstRender);

View File

@ -28,6 +28,11 @@ namespace AntDesign
/// </summary>
public Func<Task> AfterClose { get; set; } = () => Task.CompletedTask;
/// <summary>
/// ant-modal style
/// </summary>
public string Style { get; set; }
/// <summary>
/// ant-modal-body style
/// </summary>

View File

@ -142,7 +142,7 @@ namespace AntDesign
{
if (Config.MaskClosable && _dialogMouseDown)
{
await Task.Delay(50);
await Task.Delay(4);
_dialogMouseDown = false;
}
}
@ -395,5 +395,16 @@ namespace AntDesign
await base.OnAfterRenderAsync(isFirst);
}
#endregion
protected override void Dispose(bool disposing)
{
// enable body scroll
if (_disableBodyScroll)
{
_disableBodyScroll = false;
_ = Task.Delay(250);
_ = JsInvokeAsync(JSInteropConstants.EnableBodyScroll);
}
}
}
}

View File

@ -150,5 +150,11 @@ namespace AntDesign
_hasAdd = cancel.Cancel;
await InvokeStateHasChangedAsync();
}
protected override void Dispose(bool disposing)
{
_ = _dialog?.TryResetModalStyle();
base.Dispose(disposing);
}
}
}

View File

@ -10,6 +10,7 @@
<Modal @key="@options"
ModalRef ="@modalRef"
AfterClose="@options.AfterClose"
Style="@options.Style"
BodyStyle="@options.BodyStyle"
CancelText="@options.CancelText"
Centered="@options.Centered"
@ -44,6 +45,7 @@
<Modal @key="@options"
ModalRef ="@modalRef"
AfterClose="@options.AfterClose"
Style="@options.Style"
BodyStyle="@options.BodyStyle"
CancelText="@options.CancelText"
Centered="@options.Centered"

View File

@ -29,7 +29,6 @@ namespace AntDesign
private async Task HandleOk(MouseEventArgs e)
{
Console.WriteLine("HandleOk");
var onOk = ModalProps.OnOk;
if (onOk != null)
{

View File

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Text.Json;
using System.Threading.Tasks;
using AntDesign.Core.Helpers.MemberPath;
@ -229,10 +230,12 @@ namespace AntDesign
SelectedOptionItems.Clear();
Value = default;
var sameObject = object.ReferenceEquals(_datasource, value);
_datasource = value;
OnDataSourceChanged?.Invoke();
if (!sameObject)
OnDataSourceChanged?.Invoke();
return;
}
@ -325,6 +328,10 @@ namespace AntDesign
_ = OnValuesChangeAsync(default);
}
if (_isNotifyFieldChanged && (Form?.ValidateOnChange == true))
{
EditContext?.NotifyFieldChanged(FieldIdentifier);
}
}
}
@ -374,7 +381,7 @@ namespace AntDesign
/// <returns>true if SelectOptions has any selected Items, otherwise false</returns>
internal bool HasValue
{
get => SelectOptionItems.Where(x => x.IsSelected).Any() || (AddedTags?.Any() ?? false);
get => SelectedOptionItems.Any() || (AddedTags?.Any() ?? false);
}
/// <summary>
@ -425,6 +432,7 @@ namespace AntDesign
private bool _defaultActiveFirstOptionApplied;
private bool _waittingStateChange;
private bool _isPrimitive;
private bool _isValueEnum;
internal ElementReference _inputRef;
protected OverlayTrigger _dropDown;
protected SelectContent<TItemValue, TItem> _selectContent;
@ -478,6 +486,7 @@ namespace AntDesign
private Action<TItem, TItemValue> _setValue;
private bool _disableSubmitFormOnEnter;
private bool _showArrowIcon = true;
private Expression<Func<TItemValue>> _valueExpression;
#endregion Properties
@ -510,6 +519,7 @@ namespace AntDesign
if (!_isInitialized)
{
_isPrimitive = IsSimpleType(typeof(TItem));
_isValueEnum = typeof(TItemValue).IsEnum;
if (!_showArrowIconChanged && SelectMode != SelectMode.Default)
_showArrowIcon = SuffixIcon != null;
}
@ -799,7 +809,7 @@ namespace AntDesign
{
string maxWidth = "", minWidth = "", definedWidth = "";
var domRect = await JsInvokeAsync<DomRect>(JSInteropConstants.GetBoundingClientRect, Ref);
var width = domRect.width.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture);
var width = domRect.Width.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture);
minWidth = $"min-width: {width}px;";
if (DropdownMatchSelectWidth.IsT0 && DropdownMatchSelectWidth.AsT0)
{
@ -897,7 +907,6 @@ namespace AntDesign
protected internal async Task SetValueAsync(SelectOptionItem<TItemValue, TItem> selectOption)
{
if (selectOption == null) throw new ArgumentNullException(nameof(selectOption));
if (SelectMode == SelectMode.Default)
{
if (SelectedOptionItems.Count > 0)
@ -1301,7 +1310,7 @@ namespace AntDesign
if (!_isInitialized) // This is important because otherwise the initial value is overwritten by the EventCallback of ValueChanged and would be NULL.
return;
if (EqualityComparer<TItemValue>.Default.Equals(value, default))
if (!_isValueEnum && EqualityComparer<TItemValue>.Default.Equals(value, default))
{
_ = InvokeAsync(() => OnInputClearClickAsync(new()));
return;
@ -2227,4 +2236,4 @@ namespace AntDesign
#endregion Events
}
}
}

View File

@ -187,7 +187,7 @@ namespace AntDesign
IsDisabled = Disabled,
GroupName = _groupName,
Value = Value,
Item = THelper.ChangeType<TItem>(Value),
Item = THelper.ChangeType<TItem>(Value, CultureInfo.CurrentCulture),
ChildComponent = this
};

View File

@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using AntDesign.Core.Extensions;
using AntDesign.Core.JsInterop.ObservableApi;
using AntDesign.JsInterop;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
@ -114,14 +114,14 @@ namespace AntDesign.Select.Internal
if (_prefixRef.Id != default)
{
_prefixElement = await Js.InvokeAsync<DomRect>(JSInteropConstants.GetBoundingClientRect, _prefixRef);
_prefixElement.width += ItemMargin;
_prefixElement.Width += ItemMargin;
}
if (_suffixRef.Id != default)
{
_suffixElement = await Js.InvokeAsync<DomRect>(JSInteropConstants.GetBoundingClientRect, _suffixRef);
_suffixElement.width += 7;
_suffixElement.Width += 7;
}
DomEventService.AddEventListener("window", "resize", OnWindowResize, false);
await DomEventService.AddResizeObserver(_overflow, OnOveralyResize);
await CalculateResponsiveTags();
}
DomEventService.AddEventListener(ParentSelect._inputRef, "focusout", OnBlurInternal, true);
@ -136,20 +136,23 @@ namespace AntDesign.Select.Internal
await base.OnAfterRenderAsync(firstRender);
}
protected async void OnWindowResize(JsonElement element)
protected async void OnOveralyResize(List<ResizeObserverEntry> entries)
{
await CalculateResponsiveTags();
await CalculateResponsiveTags(false, entries[0].ContentRect);
}
internal async Task CalculateResponsiveTags(bool forceInputFocus = false)
internal async Task CalculateResponsiveTags(bool forceInputFocus = false, DomRect entry = null)
{
if (!ParentSelect.IsResponsive)
return;
_overflowElement = await Js.InvokeAsync<DomRect>(JSInteropConstants.GetBoundingClientRect, _overflow);
if (entry is null)
_overflowElement = await Js.InvokeAsync<DomRect>(JSInteropConstants.GetBoundingClientRect, _overflow);
else
_overflowElement = entry;
//distance between items is margin-inline-left=4px
decimal accumulatedWidth = _prefixElement.width + _suffixElement.width + (4 + (SearchValue?.Length ?? 0) * 8);
decimal accumulatedWidth = _prefixElement.Width + _suffixElement.Width + (4 + (SearchValue?.Length ?? 0) * 8);
int i = 0;
bool overflowing = false;
bool renderAgain = false;
@ -158,15 +161,15 @@ namespace AntDesign.Select.Internal
if (item.Width == 0)
{
var itemElement = await Js.InvokeAsync<DomRect>(JSInteropConstants.GetBoundingClientRect, item.SelectedTagRef);
item.Width = itemElement.width;
item.Width = itemElement.Width;
}
if (!overflowing)
{
if (accumulatedWidth + item.Width > _overflowElement.width)
if (accumulatedWidth + item.Width > _overflowElement.Width)
{
//current item will overflow; check if with aggregateTag will overflow
if (accumulatedWidth + _aggregateTagElement.width > _overflowElement.width)
if (accumulatedWidth + _aggregateTagElement.Width > _overflowElement.Width)
{
if (_calculatedMaxCount != Math.Max(0, i - 1))
{
@ -391,6 +394,8 @@ namespace AntDesign.Select.Internal
_ = InvokeAsync(async () =>
{
await Task.Delay(100);
if (ParentSelect.IsResponsive)
await DomEventService.DisposeResizeObserver(_overflow);
await Js.InvokeVoidAsync(JSInteropConstants.RemovePreventKeys, ParentSelect._inputRef);
await Js.InvokeVoidAsync(JSInteropConstants.RemovePreventEnterOnOverlayVisible, ParentSelect._inputRef);
});
@ -398,8 +403,6 @@ namespace AntDesign.Select.Internal
DomEventService.RemoveEventListerner<JsonElement>(ParentSelect._inputRef, "focus", OnFocusInternal);
DomEventService.RemoveEventListerner<JsonElement>(ParentSelect._inputRef, "focusout", OnBlurInternal);
DomEventService.RemoveEventListerner<JsonElement>("window", "beforeunload", Reloading);
if (ParentSelect.IsResponsive)
DomEventService.RemoveEventListerner<JsonElement>("window", "resize", OnWindowResize);
if (IsDisposed) return;

View File

@ -15,9 +15,9 @@ namespace AntDesign
public partial class Slider<TValue> : AntInputComponentBase<TValue>
{
private const string PreFixCls = "ant-slider";
private Element _sliderDom;
private Element _leftHandleDom;
private Element _rightHandleDom;
private HtmlElement _sliderDom;
private HtmlElement _leftHandleDom;
private HtmlElement _rightHandleDom;
private ElementReference _leftHandle;
private ElementReference _rightHandle;
private string _leftHandleStyle = "left: 0%; right: auto; transform: translateX(-50%);";
@ -629,15 +629,15 @@ namespace AntDesign
private async Task CalculateValueAsync(double clickClient)
{
_sliderDom = await JsInvokeAsync<Element>(JSInteropConstants.GetDomInfo, Ref);
double sliderOffset = (double)(Vertical ? _sliderDom.absoluteTop : _sliderDom.absoluteLeft);
double sliderLength = (double)(Vertical ? _sliderDom.clientHeight : _sliderDom.clientWidth);
_sliderDom = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, Ref);
double sliderOffset = (double)(Vertical ? _sliderDom.AbsoluteTop : _sliderDom.AbsoluteLeft);
double sliderLength = (double)(Vertical ? _sliderDom.ClientHeight : _sliderDom.ClientWidth);
double handleNewPosition;
if (_right)
{
if (_rightHandleDom == null)
{
_rightHandleDom = await JsInvokeAsync<Element>(JSInteropConstants.GetDomInfo, _rightHandle);
_rightHandleDom = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, _rightHandle);
}
if (Reverse)
{
@ -680,11 +680,11 @@ namespace AntDesign
{
if (_leftHandleDom == null)
{
_leftHandleDom = await JsInvokeAsync<Element>(JSInteropConstants.GetDomInfo, _leftHandle);
_leftHandleDom = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, _leftHandle);
}
if (_rightHandleDom == null)
{
_rightHandleDom = await JsInvokeAsync<Element>(JSInteropConstants.GetDomInfo, _rightHandle);
_rightHandleDom = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, _rightHandle);
}
if (Reverse)
{

View File

@ -1,11 +1,12 @@
@namespace AntDesign
@inherits AntInputComponentBase<bool>
@inherits AntInputBoolComponentBase
<button type="button" role="switch"
aria-checked="@(CurrentValue?"true":"false")"
disabled="@(Disabled||Loading)"
class="@ClassMapper.Class"
style="@Style"
autofocus=@AutoFocus
ant-click-animating="@(_clickAnimating?"true":"false")"
@onmouseover="HandleMouseOver"
@onmouseout="HandleMouseOut"

View File

@ -1,35 +1,30 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using OneOf;
namespace AntDesign
{
public partial class Switch : AntInputComponentBase<bool>
public partial class Switch : AntInputBoolComponentBase
{
protected string _prefixCls = "ant-switch";
[Parameter]
public bool Checked { get; set; }
[Parameter]
public bool Disabled { get; set; }
[Parameter]
public bool Loading { get; set; }
[Parameter]
public bool Control { get; set; }
[Parameter]
public EventCallback<bool> OnChange { get; set; }
[Parameter]
public string CheckedChildren { get; set; } = string.Empty;
[Parameter]
public RenderFragment CheckedChildrenTemplate { get; set; }
/// <summary>
/// The status of Switch is completely up to the user and no longer
/// automatically changes the data based on the click event.
/// </summary>
[Parameter]
public bool Control { get; set; }
[Parameter]
public EventCallback OnClick { get; set; }
[Parameter]
public string UnCheckedChildren { get; set; } = string.Empty;
@ -38,40 +33,37 @@ namespace AntDesign
private bool _clickAnimating = false;
private string _prefixCls = "ant-switch";
protected override void OnInitialized()
{
base.OnInitialized();
SetClass();
}
this.CurrentValue = this.CurrentValue ? this.CurrentValue : this.Checked;
protected void SetClass()
{
ClassMapper.Clear()
.Add(_prefixCls)
.If($"{_prefixCls}-checked", () => CurrentValue)
.If($"{_prefixCls}-disabled", () => Disabled || Loading)
.If($"{_prefixCls}-loading", () => Loading)
.If($"{_prefixCls}-small", () => Size == "small")
.If($"{_prefixCls}-rtl", () => RTL)
.If($"{_prefixCls}-rtl", () => RTL);
;
}
private void HandleClick(MouseEventArgs e)
private async Task HandleClick(MouseEventArgs e)
{
if (!Disabled && !Loading && !Control)
{
this.CurrentValue = !CurrentValue;
this.OnChange.InvokeAsync(CurrentValue);
}
if (!Control)
await base.ChangeValue(!CurrentValue);
if (OnClick.HasDelegate)
await OnClick.InvokeAsync(null);
}
private void HandleMouseOver(MouseEventArgs e)
{
_clickAnimating = true;
}
private void HandleMouseOver(MouseEventArgs e) => _clickAnimating = true;
private void HandleMouseOut(MouseEventArgs e) => _clickAnimating = false;
private void HandleMouseOut(MouseEventArgs e)
{
_clickAnimating = false;
}
}
}

View File

@ -121,7 +121,7 @@ else if (IsBody && RowSpan != 0 && ColSpan != 0)
@code
{
string HeaderTitle => Title ?? DisplayName ?? FieldName ?? DataIndex;
string HeaderTitle => Title ?? DisplayName ?? FieldName;
readonly RenderFragment<Column<TData>> SortHeader = col =>
@<div class="ant-table-column-sorters">
@ -174,6 +174,14 @@ readonly RenderFragment<Column<TData>> FilterToolTipSorter = col =>
{
@col.ToolTipSorter(col)
}
else if (@col.TitleTemplate != null)
{
@col.TitleTemplate
}
else
{
@col.HeaderTitle
}
</span>
<Dropdown Trigger="new[] { TriggerType.Click }" Visible="col._filterOpened" Placement="PlacementType.BottomRight" TriggerReference="col._filterTriggerRef" @attributes="@(new Dictionary<string, object>() { ["OnMaskClick"] = _callbackFactory.Create(col, () => { if (col._filterOpened) col.FilterConfirm(true); }) })">
@ -231,7 +239,7 @@ readonly RenderFragment<Column<TData>> FilterToolTipSorter = col =>
}
<Space Style="margin:10px">
<SpaceItem>
<Select Value="filter.FilterCompareOperator" TItemValue="TableFilterCompareOperator" TItem="TableFilterCompareOperator" Style="overflow:visible" @attributes="@(new Dictionary<string, object>() { ["ValueChanged"] = _callbackFactory.Create<TableFilterCompareOperator>(col, value => col.SetFilterCompareOperator(filter,value)) })" PopupContainerSelector="@("#popupSelForOperator" + col.Id)">
<Select Value="filter.FilterCompareOperator" TItemValue="TableFilterCompareOperator" TItem="TableFilterCompareOperator" Style="overflow:visible" @attributes="@(new Dictionary<string, object>() { ["ValueChanged"] = _callbackFactory.Create<TableFilterCompareOperator>(col, value => col.SetFilterCompareOperator(filter,value)) })" PopupContainerSelector="@("#popupSelForOperator" + col.Id)" DropdownMatchSelectWidth="false">
<SelectOptions>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.Equals" Label="@col.Table?.Locale.FilterOptions.Equals"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.NotEquals" Label="@col.Table?.Locale.FilterOptions.NotEquals"></SelectOption>
@ -239,7 +247,7 @@ readonly RenderFragment<Column<TData>> FilterToolTipSorter = col =>
{
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.Contains" Label="@col.Table?.Locale.FilterOptions.Contains"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.StartsWith" Label="@col.Table?.Locale.FilterOptions.StartsWith"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.EndsWidth" Label="@col.Table?.Locale.FilterOptions.EndsWith"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.EndsWith" Label="@col.Table?.Locale.FilterOptions.EndsWith"></SelectOption>
}
else if (col._columnDataType.IsEnum)
{
@ -301,7 +309,7 @@ readonly RenderFragment<Column<TData>> FilterToolTipSorter = col =>
<Button Size="small" Type="link" @attributes="@(new Dictionary<string, object>() { ["OnClick"] = _callbackFactory.Create<MouseEventArgs>(col, ()=> col.ResetFilters()) })">
@col.Table?.Locale.FilterReset
</Button>
@if (col._columnFilterType == TableFilterType.FeildType)
@if (col._columnFilterType == TableFilterType.FieldType)
{
<Button Size="small" Icon="plus" Type="primary" @attributes="@(new Dictionary<string, object>() { ["OnClick"] = _callbackFactory.Create<MouseEventArgs>(col, ()=> col.AddFilter()) })">
</Button>
@ -335,4 +343,4 @@ readonly RenderFragment<Column<TData>> FilterToolTipSorter = col =>
}
</Template>;
}
}

View File

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq.Expressions;
using System.Reflection;
using AntDesign.Core.Reflection;
@ -98,11 +99,9 @@ namespace AntDesign
private TableFilterType _columnFilterType;
private PropertyReflector? _propertyReflector;
private Type _columnDataType;
public string DisplayName { get; private set; }
public string? DisplayName { get; private set; }
public string FieldName { get; private set; }
@ -116,7 +115,14 @@ namespace AntDesign
public LambdaExpression GetFieldExpression { get; private set; }
void IFieldColumn.ClearSorter() => SetSorter(SortDirection.None);
void IFieldColumn.ClearSorter()
{
SetSorter(SortDirection.None);
if (FieldExpression == null)
{
StateHasChanged();
}
}
private static readonly EventCallbackFactory _callbackFactory = new EventCallbackFactory();
@ -138,10 +144,13 @@ namespace AntDesign
{
if (FieldExpression != null)
{
_propertyReflector = PropertyReflector.Create(FieldExpression);
if (FieldExpression.Body is not MemberExpression memberExp)
{
throw new ArgumentException("'Field' parameter must be child member");
}
var paramExp = Expression.Parameter(ItemType);
var member = ColumnExpressionHelper.GetReturnMemberInfo(FieldExpression);
var bodyExp = Expression.MakeMemberAccess(paramExp, member);
var bodyExp = Expression.MakeMemberAccess(paramExp, memberExp.Member);
GetFieldExpression = Expression.Lambda(bodyExp, paramExp);
}
else if (DataIndex != null)
@ -149,16 +158,18 @@ namespace AntDesign
(_, GetFieldExpression) = ColumnDataIndexHelper<TData>.GetDataIndexConfig(this);
}
if (Sortable && GetFieldExpression != null)
{
SortModel = new SortModel<TData>(GetFieldExpression, SorterMultiple, DefaultSortOrder, SorterCompare);
}
if (GetFieldExpression != null)
{
var member = ColumnExpressionHelper.GetReturnMemberInfo(GetFieldExpression);
DisplayName = member.GetCustomAttribute<DisplayNameAttribute>(true)?.DisplayName ?? member.Name;
FieldName = member.Name;
DisplayName = member?.GetCustomAttribute<DisplayNameAttribute>(true)?.DisplayName
?? member?.GetCustomAttribute<DisplayAttribute>(true)?.GetName()
?? member?.Name;
FieldName = DataIndex ?? member?.Name;
}
if (Sortable && GetFieldExpression != null)
{
SortModel = new SortModel<TData>(GetFieldExpression, FieldName, SorterMultiple, DefaultSortOrder, SorterCompare);
}
}
else if (IsBody)
@ -217,7 +228,7 @@ namespace AntDesign
}
else
{
_columnFilterType = TableFilterType.FeildType;
_columnFilterType = TableFilterType.FieldType;
InitFilters();
}
@ -286,7 +297,7 @@ namespace AntDesign
private void SetSorter(SortDirection sortDirection)
{
_sortDirection = sortDirection;
SortModel.SetSortDirection(sortDirection);
SortModel?.SetSortDirection(sortDirection);
}
private void ToggleTreeNode()
@ -343,7 +354,7 @@ namespace AntDesign
private void FilterSelected(TableFilter<TData> filter)
{
if (_columnFilterType == TableFilterType.FeildType) return;
if (_columnFilterType == TableFilterType.FieldType) return;
if (!FilterMultiple)
{
Filters.ForEach(x => x.Selected = false);
@ -361,9 +372,9 @@ namespace AntDesign
private void FilterConfirm(bool isReset = false)
{
_filterOpened = false;
if (!isReset && _columnFilterType == TableFilterType.FeildType) Filters?.ForEach(f => { if (!f.Selected && f.Value != null) f.Selected = true; });
if (!isReset && _columnFilterType == TableFilterType.FieldType) Filters?.ForEach(f => { if (!f.Selected && f.Value != null) f.Selected = true; });
_hasFilterSelected = Filters?.Any(x => x.Selected) == true;
FilterModel = _hasFilterSelected && _propertyReflector != null ? new FilterModel<TData>(_propertyReflector.Value.PropertyInfo, OnFilter, Filters.Where(x => x.Selected).ToList(), _columnFilterType) : null;
FilterModel = _hasFilterSelected ? new FilterModel<TData>(GetFieldExpression, FieldName, OnFilter, Filters.Where(x => x.Selected).ToList(), _columnFilterType) : null;
Table?.ReloadAndInvokeChange();
}

View File

@ -38,7 +38,7 @@ namespace AntDesign.FilterExpression
case TableFilterCompareOperator.IsNotNull:
return Expression.NotEqual(leftExpr, rightExpr);
case TableFilterCompareOperator.NotEquals:
return Expression.AndAlso(notNull, Expression.NotEqual(lowerLeftExpr, lowerRightExpr));
return Expression.AndAlso(notNull, Expression.NotEqual(lowerLeftExpr, lowerRightExpr));
default:
string methodName = Enum.GetName(typeof(TableFilterCompareOperator), compareOperator);
MethodInfo mi = typeof(string).GetMethod(methodName, new[] { typeof(string) });

View File

@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Linq.Expressions;
using System.Reflection;
@ -10,7 +9,7 @@ namespace AntDesign.Internal
{
internal static class ColumnExpressionHelper
{
internal static MemberInfo GetReturnMemberInfo(LambdaExpression expression)
internal static MemberInfo? GetReturnMemberInfo(LambdaExpression expression)
{
var accessorBody = expression.Body;
while (true)
@ -23,6 +22,14 @@ namespace AntDesign.Internal
{
accessorBody = conditionalExpression.IfTrue;
}
else if (accessorBody is MethodCallExpression methodCallExpression)
{
accessorBody = methodCallExpression.Object;
}
else if (accessorBody is BinaryExpression binaryExpression)
{
accessorBody = binaryExpression.Left;
}
else
{
break;
@ -31,7 +38,7 @@ namespace AntDesign.Internal
if (accessorBody is not MemberExpression memberExpression)
{
throw new ArgumentException($"The type of the provided expression {accessorBody.GetType().Name} is not supported, it should be {nameof(MemberExpression)}.");
return null;
}
return memberExpression.Member;

View File

@ -385,6 +385,7 @@ namespace AntDesign
_waitingReload = false;
Reload();
}
return this._shouldRender;
}
@ -392,7 +393,11 @@ namespace AntDesign
void ITable.HasFixRight() => _hasFixRight = true;
void ITable.TableLayoutIsFixed() => TableLayout = "fixed";
void ITable.TableLayoutIsFixed()
{
TableLayout = "fixed";
StateHasChanged();
}
private async void OnResize(JsonElement _) => await SetScrollPositionClassName();
@ -403,10 +408,10 @@ namespace AntDesign
if (_isReloading)
return;
var element = await JsInvokeAsync<Element>(JSInteropConstants.GetDomInfo, _tableBodyRef);
var scrollWidth = element.scrollWidth;
var scrollLeft = element.scrollLeft;
var clientWidth = element.clientWidth;
var element = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, _tableBodyRef);
var scrollWidth = element.ScrollWidth;
var scrollLeft = element.ScrollLeft;
var clientWidth = element.ClientWidth;
var beforePingLeft = _pingLeft;
var beforePingRight = _pingRight;
@ -437,7 +442,7 @@ namespace AntDesign
{
_shouldRender = true;
}
if (!clear)
{
StateHasChanged();

View File

@ -32,7 +32,7 @@ namespace AntDesign
Equals = 1,
Contains = 2,
StartsWith = 3,
EndsWidth = 4,
EndsWith = 4,
GreaterThan = 5,
LessThan = 6,
GreaterThanOrEquals = 7,
@ -53,6 +53,6 @@ namespace AntDesign
public enum TableFilterType
{
List = 1,
FeildType = 2
FieldType = 2
}
}

View File

@ -23,14 +23,14 @@ namespace AntDesign.TableModels
private readonly FilterExpressionResolver<TField> _filterExpressionResolver = new FilterExpressionResolver<TField>();
private PropertyInfo _propertyInfo;
private LambdaExpression _getFieldExpression;
private TableFilterType FilterType { get; set; } = TableFilterType.List;
public FilterModel(PropertyInfo propertyInfo, Expression<Func<TField, TField, bool>> onFilter, IList<TableFilter<TField>> filters, TableFilterType filterType)
public FilterModel(LambdaExpression getFieldExpression, string fieldName, Expression<Func<TField, TField, bool>> onFilter, IList<TableFilter<TField>> filters, TableFilterType filterType)
{
this._propertyInfo = propertyInfo;
this.FieldName = _propertyInfo.Name;
this._getFieldExpression = getFieldExpression;
this.FieldName = fieldName;
if (onFilter == null)
{
this.OnFilter = (value, field) => field.Equals(value);
@ -50,17 +50,17 @@ namespace AntDesign.TableModels
{
return source;
}
var sourceExpression = Expression.Parameter(typeof(TItem));
var propertyExpression = Expression.Property(sourceExpression, _propertyInfo);
var sourceExpression = _getFieldExpression.Parameters[0];
Expression lambda = null;
if (this.FilterType == TableFilterType.List)
{
lambda = Expression.Invoke((Expression<Func<bool>>)(() => false));
lambda = Expression.Constant(false, typeof(bool));
}
IFilterExpression filterExpression = null;
if (FilterType == TableFilterType.FeildType)
if (FilterType == TableFilterType.FieldType)
{
filterExpression = _filterExpressionResolver.GetFilterExpression();
}
@ -68,21 +68,21 @@ namespace AntDesign.TableModels
{
if (this.FilterType == TableFilterType.List)
{
lambda = Expression.OrElse(lambda, Expression.Invoke(OnFilter, Expression.Constant(filter.Value, typeof(TField)), propertyExpression));
lambda = Expression.OrElse(lambda!, Expression.Invoke(OnFilter, Expression.Constant(filter.Value, typeof(TField)), _getFieldExpression.Body));
}
else
else // TableFilterType.FieldType
{
if (filter.Value == null && (filter.FilterCompareOperator != TableFilterCompareOperator.IsNull && filter.FilterCompareOperator != TableFilterCompareOperator.IsNotNull)) continue;
Expression constantExpression = null;
if (filter.FilterCompareOperator == TableFilterCompareOperator.IsNull || filter.FilterCompareOperator == TableFilterCompareOperator.IsNotNull)
{
constantExpression = Expression.Constant(null, _propertyInfo.PropertyType);
constantExpression = Expression.Constant(null, typeof(TField));
}
else
{
constantExpression = Expression.Constant(filter.Value, typeof(TField));
}
var expression = filterExpression.GetFilterExpression(filter.FilterCompareOperator, propertyExpression, constantExpression);
var expression = filterExpression!.GetFilterExpression(filter.FilterCompareOperator, _getFieldExpression.Body, constantExpression);
if (lambda == null)
{
lambda = expression;

View File

@ -24,12 +24,11 @@ namespace AntDesign.TableModels
private LambdaExpression _getFieldExpression;
public SortModel(LambdaExpression getFieldExpression, int priority, SortDirection defaultSortOrder, Func<TField, TField, int> comparer)
public SortModel(LambdaExpression getFieldExpression, string fieldName, int priority, SortDirection defaultSortOrder, Func<TField, TField, int> comparer)
{
this.Priority = priority;
this._getFieldExpression = getFieldExpression;
var member = ColumnExpressionHelper.GetReturnMemberInfo(_getFieldExpression);
this.FieldName = member.GetCustomAttribute<DisplayAttribute>(true)?.Name ?? member.Name;
this.FieldName = fieldName;
this._comparer = comparer;
this._sortDirection = defaultSortOrder ?? SortDirection.None;
}

View File

@ -419,8 +419,8 @@ namespace AntDesign
private async Task TryRenderNavOperation()
{
int navWidth = (await JsInvokeAsync<Element>(JSInteropConstants.GetDomInfo, _tabBars)).clientWidth;
int navTotalWidth = (await JsInvokeAsync<Element>(JSInteropConstants.GetDomInfo, _scrollTabBar)).clientWidth;
int navWidth = (await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, _tabBars)).ClientWidth;
int navTotalWidth = (await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, _scrollTabBar)).ClientWidth;
if (navTotalWidth < navWidth)
{
_operationClass = "ant-tabs-nav-operations ant-tabs-nav-operations-hidden";
@ -447,29 +447,29 @@ namespace AntDesign
// TODO: slide to activated tab
// animate Active Ink
// ink bar
var element = await JsInvokeAsync<Element>(JSInteropConstants.GetDomInfo, _activePane.TabBar);
var navSection = await JsInvokeAsync<Element>(JSInteropConstants.GetDomInfo, _tabBars);
var element = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, _activePane.TabBar);
var navSection = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, _tabBars);
if (IsHorizontal)
{
//_inkStyle = "left: 0px; width: 0px;";
_inkStyle = $"left: {element.offsetLeft}px; width: {element.clientWidth}px";
if (element.offsetLeft > _scrollOffset + navSection.clientWidth
|| element.offsetLeft < _scrollOffset)
_inkStyle = $"left: {element.OffsetLeft}px; width: {element.ClientWidth}px";
if (element.OffsetLeft > _scrollOffset + navSection.ClientWidth
|| element.OffsetLeft < _scrollOffset)
{
// need to scroll tab bars
_scrollOffset = element.offsetLeft;
_scrollOffset = element.OffsetLeft;
_navStyle = $"transform: translate(-{_scrollOffset}px, 0px);";
}
}
else
{
_inkStyle = $"top: {element.offsetTop}px; height: {element.clientHeight}px;";
if (element.offsetTop > _scrollOffset + navSection.clientHeight
|| element.offsetTop < _scrollOffset)
_inkStyle = $"top: {element.OffsetTop}px; height: {element.ClientHeight}px;";
if (element.OffsetTop > _scrollOffset + navSection.ClientHeight
|| element.OffsetTop < _scrollOffset)
{
// need to scroll tab bars
_scrollOffset = element.offsetTop;
_scrollOffset = element.OffsetTop;
_navStyle = $"transform: translate(0px, -{_scrollOffset}px);";
}
}

View File

@ -3,18 +3,15 @@
@if (!_closed)
{
<span class="@ClassMapper.Class" style="@Style" id="@Id" @ref="Ref" @onclick="ClickTag">
@if (!string.IsNullOrEmpty(Icon))
{
<Icon Type="@Icon"></Icon>
}
@ChildContent
@if (Mode == "closeable" || Closable)
@if (Closable)
{
<Icon Type="close" TabIndex="-1" @onclick="CloseTag" />
}
</span>
}

View File

@ -10,12 +10,6 @@ namespace AntDesign
[Parameter]
public RenderFragment ChildContent { get; set; }
/// <summary>
/// 'default' | 'closeable' | 'checkable'
/// </summary>
[Parameter]
public string Mode { get; set; } = "default";
[Parameter]
public string Color
{
@ -33,6 +27,9 @@ namespace AntDesign
[Parameter]
public bool Closable { get; set; }
[Parameter]
public bool Checkable { get; set; }
[Parameter]
public bool Visible { get; set; } = true;
@ -89,7 +86,7 @@ namespace AntDesign
.If($"{prefix}-has-color", () => !string.IsNullOrEmpty(Color) && !_presetColor)
.If($"{prefix}-hidden", () => Visible == false)
.GetIf(() => $"{prefix}-{Color}", () => _presetColor)
.If($"{prefix}-checkable", () => Mode == "checkable")
.If($"{prefix}-checkable", () => Checkable)
.If($"{prefix}-checkable-checked", () => Checked)
.If($"{prefix}-rtl", () => RTL)
;
@ -97,9 +94,14 @@ namespace AntDesign
private async Task UpdateCheckedStatus()
{
if (Mode == "checkable")
if (!Checkable)
{
return;
}
this.Checked = !this.Checked;
if (this.CheckedChange.HasDelegate)
{
this.Checked = !this.Checked;
await this.CheckedChange.InvokeAsync(this.Checked);
}
}
@ -107,12 +109,22 @@ namespace AntDesign
private async Task CloseTag(MouseEventArgs e)
{
var closeEvent = new CloseEventArgs<MouseEventArgs>(e);
await this.OnClosing.InvokeAsync(closeEvent);
if (OnClosing.HasDelegate)
{
await this.OnClosing.InvokeAsync(closeEvent);
}
if (closeEvent.Cancel)
{
return;
}
await this.OnClose.InvokeAsync(e);
if (OnClose.HasDelegate)
{
await this.OnClose.InvokeAsync(e);
}
this._closed = true;
}

View File

@ -33,6 +33,7 @@
"@commitlint/cli": "^11.0.0",
"@commitlint/config-conventional": "^11.0.0",
"@types/es6-promise": "^3.3.0",
"@types/resize-observer-browser": "^0.1.3",
"babel-core": "^6.26.3",
"babel-preset-es2015": "^6.24.1",
"babelify": "^8.0.0",

View File

@ -16,8 +16,8 @@
@code
{
private uint _offsetTop = 10;
private uint _offsetBottom = 10;
private int _offsetTop = 10;
private int _offsetBottom = 10;
private void AddTop()
{

View File

@ -1,15 +1,3 @@
<style>
.scrollable-container{
height: 100px;
overflow-y: scroll;
}
.background {
padding-top: 60px;
height: 300px;
background-image: url("https://zos.alipayobjects.com/rmsportal/RmjwQiJorKyobvI.jpg")
}
</style>
<div class="scrollable-container" id="scrollable-container">
<div class="background">
<Affix TargetSelector="#scrollable-container">
@ -19,3 +7,16 @@
</Affix>
</div>
</div>
<style>
.scrollable-container {
height: 100px;
overflow-y: scroll;
}
.background {
padding-top: 60px;
height: 300px;
background-image: url("https://zos.alipayobjects.com/rmsportal/RmjwQiJorKyobvI.jpg")
}
</style>

View File

@ -1,13 +1,16 @@
<div>
<Cascader Options="@basicNodes"></Cascader>
<Cascader Options="@optoins" @bind-Value="value" SelectedNodesChanged="OnChange"></Cascader>
</div>
@code{
List<CascaderNode> basicNodes = GetBasicNodes();
private static List<CascaderNode> GetBasicNodes()
string value = "";
void OnChange(CascaderNode[] selectedNodes)
{
List<CascaderNode> list = new List<CascaderNode>() {
Console.WriteLine($"value: {value} selected: {string.Join(",", selectedNodes.Select(x => x.Value))}");
}
List<CascaderNode> optoins = new List<CascaderNode>() {
new CascaderNode()
{
Value = "1",
@ -31,7 +34,7 @@
new CascaderNode{ Value = "21", Label="杭州"},
new CascaderNode{ Value = "22", Label="温州"},
new CascaderNode{ Value = "23", Label="义乌"},
new CascaderNode{ Value = "23", Label="宁波"}
new CascaderNode{ Value = "24", Label="宁波"}
}
}, new CascaderNode()
{
@ -40,8 +43,8 @@
Children = new CascaderNode[] {
new CascaderNode{ Value = "31", Label="徐汇区"},
new CascaderNode{ Value = "32", Label="黄浦区"},
new CascaderNode{ Value = "32", Label="浦东新区"},
new CascaderNode{ Value = "32", Label="崇明区"}
new CascaderNode{ Value = "33", Label="浦东新区"},
new CascaderNode{ Value = "34", Label="崇明区"}
}
}, new CascaderNode()
{
@ -63,9 +66,8 @@
} },
new CascaderNode{ Value = "52", Label="苏州"},
new CascaderNode{ Value = "53", Label="无锡"},
new CascaderNode{ Value = "53", Label="扬州"}
new CascaderNode{ Value = "54", Label="扬州"}
}
}};
return list;
}
}

View File

@ -1,13 +1,15 @@
<div>
<Cascader Options="@selectNodes" ChangeOnSelect="true"></Cascader>
<Cascader Options="@options" @bind-Value="value" ChangeOnSelect="true" SelectedNodesChanged="OnChange"></Cascader>
</div>
@code {
List<CascaderNode> selectNodes = GetSelectNodes();
private static List<CascaderNode> GetSelectNodes()
string value = "112";
void OnChange(CascaderNode[] selectedNodes)
{
List<CascaderNode> list = new List<CascaderNode>() {
Console.WriteLine($"value: {value} selected: {string.Join(",", selectedNodes.Select(x => x.Value))}");
}
List<CascaderNode> options = new List<CascaderNode>() {
new CascaderNode()
{
Value = "1",
@ -31,7 +33,7 @@
new CascaderNode{ Value = "21", Label="杭州"},
new CascaderNode{ Value = "22", Label="温州"},
new CascaderNode{ Value = "23", Label="义乌"},
new CascaderNode{ Value = "23", Label="宁波"}
new CascaderNode{ Value = "24", Label="宁波"}
}
}, new CascaderNode()
{
@ -40,8 +42,8 @@
Children = new CascaderNode[] {
new CascaderNode{ Value = "31", Label="徐汇区"},
new CascaderNode{ Value = "32", Label="黄浦区"},
new CascaderNode{ Value = "32", Label="浦东新区"},
new CascaderNode{ Value = "32", Label="崇明区"}
new CascaderNode{ Value = "33", Label="浦东新区"},
new CascaderNode{ Value = "34", Label="崇明区"}
}
}, new CascaderNode()
{
@ -63,9 +65,8 @@
} },
new CascaderNode{ Value = "52", Label="苏州"},
new CascaderNode{ Value = "53", Label="无锡"},
new CascaderNode{ Value = "53", Label="扬州"}
new CascaderNode{ Value = "54", Label="扬州"}
}
}};
return list;
}
}
}

View File

@ -11,21 +11,21 @@
private static List<CascaderNode> GetDefaultNodes()
{
List<CascaderNode> list = new List<CascaderNode>()
{
{
new CascaderNode()
{
Value = "1",
Label = "湖北",
Children = new CascaderNode[]
{
{
new CascaderNode
{
Value = "11", Label = "武汉", Children = new CascaderNode[]
{
{
new CascaderNode
{
Value = "111", Label = "武昌区", Children = new CascaderNode[]
{
{
new CascaderNode {Value = "1111", Label = "黄鹤楼街道"},
new CascaderNode {Value = "1112", Label = "白沙洲街道"}
}
@ -42,11 +42,11 @@
Value = "2",
Label = "浙江",
Children = new CascaderNode[]
{
{
new CascaderNode {Value = "21", Label = "杭州"},
new CascaderNode {Value = "22", Label = "温州"},
new CascaderNode {Value = "23", Label = "义乌"},
new CascaderNode {Value = "23", Label = "宁波"}
new CascaderNode {Value = "24", Label = "宁波"}
}
},
new CascaderNode()
@ -54,11 +54,11 @@
Value = "3",
Label = "上海",
Children = new CascaderNode[]
{
{
new CascaderNode {Value = "31", Label = "徐汇区"},
new CascaderNode {Value = "32", Label = "黄浦区"},
new CascaderNode {Value = "32", Label = "浦东新区"},
new CascaderNode {Value = "32", Label = "崇明区"}
new CascaderNode {Value = "33", Label = "浦东新区"},
new CascaderNode {Value = "34", Label = "崇明区"}
}
},
new CascaderNode()
@ -66,7 +66,7 @@
Value = "4",
Label = "北京",
Children = new CascaderNode[]
{
{
new CascaderNode {Value = "41", Label = "朝阳"},
new CascaderNode {Value = "42", Label = "东城"},
new CascaderNode {Value = "43", Label = "西城"}
@ -77,22 +77,22 @@
Value = "5",
Label = "江苏",
Children = new CascaderNode[]
{
{
new CascaderNode
{
Value = "51", Label = "南京", Children = new CascaderNode[]
{
{
new CascaderNode {Value = "511", Label = "鼓楼区"},
new CascaderNode {Value = "512", Label = "玄武区"}
}
},
new CascaderNode {Value = "52", Label = "苏州"},
new CascaderNode {Value = "53", Label = "无锡"},
new CascaderNode {Value = "53", Label = "扬州"}
new CascaderNode {Value = "54", Label = "扬州"}
}
}
};
return list;
}
}
}

View File

@ -31,7 +31,7 @@
new CascaderNode{ Value = "21", Label="杭州"},
new CascaderNode{ Value = "22", Label="温州"},
new CascaderNode{ Value = "23", Label="义乌", Disabled = true },
new CascaderNode{ Value = "23", Label="宁波"}
new CascaderNode{ Value = "24", Label="宁波"}
}
}, new CascaderNode()
{
@ -41,8 +41,8 @@
Children = new CascaderNode[] {
new CascaderNode{ Value = "31", Label="徐汇区"},
new CascaderNode{ Value = "32", Label="黄浦区"},
new CascaderNode{ Value = "32", Label="浦东新区"},
new CascaderNode{ Value = "32", Label="崇明区", Disabled = true }
new CascaderNode{ Value = "33", Label="浦东新区"},
new CascaderNode{ Value = "34", Label="崇明区", Disabled = true }
}
}, new CascaderNode()
{
@ -64,9 +64,9 @@
} },
new CascaderNode{ Value = "52", Label="苏州"},
new CascaderNode{ Value = "53", Label="无锡"},
new CascaderNode{ Value = "53", Label="扬州"}
new CascaderNode{ Value = "54", Label="扬州"}
}
}};
return list;
}
}
}

View File

@ -31,7 +31,7 @@
new CascaderNode{ Value = "21", Label="杭州"},
new CascaderNode{ Value = "22", Label="温州"},
new CascaderNode{ Value = "23", Label="义乌"},
new CascaderNode{ Value = "23", Label="宁波"}
new CascaderNode{ Value = "24", Label="宁波"}
}
}, new CascaderNode()
{
@ -40,8 +40,8 @@
Children = new CascaderNode[] {
new CascaderNode{ Value = "31", Label="徐汇区"},
new CascaderNode{ Value = "32", Label="黄浦区"},
new CascaderNode{ Value = "32", Label="浦东新区"},
new CascaderNode{ Value = "32", Label="崇明区"}
new CascaderNode{ Value = "33", Label="浦东新区"},
new CascaderNode{ Value = "34", Label="崇明区"}
}
}, new CascaderNode()
{
@ -63,9 +63,9 @@
} },
new CascaderNode{ Value = "52", Label="苏州"},
new CascaderNode{ Value = "53", Label="无锡"},
new CascaderNode{ Value = "53", Label="扬州"}
new CascaderNode{ Value = "54", Label="扬州"}
}
}};
return list;
}
}
}

View File

@ -0,0 +1,55 @@
<Cascader Options="@options" Placeholder="Please select" ShowSearch />
@code {
CascaderNode[] options = new CascaderNode[]
{
new()
{
Value = "zhejiang",
Label = "Zhejiang",
Children = new CascaderNode[]
{
new()
{
Value = "hangzhou",
Label = "Hangzhou",
Children = new CascaderNode[]
{
new()
{
Value = "xihu",
Label = "West Lake",
},
new()
{
Value = "xiasha",
Label = "Xia Sha",
Disabled = true
}
}
}
}
},
new()
{
Value = "jiangsu",
Label = "Jiangsu",
Children = new CascaderNode[]
{
new()
{
Value = "nanjing",
Label = "Nanjing",
Children = new CascaderNode[]
{
new()
{
Value = "zhonghuamen",
Label = "Zhong Hua men"
}
}
}
}
}
};
}

Some files were not shown because too many files have changed in this diff Show More