mirror of
https://gitee.com/ant-design-blazor/ant-design-blazor.git
synced 2024-12-02 03:57:38 +08:00
merge 0.8.1 to feature
This commit is contained in:
commit
bfb5e4f396
@ -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`
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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>;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
@ -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);
|
||||
|
8
components/checkbox/CheckboxGroupMixedMode.cs
Normal file
8
components/checkbox/CheckboxGroupMixedMode.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace AntDesign
|
||||
{
|
||||
public enum CheckboxGroupMixedMode
|
||||
{
|
||||
ChildContentFirst,
|
||||
OptionsFirst
|
||||
};
|
||||
}
|
54
components/core/Base/AntInputBoolComponentBase.cs
Normal file
54
components/core/Base/AntInputBoolComponentBase.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
42
components/core/Component/Element/Element.cs
Normal file
42
components/core/Component/Element/Element.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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 OvelayTrigger’s 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);
|
@ -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()
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
805
components/core/Helpers/PropertyAccessHelper.cs
Normal file
805
components/core/Helpers/PropertyAccessHelper.cs
Normal 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<T>, 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<T>, 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
|
||||
}
|
||||
}
|
@ -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>()
|
||||
|
@ -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>
|
||||
|
@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AntDesign.JsInterop
|
||||
{
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
52
components/core/JsInterop/HtmlElement.cs
Normal file
52
components/core/JsInterop/HtmlElement.cs
Normal 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; }
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
13
components/core/JsInterop/ObservableApi/BoxSize.cs
Normal file
13
components/core/JsInterop/ObservableApi/BoxSize.cs
Normal 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; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
|
||||
}
|
||||
}
|
3
components/core/JsInterop/ObservableApi/observableApi.ts
Normal file
3
components/core/JsInterop/ObservableApi/observableApi.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import * as resize from './resizeObserver';
|
||||
|
||||
export { resize };
|
101
components/core/JsInterop/ObservableApi/resizeObserver.ts
Normal file
101
components/core/JsInterop/ObservableApi/resizeObserver.ts
Normal 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)
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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" />
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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}";
|
||||
|
@ -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">
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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)
|
||||
{
|
||||
|
@ -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++)
|
||||
{
|
||||
|
@ -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; }
|
||||
|
@ -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)";
|
||||
|
357
components/date-picker/locale/FormatAnalyzer.cs
Normal file
357
components/date-picker/locale/FormatAnalyzer.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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()];
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
});
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -94,6 +94,7 @@
|
||||
},
|
||||
"timePickerLocale": { "placeholder": "Select time" }
|
||||
},
|
||||
"global": { "placeholder": "Please select" },
|
||||
"Table": {
|
||||
"filterTitle": "Filter menu",
|
||||
"filterConfirm": "OK",
|
||||
|
@ -108,6 +108,7 @@
|
||||
"placeholder": "Wybierz godzinę"
|
||||
}
|
||||
},
|
||||
"global": { "placeholder": "Proszę wybrać" },
|
||||
"Table": {
|
||||
"filterTitle": "Menu filtra",
|
||||
"filterConfirm": "OK",
|
||||
|
@ -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%;
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -150,5 +150,11 @@ namespace AntDesign
|
||||
_hasAdd = cancel.Cancel;
|
||||
await InvokeStateHasChangedAsync();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_ = _dialog?.TryResetModalStyle();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -29,7 +29,6 @@ namespace AntDesign
|
||||
|
||||
private async Task HandleOk(MouseEventArgs e)
|
||||
{
|
||||
Console.WriteLine("HandleOk");
|
||||
var onOk = ModalProps.OnOk;
|
||||
if (onOk != null)
|
||||
{
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>;
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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) });
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);";
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
|
@ -16,8 +16,8 @@
|
||||
|
||||
@code
|
||||
{
|
||||
private uint _offsetTop = 10;
|
||||
private uint _offsetBottom = 10;
|
||||
private int _offsetTop = 10;
|
||||
private int _offsetBottom = 10;
|
||||
|
||||
private void AddTop()
|
||||
{
|
||||
|
@ -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>
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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
Loading…
Reference in New Issue
Block a user