Merge 0.9.4 to feature (#1942)

* fix(module: form): remove `FormItem` from `Form` when it was disposed (#1901)

* pref(module: table): put fixed column style into js (#1897)

* pref(module: table): put fixed column style into js

* fix resize listener

* docs: Add building demo assets to the Readme (#1904)

* feat(module: table): add `CellData` for `CellRender` (#1907)

* docs: add dynamic table demo (#1908)

* feat(module: table): add dynamic data demo

* add scroll x

* fix(module: select): value no longer reset on datasource set (#1906)

* fix(module:select): value no longer reset on datasource set

* fix(module:select): dataSource change detection

* fix: improve datasource detection
add item & set value in SelectOption

* tests: scenario from issue #1207

* fix: DataSourceEqualityComparer default

* docs: Fixed typo (#1915)

* fix(module: input): Add stop propagation (#1917)

* docs(module: select): update coordinate demo (#1914)

Co-authored-by: James Yeung <shunjiey@hotmail.com>

* fix(module: textarea): add rows parameter (#1920)

doc: adjust to match antD
tests: sizing tests

* test(module: select): Add some unit tests for Select (#1891)

* Add Select clear tests

* Add Select DataSource to null test

* Change the implement of Throw tests

* Change int to int?

* fix(module: button): loading icon styles (#1902)

* Fix loading icon styles

* Fix button render test

* feat(module: InputNumber): Add inputmode for mobile keyboard (#1923)

* perf: avoid memory leak issue of event listener (#1857)

* perf: avoid memory leak #1834

Avoid memory leak by remove the exclusive parameter and logic in the code block on AddEventListener method in DomEventService class.

The following are the components affected:
components/affix/Affix.razor.cs
components/anchor/Anchor.razor.cs
components/carousel/Carousel.razor.cs
components/core/Component/Overlay/Overlay.razor.cs
components/core/Component/Overlay/OverlayTrigger.razor.cs
components/core/JsInterop/DomEventService.cs
components/descriptions/Descriptions.razor.cs
components/dropdown/DropdownButton.cs
components/grid/Row.razor.cs
components/input/Input.cs
components/input/TextArea.razor.cs
components/layout/Sider.razor.cs
components/list/ListItem.razor.cs
components/select/Select.razor.cs
components/select/internal/SelectContent.razor.cs
components/slider/Slider.razor.cs
components/table/Table.razor.cs
components/tabs/Tabs.razor.cs

* fix override AddEventListener method in AntDesign.TestKit project

* add register/remove event listerner for exclusive use in DomEventService class

* move _dotNetObjects to DomEventListerner class/service, so that users not required to maintain it in each component.

* * move share/reuse dom event listerner methods to DomEventListerner class

* remove method 'AddEventListener' that no longer exists in DomEventService class in AntDesign.TestKit project

* * change the component referring to an IDomEventListerner interface instead of a concrete class,
  so that the component can be tested via a mock TestDomEventListerner.

* introduce DisposeShared and Dispose method in DomEventListerner to ease user remove callback from DomEventListerner

* register IDomEventListerner into DI container instead of create manually

* fix FormatKey

* fix FormatKey

* fix tests

* fix test

* fix test

Co-authored-by: James Yeung <shunjiey@hotmail.com>

* fix(module: textarea): replace wrong event (#1927)

* tests(module: textarea): include js function mock to avoid bunit exceptions (#1930)

* perf(module: overlay): positioning moved to js (#1848)

* fix(module:overlay): move postion calculation to js

fixes #1836

* docs: TriggerBoundaryAdjustMode explanation

* docs: select & datepicker got BoundaryAdjustmetMode

cleanup

* test: fixes

optimization & cleanup

* fix(module:overlay): recalculate overlay position when trigger resizes

* Minor clean-up

* fix(module:overlay): wait for Show to finish in Hide

* fix: prevent vertical scrollbar on overlay adding

* fix: extract waiting function

* fix: overlay not to repostion when trigger vanishes (menu issue)

* fix: scroll adjustment for position: fixed

* fix: on menu mode change, keep

* merge conflict fix

* fix: nominal calculation reset on failed adjustment

* fix: bugs

* test: exclude log method from test coverage in overlay.ts

* fix(module: table): It would be load twice at first time if pagesize is not 10 (#1933)

* fix(module: menu): collapsed menu title can't hide while use router link (#1934)

* fix(module: select): fix data source of type IEnumerable<object> (#1932)

* fix(module:select): fix data source of type IEnumerable<object>

* Update Select.razor.cs

* Update Select.OnDataSourceChangeTests.razor

Co-authored-by: James Yeung <shunjiey@hotmail.com>

* feat(module: InputNumber): Add OnFocus event (#1931)

* Add on textbox focus

* Adjust naming

Co-authored-by: James Yeung <shunjiey@hotmail.com>

* fix(module: list): resposive style doesn't work (#1937)

* docs: Update index.zh-CN.md (#1936)

Update document ConfigProvider global configuration example code

Co-authored-by: James Yeung <shunjiey@hotmail.com>

* change log 0.9.4 (#1938)

* chore: test cs project to include test runner adapter (#1939)

test: add general overlay disposal method mock

Co-authored-by: Alan.Liu <lxyruanjian@126.com>
Co-authored-by: James Yeung <shunjiey@hotmail.com>
Co-authored-by: rabberbock <rabberbock@gmail.com>
Co-authored-by: Andrzej Bakun <anddrzejb@poczta.fm>
Co-authored-by: Chandan Rauniyar <chandankkrr@gmail.com>
Co-authored-by: Luke Parker [SSW] <10430890+Hona@users.noreply.github.com>
Co-authored-by: SmallY <45689960+iamSmallY@users.noreply.github.com>
Co-authored-by: Maksim <maksalmak@gmail.com>
Co-authored-by: Tony Yip <tonyyip1969@gmail.com>
Co-authored-by: SmRiley <45205313+SmRiley@users.noreply.github.com>
This commit is contained in:
James Yeung 2021-09-14 22:00:55 +08:00 committed by GitHub
commit 0ef438cc30
173 changed files with 5883 additions and 2064 deletions

View File

@ -14,6 +14,32 @@ timeline: true
- Major version release is not included in this schedule for breaking change and new features. - Major version release is not included in this schedule for breaking change and new features.
--- ---
### 0.9.4
2021-09-12
- Table
- 🐞 Fixed an issue that initialization is refreshed twice when PageSize is not equal to 10. [#1933](https://github.com/ant-design-blazor/ant-design-blazor/pull/1933) [@ElderJames](https://github.com/ElderJames)
- 🆕 Addd CellData for CellRender. [#1907](https://github.com/ant-design-blazor/ant-design-blazor/pull/1907) [@ElderJames](https://github.com/ElderJames)
- ⚡️ Put fixed column style into js. [#1897](https://github.com/ant-design-blazor/ant-design-blazor/pull/1897) [@ElderJames](https://github.com/ElderJames)
- 📖 add dynamic table demo. [#1908](https://github.com/ant-design-blazor/ant-design-blazor/pull/1908) [@ElderJames](https://github.com/ElderJames)
- InputNumber
- 🆕 Add OnFocus event [#1931](https://github.com/ant-design-blazor/ant-design-blazor/pull/1931) [@Hona](https://github.com/Hona)
- 🐞 Fixed inputmode to support mobile numeric keypad. [#1923](https://github.com/ant-design-blazor/ant-design-blazor/pull/1923) [@CAPCHIK](https://github.com/CAPCHIK)
- Select
- 🐞 Fixed the data source which has members of different types. [#1932](https://github.com/ant-design-blazor/ant-design-blazor/pull/1932) [@anranruye](https://github.com/anranruye)
- 🐞 Fixed the problem that the selected item will be reset when setting DataSource [#1906](https://github.com/ant-design-blazor/ant-design-blazor/pull/1906) [@anddrzejb](https://github.com/anddrzejb)
- 🐞 Fixed Menu that the Title of MenuItem with RouterLink is not hidden when it is collapsed. [#1934](https://github.com/ant-design-blazor/ant-design-blazor/pull/1934) [@ElderJames](https://github.com/ElderJames)
- 🐞 Fixed Overlay with a series of issues related to dropdown & popup。 [#1848](https://github.com/ant-design-blazor/ant-design-blazor/pull/1848) [@anddrzejb](https://github.com/anddrzejb)
- 💄 Fixed loading icon styles. [#1902](https://github.com/ant-design-blazor/ant-design-blazor/pull/1902) [@CAPCHIK](https://github.com/CAPCHIK)
- 🐞 Added parameter `Rows`. [#1920](https://github.com/ant-design-blazor/ant-design-blazor/pull/1920) [@anddrzejb](https://github.com/anddrzejb)
- 🐞 Add stop propogation. [#1917](https://github.com/ant-design-blazor/ant-design-blazor/pull/1917) [@Hona](https://github.com/Hona)
- 🐞 Fixed Form modifies the bound model to throw an exception in Rule validation mode. [#1901](https://github.com/ant-design-blazor/ant-design-blazor/pull/1901) [@lxyruanjian](https://github.com/lxyruanjian)
- 🐞 Fixed List resposive style doesn't work. [#1937](https://github.com/ant-design-blazor/ant-design-blazor/pull/1937) [@ElderJames](https://github.com/ElderJames)
- ⚡️ Fixed EventListener avoid memory leak issue. [#1857](https://github.com/ant-design-blazor/ant-design-blazor/pull/1857) [@tonyyip1969](https://github.com/tonyyip1969)
### 0.9.3 ### 0.9.3
@ -26,7 +52,7 @@ timeline: true
- 📖 Add an example of nested table. [#1884](https://github.com/ant-design-blazor/ant-design-blazor/pull/1884) [@ElderJames](https://github.com/ElderJames) - 📖 Add an example of nested table. [#1884](https://github.com/ant-design-blazor/ant-design-blazor/pull/1884) [@ElderJames](https://github.com/ElderJames)
- 🐞 Fixed Time column built-in filter will ignore milliseconds when filtering.[#1864](https://github.com/ant-design-blazor/ant-design-blazor/pull/1864) [@iamSmallY](https://github.com/iamSmallY) - 🐞 Fixed Time column built-in filter will ignore milliseconds when filtering.[#1864](https://github.com/ant-design-blazor/ant-design-blazor/pull/1864) [@iamSmallY](https://github.com/iamSmallY)
- 🐞 Fixed the issue that operations such as page turning, sorting and filtering are not refreshed by using client mode. [#1858](https://github.com/ant-design-blazor/ant-design-blazor/pull/1858) [@ElderJames](https://github.com/ElderJames) - 🐞 Fixed the issue that operations such as page turning, sorting and filtering are not refreshed by using client mode. [#1858](https://github.com/ant-design-blazor/ant-design-blazor/pull/1858) [@ElderJames](https://github.com/ElderJames)
[#1875](https://github.com/ant-design/ant-design/pull/1875) [@nikolaykrondev](https://github.com/nikolaykrondev) [#1875](https://github.com/ant-design-blazor/ant-design-blazor/pull/1875) [@nikolaykrondev](https://github.com/nikolaykrondev)
- 🐞 Fixed the issue that OnChange is called multiple times after initialization. [#1855](https://github.com/ant-design-blazor/ant-design-blazor/pull/1855) [@ElderJames](https://github.com/ElderJames) - 🐞 Fixed the issue that OnChange is called multiple times after initialization. [#1855](https://github.com/ant-design-blazor/ant-design-blazor/pull/1855) [@ElderJames](https://github.com/ElderJames)
- 🆕 Breadcrumb add Href and Overlay dropdown. [#1859](https://github.com/ant-design-blazor/ant-design-blazor/pull/1859) [@CAPCHIK](https://github.com/CAPCHIK) - 🆕 Breadcrumb add Href and Overlay dropdown. [#1859](https://github.com/ant-design-blazor/ant-design-blazor/pull/1859) [@CAPCHIK](https://github.com/CAPCHIK)

View File

@ -15,6 +15,33 @@ timeline: true
--- ---
### 0.9.4
2021-09-12
- Table
- 🐞 修复 在 PageSize 不等于 10 时,初始化时会被刷新两次的问题。[#1933](https://github.com/ant-design-blazor/ant-design-blazor/pull/1933) [@ElderJames](https://github.com/ElderJames)
- 🆕 传递 `CellData` 给 CellRender 模板,可访问当前单元格和行的一些信息。[#1907](https://github.com/ant-design-blazor/ant-design-blazor/pull/1907) [@ElderJames](https://github.com/ElderJames)
- ⚡️ 将固定列的样式处理放到 JS以提升性能。[#1897](https://github.com/ant-design-blazor/ant-design-blazor/pull/1897) [@ElderJames](https://github.com/ElderJames)
- 📖 增加 动态表格 demo。[#1908](https://github.com/ant-design-blazor/ant-design-blazor/pull/1908) [@ElderJames](https://github.com/ElderJames)
- InputNumber
- 🆕 增加 OnFocus 事件。[#1931](https://github.com/ant-design-blazor/ant-design-blazor/pull/1931) [@Hona](https://github.com/Hona)
- 🐞 修复 inputmode支持手机数字键盘。[#1923](https://github.com/ant-design-blazor/ant-design-blazor/pull/1923) [@CAPCHIK](https://github.com/CAPCHIK)
- Select
- 🐞 修复 对异构选项类型的支持。[#1932](https://github.com/ant-design-blazor/ant-design-blazor/pull/1932) [@anranruye](https://github.com/anranruye)
- 🐞 修复 设置 DataSource 时,选中项会被的重置问题。[#1906](https://github.com/ant-design-blazor/ant-design-blazor/pull/1906) [@anddrzejb](https://github.com/anddrzejb)
- 🐞 修复 Overlay 与 dropdown、选项框、popup 有关的一系列问题。[#1848](https://github.com/ant-design-blazor/ant-design-blazor/pull/1848) [@anddrzejb](https://github.com/anddrzejb)
- 💄 修复 Button 的 loading 样式。[#1902](https://github.com/ant-design-blazor/ant-design-blazor/pull/1902) [@CAPCHIK](https://github.com/CAPCHIK)
- 🐞 增加 TextArea 的 `Rows` 属性,支持固定的行数。[#1920](https://github.com/ant-design-blazor/ant-design-blazor/pull/1920) [@anddrzejb](https://github.com/anddrzejb)
- 🐞 增加 Input 的 StopPropogation 属性,以减少事件触发,提升性能。[#1917](https://github.com/ant-design-blazor/ant-design-blazor/pull/1917) [@Hona](https://github.com/Hona)
- 🐞 修复 Form 移除已释放的 FormItem 实例。[#1901](https://github.com/ant-design-blazor/ant-design-blazor/pull/1901) [@lxyruanjian](https://github.com/lxyruanjian)
- ⚡️ 事件订阅器的内存泄漏问题。[#1857](https://github.com/ant-design-blazor/ant-design-blazor/pull/1857) [@tonyyip1969](https://github.com/tonyyip1969)
- 🐞 修复 List 组件的响应式无效的问题。 [#1937](https://github.com/ant-design-blazor/ant-design-blazor/pull/1937) [@ElderJames](https://github.com/ElderJames)
- 🐞 修复 Menu 有 RouterLink 的 MenuItem 在收起时 Title 不隐藏的问题。[#1934](https://github.com/ant-design-blazor/ant-design-blazor/pull/1934) [@ElderJames](https://github.com/ElderJames)
### 0.9.3 ### 0.9.3
2021-08-29 2021-08-29
@ -26,7 +53,7 @@ timeline: true
- 📖 增加 表格嵌套的示例。[#1884](https://github.com/ant-design-blazor/ant-design-blazor/pull/1884) [@ElderJames](https://github.com/ElderJames) - 📖 增加 表格嵌套的示例。[#1884](https://github.com/ant-design-blazor/ant-design-blazor/pull/1884) [@ElderJames](https://github.com/ElderJames)
- 🐞 修复 时间列内置筛选器在筛选时将忽略毫秒。[#1864](https://github.com/ant-design-blazor/ant-design-blazor/pull/1864) [@iamSmallY](https://github.com/iamSmallY) - 🐞 修复 时间列内置筛选器在筛选时将忽略毫秒。[#1864](https://github.com/ant-design-blazor/ant-design-blazor/pull/1864) [@iamSmallY](https://github.com/iamSmallY)
- 🐞 修复 使用客户端模式翻页、排序、筛选等操作不刷新的问题。[#1858](https://github.com/ant-design-blazor/ant-design-blazor/pull/1858) [@ElderJames](https://github.com/ElderJames) - 🐞 修复 使用客户端模式翻页、排序、筛选等操作不刷新的问题。[#1858](https://github.com/ant-design-blazor/ant-design-blazor/pull/1858) [@ElderJames](https://github.com/ElderJames)
[#1875](https://github.com/ant-design/ant-design/pull/1875) [@nikolaykrondev](https://github.com/nikolaykrondev) [#1875](https://github.com/ant-design-blazor/ant-design-blazor/pull/1875) [@nikolaykrondev](https://github.com/nikolaykrondev)
- 🐞 修复 初始化后 OnChange 调用多次的问题。[#1855](https://github.com/ant-design-blazor/ant-design-blazor/pull/1855) [@ElderJames](https://github.com/ElderJames) - 🐞 修复 初始化后 OnChange 调用多次的问题。[#1855](https://github.com/ant-design-blazor/ant-design-blazor/pull/1855) [@ElderJames](https://github.com/ElderJames)
- 🆕 新增 Breadcrumb 的 Href 和 Overlay 下拉菜单。 [#1859](https://github.com/ant-design-blazor/ant-design-blazor/pull/1859) [@CAPCHIK](https://github.com/CAPCHIK) - 🆕 新增 Breadcrumb 的 Href 和 Overlay 下拉菜单。 [#1859](https://github.com/ant-design-blazor/ant-design-blazor/pull/1859) [@CAPCHIK](https://github.com/CAPCHIK)

View File

@ -159,6 +159,7 @@ WebAssembly 静态托管页面示例
$ git clone git@github.com:ant-design-blazor/ant-design-blazor.git $ git clone git@github.com:ant-design-blazor/ant-design-blazor.git
$ cd ant-design-blazor $ cd ant-design-blazor
$ npm install $ npm install
$ dotnet build ./site/AntDesign.Docs.Build/AntDesign.Docs.Build.csproj
$ npm start $ npm start
``` ```

View File

@ -165,6 +165,7 @@ Options for the template
$ git clone git@github.com:ant-design-blazor/ant-design-blazor.git $ git clone git@github.com:ant-design-blazor/ant-design-blazor.git
$ cd ant-design-blazor $ cd ant-design-blazor
$ npm install $ npm install
$ dotnet build ./site/AntDesign.Docs.Build/AntDesign.Docs.Build.csproj
$ npm start $ npm start
``` ```

View File

@ -37,7 +37,7 @@ namespace AntDesign
private string _affixStyle; private string _affixStyle;
[Inject] [Inject]
private DomEventService DomEventService { get; set; } private IDomEventListener DomEventListener { get; set; }
#region Parameters #region Parameters
@ -92,15 +92,14 @@ namespace AntDesign
await RenderAffixAsync(); await RenderAffixAsync();
if (!_rootListened && string.IsNullOrEmpty(TargetSelector)) if (!_rootListened && string.IsNullOrEmpty(TargetSelector))
{ {
DomEventService.AddEventListener(RootScollSelector, "scroll", OnWindowScroll, false); DomEventListener.AddShared<JsonElement>(RootScollSelector, "scroll", OnWindowScroll);
DomEventService.AddEventListener(RootScollSelector, "resize", OnWindowResize, false); DomEventListener.AddShared<JsonElement>(RootScollSelector, "resize", OnWindowResize);
_rootListened = true; _rootListened = true;
} }
else if (!string.IsNullOrEmpty(TargetSelector)) else if (!string.IsNullOrEmpty(TargetSelector))
{ {
DomEventService.AddEventListener(TargetSelector, "scroll", OnTargetScroll); DomEventListener.AddExclusive<JsonElement>(TargetSelector, "scroll", OnTargetScroll);
DomEventService.AddEventListener(TargetSelector, "resize", OnTargetResize); DomEventListener.AddExclusive<JsonElement>(TargetSelector, "resize", OnTargetResize);
_targetListened = true; _targetListened = true;
} }
} }
@ -143,8 +142,8 @@ namespace AntDesign
containerRect = new DomRect() containerRect = new DomRect()
{ {
Top = 0, Top = 0,
Bottom = window.innerHeight, Bottom = window.InnerHeight,
Height = window.innerHeight, Height = window.InnerHeight,
}; };
} }
else else
@ -159,7 +158,7 @@ namespace AntDesign
{ {
if (domRect.Bottom > bottomDist) if (domRect.Bottom > bottomDist)
{ {
_affixStyle = _hiddenStyle + $"bottom: { window.innerHeight - bottomDist}px; position: fixed;"; _affixStyle = _hiddenStyle + $"bottom: { window.InnerHeight - bottomDist}px; position: fixed;";
Affixed = true; Affixed = true;
} }
else else
@ -189,17 +188,7 @@ namespace AntDesign
{ {
base.Dispose(disposing); base.Dispose(disposing);
if (_rootListened) DomEventListener.Dispose();
{
DomEventService.RemoveEventListerner<JsonElement>(RootScollSelector, "scroll", OnWindowScroll);
DomEventService.RemoveEventListerner<JsonElement>(RootScollSelector, "resize", OnWindowResize);
}
if (_targetListened)
{
DomEventService.RemoveEventListerner<JsonElement>(TargetSelector, "scroll", OnTargetScroll);
DomEventService.RemoveEventListerner<JsonElement>(TargetSelector, "resize", OnTargetResize);
}
} }
} }
} }

View File

@ -26,7 +26,7 @@ namespace AntDesign
private bool _linksChanged = false; private bool _linksChanged = false;
[Inject] [Inject]
private DomEventService DomEventService { get; set; } private IDomEventListener DomEventListener { get; set; }
#region Parameters #region Parameters
@ -129,7 +129,7 @@ namespace AntDesign
{ {
if (GetCurrentAnchor is null) if (GetCurrentAnchor is null)
{ {
DomEventService.AddEventListener("window", "scroll", OnScroll, false); DomEventListener.AddShared<JsonElement>("window", "scroll", OnScroll);
} }
} }
@ -297,9 +297,8 @@ namespace AntDesign
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
DomEventListener.Dispose();
base.Dispose(disposing); base.Dispose(disposing);
DomEventService.RemoveEventListerner<JsonElement>("window", "scroll", OnScroll);
} }
} }
} }

View File

@ -8,7 +8,8 @@
Style="display: inline" Style="display: inline"
IsButton="@true" IsButton="@true"
Disabled="false" Disabled="false"
Trigger="new TriggerType[] { TriggerType.Click }" Trigger="new[] { Trigger.Click }"
BoundaryAdjustMode="@BoundaryAdjustMode"
PopupContainerSelector="@PopupContainerSelector" PopupContainerSelector="@PopupContainerSelector"
OverlayEnterCls="ant-slide-up-enter ant-slide-up-enter-active ant-slide-up" 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" OverlayLeaveCls="ant-slide-up-leave ant-slide-up-leave-active ant-slide-up"

View File

@ -163,6 +163,13 @@ namespace AntDesign
/// </summary> /// </summary>
public object ActiveValue { get; set; } public object ActiveValue { get; set; }
/// <summary>
/// Overlay adjustment strategy (when for example browser resize is happening). Check
/// enum for details.
/// </summary>
[Parameter]
public TriggerBoundaryAdjustMode BoundaryAdjustMode { get; set; } = TriggerBoundaryAdjustMode.InView;
[Parameter] [Parameter]
public bool ShowPanel { get; set; } = false; public bool ShowPanel { get; set; } = false;

View File

@ -8,7 +8,7 @@
</CascadingValue> </CascadingValue>
@if (_overflow) @if (_overflow)
{ {
<Popover Trigger="new[] { TriggerType.Hover }" <Popover Trigger="new[] { Trigger.Hover }"
Placement=MaxPopoverPlacement Placement=MaxPopoverPlacement
OverlayClassName="@_popoverClassMapper.Class"> OverlayClassName="@_popoverClassMapper.Class">
<ContentTemplate> <ContentTemplate>

View File

@ -15,7 +15,7 @@ namespace AntDesign
public string MaxStyle { get; set; } public string MaxStyle { get; set; }
[Parameter] [Parameter]
public PlacementType MaxPopoverPlacement { get; set; } = PlacementType.Top; public Placement MaxPopoverPlacement { get; set; } = Placement.Top;
private ClassMapper _popoverClassMapper = new ClassMapper(); private ClassMapper _popoverClassMapper = new ClassMapper();

View File

@ -10,7 +10,7 @@ namespace AntDesign
private bool _visible = false; private bool _visible = false;
[Inject] [Inject]
public DomEventService DomEventService { get; set; } private IDomEventListener DomEventListener { get; set; }
[Parameter] [Parameter]
public string Icon { get; set; } = "vertical-align-top"; public string Icon { get; set; } = "vertical-align-top";
@ -55,11 +55,11 @@ namespace AntDesign
{ {
if (string.IsNullOrWhiteSpace(TargetSelector)) if (string.IsNullOrWhiteSpace(TargetSelector))
{ {
DomEventService.AddEventListener("window", "scroll", OnScroll); DomEventListener.AddExclusive<JsonElement>("window", "scroll", OnScroll);
} }
else else
{ {
DomEventService.AddEventListener(TargetSelector, "scroll", OnScroll); DomEventListener.AddExclusive<JsonElement>(TargetSelector, "scroll", OnScroll);
} }
await base.OnFirstAfterRenderAsync(); await base.OnFirstAfterRenderAsync();
} }
@ -88,15 +88,8 @@ namespace AntDesign
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
DomEventListener.DisposeExclusive();
base.Dispose(disposing); base.Dispose(disposing);
if (string.IsNullOrWhiteSpace(TargetSelector))
{
DomEventService.RemoveEventListerner<JsonElement>("window", "scroll", OnScroll);
}
else
{
DomEventService.RemoveEventListerner<JsonElement>(TargetSelector, "scroll", OnScroll);
}
} }
} }
} }

View File

@ -5,7 +5,7 @@
@if (Overlay != null) @if (Overlay != null)
{ {
<Dropdown Placement="@PlacementType.BottomCenter"> <Dropdown Placement="@Placement.BottomCenter">
<Unbound> <Unbound>
<span @ref="context.Current" class="ant-breadcrumb-overlay-link"> <span @ref="context.Current" class="ant-breadcrumb-overlay-link">
<span class="ant-breadcrumb-link"> <span class="ant-breadcrumb-link">

View File

@ -2,20 +2,31 @@
@inherits AntDomComponentBase @inherits AntDomComponentBase
<CascadingValue Value="this" IsFixed="@true"> <CascadingValue Value="this" IsFixed="@true">
<button class="@ClassMapper.Class" style="@(this.Danger ? this._btnWave + Style:Style)" id="@Id" type="@HtmlType" @ref="@Ref" <button class="@ClassMapper.Class" style="@(this.Danger ? this._btnWave + Style : Style)" id="@Id" type="@HtmlType" @ref="@Ref"
@onclick="HandleOnClick" disabled="@Disabled" @onclick="HandleOnClick" disabled="@Disabled"
@onclick:stopPropagation="@OnClickStopPropagation" @onclick:stopPropagation="@OnClickStopPropagation"
@onmouseup="OnMouseUp" @onmouseup="OnMouseUp"
ant-click-animating-without-extra-node="@(this._animating ? "true":"false")"> ant-click-animating-without-extra-node="@(this._animating ? "true":"false")">
@if (Loading) @if (Loading)
{ {
<Icon Type="loading"></Icon> <span class="ant-btn-loading-icon">
<Icon Type="loading" />
</span>
} }
@if (!string.IsNullOrEmpty(Icon)) else if (!string.IsNullOrEmpty(Icon))
{ {
<Icon Type="@Icon" Style="@IconStyle"></Icon> <Icon Type="@Icon" />
}
@if (ChildContent != default)
{
@if (NoSpanWrap)
{
@ChildContent
}
else
{
<span>@ChildContent</span>
}
} }
@ChildContent
</button> </button>
</CascadingValue> </CascadingValue>

View File

@ -49,7 +49,7 @@ namespace AntDesign
public bool Danger { get; set; } public bool Danger { get; set; }
/// <summary> /// <summary>
/// Whether the `Button` is disabled. /// Whether the `Button` is disabled.
/// </summary> /// </summary>
[Parameter] [Parameter]
public bool Disabled { get; set; } public bool Disabled { get; set; }
@ -73,21 +73,10 @@ namespace AntDesign
public string Icon { get; set; } public string Icon { get; set; }
/// <summary> /// <summary>
/// Show loading indicator. You have to write the loading logic on your own. /// Show loading indicator. You have to write the loading logic on your own.
/// </summary> /// </summary>
[Parameter] [Parameter]
public bool Loading public bool Loading { get; set; }
{
get => _loading;
set
{
if (_loading != value)
{
_loading = value;
UpdateIconDisplay(_loading);
}
}
}
/// <summary> /// <summary>
/// Callback when `Button` is clicked /// Callback when `Button` is clicked
@ -114,19 +103,23 @@ namespace AntDesign
public string Size { get; set; } = AntSizeLDSType.Default; public string Size { get; set; } = AntSizeLDSType.Default;
/// <summary> /// <summary>
/// Type of the button. /// Type of the button.
/// </summary> /// </summary>
[Parameter] [Parameter]
public string Type { get; set; } = ButtonType.Default; public string Type { get; set; } = ButtonType.Default;
/// <summary>
/// Do not wrap with &lt;span&gt;
/// </summary>
[Parameter]
public bool NoSpanWrap { get; set; }
public IList<Icon> Icons { get; set; } = new List<Icon>(); public IList<Icon> Icons { get; set; } = new List<Icon>();
protected string IconStyle { get; set; }
private bool _animating = false; private bool _animating = false;
private string _btnWave = "--antd-wave-shadow-color: rgb(255, 120, 117);"; private string _btnWave = "--antd-wave-shadow-color: rgb(255, 120, 117);";
private bool _loading = false;
protected void SetClassMap() protected void SetClassMap()
{ {
@ -152,12 +145,6 @@ namespace AntDesign
base.OnInitialized(); base.OnInitialized();
SetClassMap(); SetClassMap();
SetButtonColorStyle(); SetButtonColorStyle();
UpdateIconDisplay(_loading);
}
private void UpdateIconDisplay(bool loading)
{
IconStyle = $"display:{(loading ? "none" : "inline-block")}";
} }
private async Task HandleOnClick(MouseEventArgs args) private async Task HandleOnClick(MouseEventArgs args)

View File

@ -74,7 +74,7 @@ namespace AntDesign
#endregion Parameters #endregion Parameters
[Inject] public DomEventService DomEventService { get; set; } [Inject] public IDomEventListener DomEventListener { get; set; }
public void Next() => GoTo(_slicks.IndexOf(ActiveSlick) + 1); public void Next() => GoTo(_slicks.IndexOf(ActiveSlick) + 1);
@ -130,7 +130,7 @@ namespace AntDesign
if (firstRender) if (firstRender)
{ {
Resize(); Resize();
DomEventService.AddEventListener("window", "resize", Resize, false); DomEventListener.AddShared<JsonElement>("window", "resize", Resize);
} }
} }
@ -208,10 +208,8 @@ namespace AntDesign
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
DomEventService.RemoveEventListerner<JsonElement>("window", "resize", Resize); DomEventListener.Dispose();
_slicks.Clear(); _slicks.Clear();
base.Dispose(disposing); base.Dispose(disposing);
} }
} }

View File

@ -5,7 +5,8 @@
<CascadingValue Value=@("ant-cascader-menus") Name="PrefixCls"> <CascadingValue Value=@("ant-cascader-menus") Name="PrefixCls">
<OverlayTrigger Visible="@_dropdownOpened" <OverlayTrigger Visible="@_dropdownOpened"
OnMaskClick="CascaderOnBlur" OnMaskClick="CascaderOnBlur"
Trigger="new TriggerType[] { }" Trigger="new Trigger[] { }"
BoundaryAdjustMode="@BoundaryAdjustMode"
PopupContainerSelector="@PopupContainerSelector" PopupContainerSelector="@PopupContainerSelector"
OverlayEnterCls="ant-slide-up-enter ant-slide-up-enter-active ant-slide-up" 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"> OverlayLeaveCls="ant-slide-up-leave ant-slide-up-leave-active ant-slide-up">

View File

@ -13,6 +13,11 @@ namespace AntDesign
{ {
[Parameter] public bool AllowClear { get; set; } = true; [Parameter] public bool AllowClear { get; set; } = true;
/// <summary>
/// Overlay adjustment strategy (when for example browser resize is happening)
/// </summary>
[Parameter] public TriggerBoundaryAdjustMode BoundaryAdjustMode { get; set; } = TriggerBoundaryAdjustMode.None;
[Parameter] public bool ChangeOnSelect { get; set; } [Parameter] public bool ChangeOnSelect { get; set; }
[Parameter] public string DefaultValue { get; set; } [Parameter] public string DefaultValue { get; set; }

View File

@ -11,10 +11,11 @@
@ref="Ref" @ref="Ref"
@onmouseenter="OnOverlayMouseEnter" @onmouseenter="OnOverlayMouseEnter"
@onmouseleave="OnOverlayMouseLeave" @onmouseleave="OnOverlayMouseLeave"
@onmouseup="OnOverlayMouseUp" @onmouseup="OnOverlayMouseUp"
@onclick:stopPropagation> @onclick:stopPropagation>
<CascadingValue Value="this" Name="Overlay" IsFixed="@true">
<CascadingValue Value="Trigger" Name="ParentTrigger"> <CascadingValue Value="this" Name="Overlay" IsFixed>
<CascadingValue Value="Trigger" Name="ParentTrigger" IsFixed>
@if (!string.IsNullOrEmpty(OverlayChildPrefixCls)) @if (!string.IsNullOrEmpty(OverlayChildPrefixCls))
{ {
<CascadingValue Value="OverlayChildPrefixCls" Name="PrefixCls"> <CascadingValue Value="OverlayChildPrefixCls" Name="PrefixCls">

View File

@ -1,7 +1,7 @@
using System; using System;
using System.Linq;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using AntDesign.Core.JsInterop.Modules.Components;
using AntDesign.JsInterop; using AntDesign.JsInterop;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
@ -9,14 +9,21 @@ namespace AntDesign.Internal
{ {
public sealed partial class Overlay : AntDomComponentBase public sealed partial class Overlay : AntDomComponentBase
{ {
[CascadingParameter(Name = "Trigger")] [CascadingParameter(Name = "ArrowPointAtCenter")]
public OverlayTrigger Trigger { get; set; } public bool ArrowPointAtCenter { get; set; }
/// <summary>
/// Used in nested overlays (for example menu -> submenu) when
/// trigger is another overlay.
/// </summary>
[CascadingParameter(Name = "ParentTrigger")] [CascadingParameter(Name = "ParentTrigger")]
public OverlayTrigger ParentTrigger { get; set; } public OverlayTrigger ParentTrigger { get; set; }
[Parameter] /// <summary>
public string OverlayChildPrefixCls { get; set; } = ""; /// Component that will trigger the overlay to show.
/// </summary>
[CascadingParameter(Name = "Trigger")]
public OverlayTrigger Trigger { get; set; }
[Parameter] [Parameter]
public RenderFragment ChildContent { get; set; } public RenderFragment ChildContent { get; set; }
@ -31,13 +38,14 @@ namespace AntDesign.Internal
public EventCallback OnOverlayMouseUp { get; set; } public EventCallback OnOverlayMouseUp { get; set; }
[Parameter] [Parameter]
public Action OnShow { get; set; } public EventCallback OnShow { get; set; }
[Parameter] [Parameter]
public Action OnHide { get; set; } public EventCallback OnHide { get; set; }
[Parameter]
public string OverlayChildPrefixCls { get; set; } = "";
[CascadingParameter(Name = "ArrowPointAtCenter")]
public bool ArrowPointAtCenter { get; set; }
[Parameter] [Parameter]
public int HideMillisecondsDelay { get; set; } = 100; public int HideMillisecondsDelay { get; set; } = 100;
@ -69,7 +77,7 @@ namespace AntDesign.Internal
public bool HiddenMode { get; set; } = false; public bool HiddenMode { get; set; } = false;
[Inject] [Inject]
private DomEventService DomEventService { get; set; } private IDomEventListener DomEventListener { get; set; }
private bool _hasAddOverlayToBody = false; private bool _hasAddOverlayToBody = false;
private bool _isPreventHide = false; private bool _isPreventHide = false;
@ -80,6 +88,7 @@ namespace AntDesign.Internal
private bool _isWaitForOverlayFirstRender = false; private bool _isWaitForOverlayFirstRender = false;
private bool _preVisible = false; private bool _preVisible = false;
private bool _isOverlayDuringShowing = false;
private bool _isOverlayShow = false; private bool _isOverlayShow = false;
private bool _isOverlayHiding = false; private bool _isOverlayHiding = false;
private bool _lastDisabledState = false; private bool _lastDisabledState = false;
@ -87,14 +96,20 @@ namespace AntDesign.Internal
private int? _overlayLeft = null; private int? _overlayLeft = null;
private int? _overlayTop = null; private int? _overlayTop = null;
private string _overlayStyle = ""; //if this style needs to be changed, also change
//the removal of that style in js interop overlay.ts class (in constructor)
private string _overlayStyle = "display: none;"; //initial value prevents from screen flickering when adding overlay to dom; it will be overwritten immediately
private string _overlayCls = ""; private string _overlayCls = "";
private const int ARROW_SIZE = 13; private bool _shouldRender = true;
private const int HORIZONTAL_ARROW_SHIFT = 13;
private const int VERTICAL_ARROW_SHIFT = 5;
private int _overlayClientWidth = 0; protected override bool ShouldRender()
{
if (_shouldRender)
return base.ShouldRender();
_shouldRender = true;
return false;
}
protected override void OnInitialized() protected override void OnInitialized()
{ {
@ -121,7 +136,7 @@ namespace AntDesign.Internal
{ {
if (firstRender) if (firstRender)
{ {
DomEventService.AddEventListener("window", "beforeunload", Reloading, false); DomEventListener.AddShared<JsonElement>("window", "beforeunload", Reloading);
} }
if (_lastDisabledState != Trigger.Disabled) if (_lastDisabledState != Trigger.Disabled)
@ -143,7 +158,7 @@ namespace AntDesign.Internal
if (_isWaitForOverlayFirstRender && _isOverlayFirstRender) if (_isWaitForOverlayFirstRender && _isOverlayFirstRender)
{ {
_isOverlayFirstRender = false; _isOverlayFirstRender = false;
await Show(_overlayLeft, _overlayTop); //await Show(_overlayLeft, _overlayTop);
_isWaitForOverlayFirstRender = false; _isWaitForOverlayFirstRender = false;
_overlayLeft = null; _overlayLeft = null;
@ -159,10 +174,10 @@ namespace AntDesign.Internal
_ = InvokeAsync(async () => _ = InvokeAsync(async () =>
{ {
await Task.Delay(100); await Task.Delay(100);
await JsInvokeAsync(JSInteropConstants.DelElementFrom, Ref, Trigger.PopupContainerSelector); await JsInvokeAsync(JSInteropConstants.OverlayComponentHelper.DeleteOverlayFromContainer, Ref.Id);
}); });
} }
DomEventService.RemoveEventListerner<JsonElement>("window", "beforeunload", Reloading); DomEventListener.Dispose();
base.Dispose(disposing); base.Dispose(disposing);
} }
@ -172,24 +187,14 @@ namespace AntDesign.Internal
{ {
return; return;
} }
await Task.Yield(); _isOverlayDuringShowing = true;
HtmlElement trigger;
if (Trigger.ChildContent is not null) if (_isOverlayFirstRender)
{ {
trigger = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetFirstChildDomInfo, Trigger.Ref); Trigger.SetShouldRender(false);
// fix bug in submenu: Overlay show when OvelayTrigger is not rendered complete. await Task.Yield();
// I try to render Overlay when OvelayTriggers OnAfterRender is called, but is still get negative absoluteLeft
// This may be a bad solution, but for now I can only do it this way.
while (trigger.AbsoluteLeft <= 0 && trigger.ClientWidth <= 0)
{
await Task.Delay(50);
trigger = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetFirstChildDomInfo, Trigger.Ref);
}
}
else //(Trigger.Unbound != null)
{
trigger = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, Trigger.Ref);
} }
else
_overlayLeft = overlayLeft; _overlayLeft = overlayLeft;
_overlayTop = overlayTop; _overlayTop = overlayTop;
@ -198,66 +203,51 @@ namespace AntDesign.Internal
{ {
_isWaitForOverlayFirstRender = true; _isWaitForOverlayFirstRender = true;
StateHasChanged(); await InvokeAsync(StateHasChanged);
return;
} }
_isOverlayShow = true;
_isOverlayHiding = false;
await UpdateParentOverlayState(true); await UpdateParentOverlayState(true);
await AddOverlayToBody(); await AddOverlayToBody(overlayLeft, overlayTop);
_isOverlayShow = true;
HtmlElement overlayElement = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, Ref); _isOverlayDuringShowing = false;
HtmlElement containerElement = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, Trigger.PopupContainerSelector); _isOverlayHiding = false;
int left = await GetOverlayLeftWithBoundaryAdjust(trigger, overlayElement, containerElement);
int top = await GetOverlayTopWithBoundaryAdjust(trigger, overlayElement, containerElement);
int zIndex = await JsInvokeAsync<int>(JSInteropConstants.GetMaxZIndex);
if (Trigger.Placement.IsIn(PlacementType.BottomRight, PlacementType.TopRight))
{
var right = ChangeOverlayLeftToRight(left, overlayElement, containerElement);
_overlayStyle = $"z-index:{zIndex};left: unset;right: {right}px;top: {top}px;{GetTransformOrigin()}";
}
else
{
_overlayStyle = $"z-index:{zIndex};left: {left}px;top: {top}px;{GetTransformOrigin()}";
}
_overlayCls = Trigger.GetOverlayEnterClass(); _overlayCls = Trigger.GetOverlayEnterClass();
await Trigger.OnVisibleChange.InvokeAsync(true); await Trigger.OnVisibleChange.InvokeAsync(true);
StateHasChanged(); await InvokeAsync(StateHasChanged);
OnShow?.Invoke(); if (OnShow.HasDelegate)
OnShow.InvokeAsync(null);
} }
internal async Task Hide(bool force = false) internal async Task Hide(bool force = false)
{ {
if (_isOverlayDuringShowing)
{
//If Show() method is processing, wait up to 1000 ms
//for it to end processing
await WaitFor(() => _isOverlayShow);
}
if (!_isOverlayShow) if (!_isOverlayShow)
{ {
return; return;
} }
await Task.Delay(HideMillisecondsDelay); await Task.Delay(HideMillisecondsDelay);
if (!force && !IsContainTrigger(TriggerType.Click) && (_isPreventHide || _mouseInOverlay || _isChildOverlayShow)) if (!force && !IsContainTrigger(TriggerType.Click) && (_isPreventHide || _mouseInOverlay || _isChildOverlayShow))
{ {
return; return;
} }
_isOverlayFirstRender = true; _isOverlayFirstRender = true;
_isWaitForOverlayFirstRender = false; _isWaitForOverlayFirstRender = false;
_isOverlayHiding = true; _isOverlayHiding = true;
_overlayCls = Trigger.GetOverlayLeaveClass(); _overlayCls = Trigger.GetOverlayLeaveClass();
await Trigger.OnOverlayHiding.InvokeAsync(true); await Trigger.OnOverlayHiding.InvokeAsync(true);
await UpdateParentOverlayState(false); await UpdateParentOverlayState(false);
StateHasChanged(); StateHasChanged();
@ -271,7 +261,8 @@ namespace AntDesign.Internal
StateHasChanged(); StateHasChanged();
OnHide?.Invoke(); if (OnHide.HasDelegate)
OnHide.InvokeAsync(null);
} }
internal void PreventHide(bool prevent) internal void PreventHide(bool prevent)
@ -308,275 +299,84 @@ namespace AntDesign.Internal
/// Indicates that a page is being refreshed /// Indicates that a page is being refreshed
/// </summary> /// </summary>
private bool _isReloading; private bool _isReloading;
private OverlayPosition _position;
private void Reloading(JsonElement jsonElement) => _isReloading = true; private void Reloading(JsonElement jsonElement) => _isReloading = true;
private async Task AddOverlayToBody() private int _recurenceGuard = 0;
private async Task AddOverlayToBody(int? overlayLeft = null, int? overlayTop = null)
{ {
if (!_hasAddOverlayToBody) if (!_hasAddOverlayToBody)
{ {
await JsInvokeAsync(JSInteropConstants.AddElementTo, Ref, Trigger.PopupContainerSelector); bool triggerIsWrappedInDiv = Trigger.Unbound is null;
_recurenceGuard++;
_hasAddOverlayToBody = true; //In ServerSide it may happen that trigger element reference has not yet been retrieved.
} if (!(await WaitFor(() => Trigger.Ref.Id is not null)))
}
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(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(HtmlElement trigger, HtmlElement overlay, HtmlElement containerElement)
{
int top = 0;
int triggerTop = (int)(containerElement.ScrollTop + trigger.AbsoluteTop - containerElement.AbsoluteTop);
int triggerHeight = trigger.ClientHeight != 0 ? trigger.ClientHeight : trigger.OffsetHeight;
// contextMenu
if (_overlayTop != null)
{
triggerTop += (int)_overlayTop;
triggerHeight = 0;
}
if (Trigger.Placement.IsIn(PlacementType.Left, PlacementType.Right))
{
top = triggerTop + triggerHeight / 2 - overlay.ClientHeight / 2;
}
else if (Trigger.Placement.IsIn(PlacementType.LeftTop, PlacementType.RightTop))
{
top = triggerTop;
if (ArrowPointAtCenter)
{ {
top += -VERTICAL_ARROW_SHIFT - ARROW_SIZE / 2 + triggerHeight / 2; //Place where Error Boundary could be utilized
throw new ArgumentNullException("Trigger.Ref.Id cannot be null when attaching overlay to it.");
} }
} _position = await JsInvokeAsync<OverlayPosition>(JSInteropConstants.OverlayComponentHelper.AddOverlayToContainer,
else if (Trigger.Placement.IsIn(PlacementType.LeftBottom, PlacementType.RightBottom)) Ref.Id, Ref, Trigger.Ref, Trigger.Placement, Trigger.PopupContainerSelector,
{ Trigger.BoundaryAdjustMode, triggerIsWrappedInDiv, Trigger.PrefixCls,
top = triggerTop - overlay.ClientHeight + triggerHeight; VerticalOffset, HorizontalOffset, ArrowPointAtCenter, overlayTop, overlayLeft);
if (_position is null && _recurenceGuard <= 10) //up to 10 attempts
if (ArrowPointAtCenter)
{ {
top += VERTICAL_ARROW_SHIFT + ARROW_SIZE / 2 - triggerHeight / 2; Console.WriteLine($"Failed to add overlay to the container. Container: {Trigger.PopupContainerSelector}, trigger: {Trigger.Ref.Id}, overlay: {Ref.Id}. Awaiting and rerunning.");
await Task.Delay(10);
await AddOverlayToBody(overlayLeft, overlayTop);
} }
} else
else if (Trigger.Placement.IsIn(PlacementType.BottomLeft, PlacementType.BottomCenter, PlacementType.Bottom, PlacementType.BottomRight))
{
top = triggerTop + triggerHeight + VerticalOffset;
}
else if (Trigger.Placement.IsIn(PlacementType.TopLeft, PlacementType.TopCenter, PlacementType.Top, PlacementType.TopRight))
{
top = triggerTop - overlay.ClientHeight - VerticalOffset;
}
top -= containerElement.ClientTop;
return top;
}
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;
if (overlay.ClientWidth > 0)
{
_overlayClientWidth = overlay.ClientWidth;
}
// contextMenu
if (_overlayLeft != null)
{
triggerLeft += (int)_overlayLeft;
triggerWidth = 0;
}
if (Trigger.Placement.IsIn(PlacementType.Left, PlacementType.LeftTop, PlacementType.LeftBottom))
{
left = triggerLeft - _overlayClientWidth - HorizontalOffset;
}
else if (Trigger.Placement.IsIn(PlacementType.Right, PlacementType.RightTop, PlacementType.RightBottom))
{
left = triggerLeft + triggerWidth + HorizontalOffset;
}
else if (Trigger.Placement.IsIn(PlacementType.BottomLeft, PlacementType.TopLeft))
{
left = triggerLeft;
if (ArrowPointAtCenter)
{ {
left += -HORIZONTAL_ARROW_SHIFT - ARROW_SIZE / 2 + triggerWidth / 2; _hasAddOverlayToBody = true;
_overlayStyle = _position.PositionCss + GetTransformOrigin();
if (_position.Placement != Trigger.Placement)
{
Trigger.ChangePlacementForShow(PlacementType.Create(_position.Placement));
}
} }
} }
else if (Trigger.Placement.IsIn(PlacementType.BottomCenter, PlacementType.Bottom, PlacementType.TopCenter, PlacementType.Top))
{
left = triggerLeft + triggerWidth / 2 - _overlayClientWidth / 2;
}
else if (Trigger.Placement.IsIn(PlacementType.BottomRight, PlacementType.TopRight))
{
left = triggerLeft + triggerWidth - _overlayClientWidth;
if (ArrowPointAtCenter)
{
left += HORIZONTAL_ARROW_SHIFT + ARROW_SIZE / 2 - triggerWidth / 2;
}
}
left -= containerElement.ClientLeft;
return left;
}
/*
*
* boundary detection and orientation adjustment
*/
private async Task<int> BoundaryAdjust(
HtmlElement trigger, HtmlElement overlay, HtmlElement containerElement,
int curPos, string direction)
{
if (Trigger.BoundaryAdjustMode == TriggerBoundaryAdjustMode.None)
{
return curPos;
}
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)
{
curPos -= overlaySize;
}
else if (curPos < 0)
{
curPos += overlaySize;
}
return curPos;
}
int overlayPosWithSize = GetOverlayPosWithSize(curPos, overlaySize);
if ((overlayPosWithSize > boundarySize || overlayPosWithSize < 0)
&& distanceFromBoundry >= overlaySize) // check if still outof boundary after reverse placement
{
// 翻转位置/reverse placement
Trigger.ChangePlacementForShow(Trigger.Placement.GetReverseType());
int reversePos = direction switch
{
"top" => GetOverlayTop(trigger, overlay, containerElement),
_ => GetOverlayLeft(trigger, overlay, containerElement)
};
curPos = reversePos;
}
return curPos < 0 ? 0 : curPos;
}
private int GetOverlayPosWithSize(int overlayPos, int overlaySize)
{
PlacementDirection placementDirection = Trigger.Placement.GetDirection();
if (placementDirection.IsIn(PlacementDirection.Top, PlacementDirection.Left))
{
return overlayPos - overlaySize;
}
else else
{ {
return overlayPos + overlaySize; await UpdatePosition(overlayLeft, overlayTop);
} }
_recurenceGuard = 0;
} }
private int GetOverlayBoundaryPos(int overlaySize, int boundarySize) /// <summary>
/// Will probe a check predicate every given milliseconds until predicate is true or until
/// runs out of number of probings.
/// </summary>
/// <param name="check">A predicate that will be run every time after waitTimeInMilisecondsPerProbing will pass.</param>
/// <param name="probings">Maximum number of probings. After this number is reached, the method finishes.</param>
/// <param name="waitTimeInMilisecondsPerProbing">How long to wait between each probing.</param>
/// <returns>Task</returns>
private async Task<bool> WaitFor(Func<bool> check, int probings = 100, int waitTimeInMilisecondsPerProbing = 10)
{ {
PlacementDirection placementDirection = Trigger.Placement.GetDirection(); if (!check())
if (placementDirection.IsIn(PlacementDirection.Top, PlacementDirection.Left))
{ {
return 0; for (int i = 0; i < probings; i++)
}
else
{
return boundarySize - overlaySize;
}
}
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, await Task.Delay(waitTimeInMilisecondsPerProbing);
_ => containerElement.ScrollWidth if (check())
}; {
return true;
}
}
return false;
} }
return true;
Window windowElement = await JsInvokeAsync<Window>(JSInteropConstants.GetWindow, Ref);
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" => (int)windowElement.innerHeight + windowScrollY,
_ => (int)windowElement.innerWidth + windowScrollX
};
} }
private string GetTransformOrigin() private string GetTransformOrigin()
{ {
return $"transform-origin: {Trigger.Placement.TranformOrigin}"; return $"transform-origin: {Trigger.GetPlacementType().TranformOrigin}";
} }
private bool IsContainTrigger(TriggerType triggerType) private bool IsContainTrigger(TriggerType triggerType)
{ {
foreach (TriggerType trigger in Trigger.Trigger) foreach (TriggerType trigger in Trigger.GetTriggerType())
{ {
if (trigger == triggerType) if (trigger == triggerType)
{ {
@ -630,44 +430,25 @@ namespace AntDesign.Internal
if (_hasAddOverlayToBody) if (_hasAddOverlayToBody)
return "visibility: hidden;"; return "visibility: hidden;";
return "display: inline-flex; visibility: hidden;"; return "display: inline-flex; visibility: hidden;";
} }
internal async Task UpdatePosition(int? overlayLeft = null, int? overlayTop = null) internal async Task UpdatePosition(int? overlayLeft = null, int? overlayTop = null)
{ {
HtmlElement trigger; bool triggerIsWrappedInDiv = Trigger.Unbound is null;
if (Trigger.ChildContent != null)
_position = await JsInvokeAsync<OverlayPosition>(JSInteropConstants.OverlayComponentHelper.UpdateOverlayPosition,
Ref.Id, Ref, Trigger.Ref, Trigger.Placement, Trigger.PopupContainerSelector,
Trigger.BoundaryAdjustMode, triggerIsWrappedInDiv, Trigger.PrefixCls,
VerticalOffset, HorizontalOffset, ArrowPointAtCenter, overlayTop, overlayLeft);
if (_position is not null)
{ {
trigger = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetFirstChildDomInfo, Trigger.Ref); if (_position.Placement != Trigger.Placement)
{
Trigger.ChangePlacementForShow(PlacementType.Create(_position.Placement));
}
_overlayStyle = _position.PositionCss + GetTransformOrigin();
} }
else //(Trigger.Unbound != null)
{
trigger = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, Trigger.Ref);
}
_overlayLeft = overlayLeft;
_overlayTop = overlayTop;
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);
int zIndex = await JsInvokeAsync<int>(JSInteropConstants.GetMaxZIndex);
if (Trigger.Placement.IsIn(PlacementType.BottomRight, PlacementType.TopRight))
{
var right = ChangeOverlayLeftToRight(left, overlayElement, containerElement);
_overlayStyle = $"z-index:{zIndex};left: unset;right: {right}px;top: {top}px;{GetTransformOrigin()}";
}
else
{
_overlayStyle = $"z-index:{zIndex};left: {left}px;top: {top}px;{GetTransformOrigin()}";
}
StateHasChanged();
} }
private int ChangeOverlayLeftToRight(int left, HtmlElement overlay, HtmlElement container) private int ChangeOverlayLeftToRight(int left, HtmlElement overlay, HtmlElement container)

View File

@ -34,5 +34,5 @@
OnHide="OnOverlayHide" OnHide="OnOverlayHide"
OnOverlayMouseEnter="OnOverlayMouseEnter" OnOverlayMouseEnter="OnOverlayMouseEnter"
OnOverlayMouseLeave="OnOverlayMouseLeave" OnOverlayMouseLeave="OnOverlayMouseLeave"
OnOverlayMouseUp="OnOverlayMouseUp" /> OnOverlayMouseUp="OnOverlayMouseUp" />
</CascadingValue> </CascadingValue>

View File

@ -31,7 +31,8 @@ namespace AntDesign.Internal
} }
/// <summary> /// <summary>
/// Overlay adjustment strategy (when for example browser resize is happening) /// Overlay adjustment strategy (when for example browser resize is happening). Check
/// enum for details.
/// </summary> /// </summary>
[Parameter] [Parameter]
public TriggerBoundaryAdjustMode BoundaryAdjustMode { get; set; } = TriggerBoundaryAdjustMode.InView; public TriggerBoundaryAdjustMode BoundaryAdjustMode { get; set; } = TriggerBoundaryAdjustMode.InView;
@ -90,12 +91,14 @@ namespace AntDesign.Internal
/// <summary> /// <summary>
/// Callback when mouse enters trigger boundaries. /// Callback when mouse enters trigger boundaries.
/// </summary> /// </summary>
[Parameter] public Action OnMouseEnter { get; set; } [Parameter]
public EventCallback OnMouseEnter { get; set; }
/// <summary> /// <summary>
/// Callback when mouse leaves trigger boundaries. /// Callback when mouse leaves trigger boundaries.
/// </summary> /// </summary>
[Parameter] public Action OnMouseLeave { get; set; } [Parameter]
public EventCallback OnMouseLeave { get; set; }
/// <summary> /// <summary>
/// Callback when overlay is hiding. /// Callback when overlay is hiding.
@ -158,19 +161,21 @@ namespace AntDesign.Internal
private PlacementType _paramPlacement = PlacementType.BottomLeft; private PlacementType _paramPlacement = PlacementType.BottomLeft;
[Parameter] [Parameter]
public PlacementType Placement public Placement Placement
{ {
get get
{ {
return RTL ? _placement.GetRTLPlacement() : _placement; return (RTL ? _placement.GetRTLPlacement() : _placement).Placement;
} }
set set
{ {
_placement = value; _placement = PlacementType.Create(value);
_paramPlacement = value; _paramPlacement = PlacementType.Create(value);
} }
} }
internal PlacementType GetPlacementType() => _placement;
/// <summary> /// <summary>
/// Override default placement class which is based on `Placement` parameter. /// Override default placement class which is based on `Placement` parameter.
/// </summary> /// </summary>
@ -189,7 +194,14 @@ namespace AntDesign.Internal
/// Trigger mode. Could be multiple by passing an array. /// Trigger mode. Could be multiple by passing an array.
/// </summary> /// </summary>
[Parameter] [Parameter]
public TriggerType[] Trigger { get; set; } = new TriggerType[] { TriggerType.Hover }; public Trigger[] Trigger //TODO: this should probably be a flag not an array
{
get { return _trigger.Select(t => t.Trigger).ToArray(); }
set
{
_trigger = value.Select(t => TriggerType.Create(t)).ToArray();
}
}
[Parameter] [Parameter]
public string TriggerCls { get; set; } public string TriggerCls { get; set; }
@ -204,6 +216,8 @@ namespace AntDesign.Internal
set => Ref = value; set => Ref = value;
} }
internal TriggerType[] GetTriggerType() => _trigger;
/// <summary> /// <summary>
/// ChildElement with ElementReference set to avoid wrapping div. /// ChildElement with ElementReference set to avoid wrapping div.
/// </summary> /// </summary>
@ -217,21 +231,31 @@ namespace AntDesign.Internal
public bool Visible { get; set; } = false; public bool Visible { get; set; } = false;
[Inject] [Inject]
protected DomEventService DomEventService { get; set; } protected IDomEventListener DomEventListener { get; set; }
private bool _mouseInTrigger = false; private bool _mouseInTrigger = false;
private bool _mouseInOverlay = false; private bool _mouseInOverlay = false;
private bool _mouseUpInOverlay = false; private bool _mouseUpInOverlay = false;
protected Overlay _overlay = null; protected Overlay _overlay = null;
private TriggerType[] _trigger = new TriggerType[] { TriggerType.Hover };
private bool _shouldRender = true;
internal void SetShouldRender(bool shouldRender) => _shouldRender = shouldRender;
protected override bool ShouldRender()
{
if (_shouldRender)
return base.ShouldRender();
_shouldRender = true;
return false;
}
protected override void OnAfterRender(bool firstRender) protected override void OnAfterRender(bool firstRender)
{ {
if (firstRender) if (firstRender)
{ {
DomEventService.AddEventListener("document", "mouseup", OnMouseUp, false); DomEventListener.AddShared<JsonElement>("document", "mouseup", OnMouseUp);
DomEventService.AddEventListener("window", "resize", OnWindowResize, false);
DomEventService.AddEventListener("document", "scroll", OnWindowScroll, false);
} }
base.OnAfterRender(firstRender); base.OnAfterRender(firstRender);
@ -245,15 +269,14 @@ namespace AntDesign.Internal
{ {
Ref = RefBack.Current; Ref = RefBack.Current;
DomEventService.AddEventListener(Ref, "click", OnUnboundClick, true); DomEventListener.AddExclusive<JsonElement>(Ref, "click", OnUnboundClick);
DomEventService.AddEventListener(Ref, "mouseover", OnUnboundMouseEnter, true); DomEventListener.AddExclusive<JsonElement>(Ref, "mouseover", OnUnboundMouseEnter);
DomEventService.AddEventListener(Ref, "mouseout", OnUnboundMouseLeave, true); DomEventListener.AddExclusive<JsonElement>(Ref, "mouseout", OnUnboundMouseLeave);
DomEventService.AddEventListener(Ref, "focusin", OnUnboundFocusIn, true); DomEventListener.AddExclusive<JsonElement>(Ref, "focusin", OnUnboundFocusIn);
DomEventService.AddEventListener(Ref, "focusout", OnUnboundFocusOut, true); DomEventListener.AddExclusive<JsonElement>(Ref, "focusout", OnUnboundFocusOut);
DomEventService.AddEventListener(Ref, "contextmenu", OnContextMenu, true, true); DomEventListener.AddExclusive<JsonElement>(Ref, "contextmenu", OnContextMenu, true);
} }
if (!string.IsNullOrWhiteSpace(TriggerCls)) if (!string.IsNullOrWhiteSpace(TriggerCls))
{ {
if (Unbound != null) if (Unbound != null)
@ -295,19 +318,8 @@ namespace AntDesign.Internal
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
DomEventService.RemoveEventListerner<JsonElement>("document", "mouseup", OnMouseUp); DomEventListener.Dispose();
DomEventService.RemoveEventListerner<JsonElement>("window", "resize", OnWindowResize);
DomEventService.RemoveEventListerner<JsonElement>("document", "scroll", OnWindowScroll);
if (Unbound != null)
{
DomEventService.RemoveEventListerner<JsonElement>(Ref, "click", OnUnboundClick);
DomEventService.RemoveEventListerner<JsonElement>(Ref, "mouseover", OnUnboundMouseEnter);
DomEventService.RemoveEventListerner<JsonElement>(Ref, "mouseout", OnUnboundMouseLeave);
DomEventService.RemoveEventListerner<JsonElement>(Ref, "focusin", OnUnboundFocusIn);
DomEventService.RemoveEventListerner<JsonElement>(Ref, "focusout", OnUnboundFocusOut);
DomEventService.RemoveEventListerner<JsonElement>(Ref, "contextmenu", OnContextMenu);
}
base.Dispose(disposing); base.Dispose(disposing);
} }
@ -321,8 +333,13 @@ namespace AntDesign.Internal
await Show(); await Show();
} }
else
{
_shouldRender = false;
}
OnMouseEnter?.Invoke(); if (OnMouseEnter.HasDelegate)
OnMouseEnter.InvokeAsync(null);
} }
protected virtual async Task OnTriggerMouseLeave() protected virtual async Task OnTriggerMouseLeave()
@ -335,8 +352,13 @@ namespace AntDesign.Internal
await Hide(); await Hide();
} }
else
{
_shouldRender = false;
}
OnMouseLeave?.Invoke(); if (OnMouseLeave.HasDelegate)
OnMouseLeave.InvokeAsync(null);
} }
protected virtual async Task OnTriggerFocusIn() protected virtual async Task OnTriggerFocusIn()
@ -432,8 +454,8 @@ namespace AntDesign.Internal
{ {
int offsetX = 10; int offsetX = 10;
int offsetY = 10; int offsetY = 10;
#if NET5_0 #if NET5_0_OR_GREATER
// offsetX/offsetY were only supported in Net5 // offsetX/offsetY were only supported in Net5 or grater
offsetX = (int)args.OffsetX; offsetX = (int)args.OffsetX;
offsetY = (int)args.OffsetY; offsetY = (int)args.OffsetY;
#endif #endif
@ -462,24 +484,10 @@ namespace AntDesign.Internal
} }
} }
protected async void OnWindowResize(JsonElement element)
{
RestorePlacement();
if (IsOverlayShow())
{
await GetOverlayComponent().UpdatePosition();
}
}
protected void OnWindowScroll(JsonElement element)
{
RestorePlacement();
}
protected virtual bool IsContainTrigger(TriggerType triggerType) protected virtual bool IsContainTrigger(TriggerType triggerType)
{ {
return Trigger.Contains(triggerType); return _trigger.Contains(triggerType);
} }
protected virtual async Task OverlayVisibleChange(bool visible) protected virtual async Task OverlayVisibleChange(bool visible)
@ -505,18 +513,13 @@ namespace AntDesign.Internal
_placement = placement; _placement = placement;
} }
internal void RestorePlacement()
{
_placement = _paramPlacement;
}
internal virtual string GetPlacementClass() internal virtual string GetPlacementClass()
{ {
if (!string.IsNullOrEmpty(PlacementCls)) if (!string.IsNullOrEmpty(PlacementCls))
{ {
return PlacementCls; return PlacementCls;
} }
return $"{PrefixCls}-placement-{Placement.Name}"; return $"{PrefixCls}-placement-{_placement.Name}";
} }
internal virtual string GetOverlayEnterClass() internal virtual string GetOverlayEnterClass()
@ -525,7 +528,7 @@ namespace AntDesign.Internal
{ {
return OverlayEnterCls; return OverlayEnterCls;
} }
return $"ant-slide-{Placement.SlideName}-enter ant-slide-{Placement.SlideName}-enter-active ant-slide-{Placement.SlideName}"; return $"ant-slide-{_placement.SlideName}-enter ant-slide-{_placement.SlideName}-enter-active ant-slide-{_placement.SlideName}";
} }
internal virtual string GetOverlayLeaveClass() internal virtual string GetOverlayLeaveClass()
@ -534,7 +537,7 @@ namespace AntDesign.Internal
{ {
return OverlayLeaveCls; return OverlayLeaveCls;
} }
return $"ant-slide-{Placement.SlideName}-leave ant-slide-{Placement.SlideName}-leave-active ant-slide-{Placement.SlideName}"; return $"ant-slide-{_placement.SlideName}-leave ant-slide-{_placement.SlideName}-leave-active ant-slide-{_placement.SlideName}";
} }
internal virtual string GetOverlayHiddenClass() internal virtual string GetOverlayHiddenClass()

View File

@ -1,32 +1,72 @@
namespace AntDesign namespace AntDesign
{ {
public enum Placement
{
TopLeft,
TopCenter,
Top,
TopRight,
Left,
LeftTop,
LeftBottom,
Right,
RightTop,
RightBottom,
BottomLeft,
BottomCenter,
Bottom,
BottomRight
}
public sealed class PlacementType : EnumValue<PlacementType> public sealed class PlacementType : EnumValue<PlacementType>
{ {
public static readonly PlacementType TopLeft = new PlacementType("topLeft", "down", "33% 100%", 0); public static readonly PlacementType TopLeft = new PlacementType("topLeft", "down", "33% 100%", 0, Placement.TopLeft);
public static readonly PlacementType TopCenter = new PlacementType("topCenter", "down", "50% 100%", 1); public static readonly PlacementType TopCenter = new PlacementType("topCenter", "down", "50% 100%", 1, Placement.TopCenter);
public static readonly PlacementType Top = new PlacementType("top", "down", "50% 100%", 1); public static readonly PlacementType Top = new PlacementType("top", "down", "50% 100%", 1, Placement.Top);
public static readonly PlacementType TopRight = new PlacementType("topRight", "down", "66% 100%", 2); public static readonly PlacementType TopRight = new PlacementType("topRight", "down", "66% 100%", 2, Placement.TopRight);
public static readonly PlacementType Left = new PlacementType("left", "up", "100% 50%%", 3); public static readonly PlacementType Left = new PlacementType("left", "up", "100% 50%%", 3, Placement.Left);
public static readonly PlacementType LeftTop = new PlacementType("leftTop", "down", "100% 33%", 4); public static readonly PlacementType LeftTop = new PlacementType("leftTop", "down", "100% 33%", 4, Placement.LeftTop);
public static readonly PlacementType LeftBottom = new PlacementType("leftBottom", "up", "100% 66%", 5); public static readonly PlacementType LeftBottom = new PlacementType("leftBottom", "up", "100% 66%", 5, Placement.LeftBottom);
public static readonly PlacementType Right = new PlacementType("right", "up", "0 50%", 6); public static readonly PlacementType Right = new PlacementType("right", "up", "0 50%", 6, Placement.Right);
public static readonly PlacementType RightTop = new PlacementType("rightTop", "down", "0 33%", 7); public static readonly PlacementType RightTop = new PlacementType("rightTop", "down", "0 33%", 7, Placement.RightTop);
public static readonly PlacementType RightBottom = new PlacementType("rightBottom", "up", "0 66%", 8); public static readonly PlacementType RightBottom = new PlacementType("rightBottom", "up", "0 66%", 8, Placement.RightBottom);
public static readonly PlacementType BottomLeft = new PlacementType("bottomLeft", "up", "33% 0", 9); public static readonly PlacementType BottomLeft = new PlacementType("bottomLeft", "up", "33% 0", 9, Placement.BottomLeft);
public static readonly PlacementType BottomCenter = new PlacementType("bottomCenter", "up", "50% 0", 10); public static readonly PlacementType BottomCenter = new PlacementType("bottomCenter", "up", "50% 0", 10, Placement.BottomCenter);
public static readonly PlacementType Bottom = new PlacementType("bottom", "up", "50% 0", 10); public static readonly PlacementType Bottom = new PlacementType("bottom", "up", "50% 0", 10, Placement.Bottom);
public static readonly PlacementType BottomRight = new PlacementType("bottomRight", "up", "66% 0", 11); public static readonly PlacementType BottomRight = new PlacementType("bottomRight", "up", "66% 0", 11, Placement.BottomRight);
public static PlacementType Create(Placement placement) => placement switch
{
Placement.TopLeft => PlacementType.TopLeft,
Placement.TopCenter => PlacementType.TopCenter,
Placement.Top => PlacementType.Top,
Placement.TopRight => PlacementType.TopRight,
Placement.Left => PlacementType.Left,
Placement.LeftTop => PlacementType.LeftTop,
Placement.LeftBottom => PlacementType.LeftBottom,
Placement.Right => PlacementType.Right,
Placement.RightTop => PlacementType.RightTop,
Placement.RightBottom => PlacementType.RightBottom,
Placement.BottomLeft => PlacementType.BottomLeft,
Placement.BottomCenter => PlacementType.BottomCenter,
Placement.Bottom => PlacementType.Bottom,
Placement.BottomRight => PlacementType.BottomRight,
_ => PlacementType.BottomLeft
};
public string SlideName { get; private set; } public string SlideName { get; private set; }
public string TranformOrigin { get; private set; } public string TranformOrigin { get; private set; }
public PlacementType(string name, string slideName, string transformOrigin, int value) : base(name, value) public Placement Placement { get; private set; } = Placement.BottomLeft;
private PlacementType(string name, string slideName, string transformOrigin, int value, Placement placement) : base(name, value)
{ {
SlideName = slideName; SlideName = slideName;
TranformOrigin = transformOrigin; TranformOrigin = transformOrigin;
Placement = placement;
} }
internal PlacementType GetReverseType() internal PlacementType GetReverseType()

View File

@ -9,12 +9,18 @@
None, None,
/// <summary> /// <summary>
/// 在可视范围内(默认模式) /// 在可视范围内(默认模式)
/// in view(default mode) /// The default, the viewport boundaries are the boundaries that are used for calculation if overlay
/// is fully visible.
/// Attempt to fit the overlay so it is always fully visible in the viewport.
/// So if the overlay is outside of the viewport ("overflows"), then reposition calculation is going
/// to be attempted.
/// </summary> /// </summary>
InView, InView,
/// <summary> /// <summary>
/// 在滚动范围内 /// 在滚动范围内
/// in scroll /// The document boundaries are the boundaries used for calculation if overlay needs to be reposition.
/// So even if the overlay is outside of the viewport, the overlay may still be shown as long as it
/// does not "overflow" the document boundaries.
/// </summary> /// </summary>
InScroll, InScroll,
} }

View File

@ -1,14 +1,41 @@
namespace AntDesign using System;
using System.ComponentModel;
namespace AntDesign
{ {
[Flags]
public enum Trigger
{
Click = 1 << 0,
Hover = 1 << 1,
Focus = 1 << 2,
ContextMenu = 1 << 3
}
public sealed class TriggerType : EnumValue<TriggerType> public sealed class TriggerType : EnumValue<TriggerType>
{ {
public static readonly TriggerType Click = new TriggerType(nameof(Click), 0); public static readonly TriggerType Click = new TriggerType(nameof(Click), 0, Trigger.Click);
public static readonly TriggerType Hover = new TriggerType(nameof(Hover), 1); public static readonly TriggerType Hover = new TriggerType(nameof(Hover), 1, Trigger.Hover);
public static readonly TriggerType Focus = new TriggerType(nameof(Focus), 2); public static readonly TriggerType Focus = new TriggerType(nameof(Focus), 2, Trigger.Focus);
public static readonly TriggerType ContextMenu = new TriggerType(nameof(ContextMenu), 3); public static readonly TriggerType ContextMenu = new TriggerType(nameof(ContextMenu), 3, Trigger.ContextMenu);
private TriggerType(string name, int value) : base(name, value) public static TriggerType Create(Trigger trigger)
{ {
return trigger switch
{
Trigger.Click => Click,
Trigger.Hover => Hover,
Trigger.Focus => Focus,
Trigger.ContextMenu => ContextMenu,
_ => throw new InvalidEnumArgumentException($"Unrecognized value of Trigger enum ({trigger}).")
};
} }
private TriggerType(string name, int value, Trigger trigger) : base(name, value)
{
Trigger = trigger;
}
public Trigger Trigger { get; private set; }
} }
} }

View File

@ -4,6 +4,7 @@ using AntDesign;
using AntDesign.JsInterop; using AntDesign.JsInterop;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.JSInterop;
namespace Microsoft.Extensions.DependencyInjection namespace Microsoft.Extensions.DependencyInjection
{ {
@ -12,6 +13,12 @@ namespace Microsoft.Extensions.DependencyInjection
public static IServiceCollection AddAntDesign(this IServiceCollection services) public static IServiceCollection AddAntDesign(this IServiceCollection services)
{ {
services.TryAddScoped<DomEventService>(); services.TryAddScoped<DomEventService>();
services.TryAddTransient<IDomEventListener>((sp) =>
{
var domEventService = sp.GetRequiredService<DomEventService>();
return domEventService.CreateDomEventListerner();
});
services.TryAddScoped(sp => new HtmlRenderService(new HtmlRenderer(sp, sp.GetRequiredService<ILoggerFactory>(), services.TryAddScoped(sp => new HtmlRenderService(new HtmlRenderer(sp, sp.GetRequiredService<ILoggerFactory>(),
s => HtmlEncoder.Default.Encode(s))) s => HtmlEncoder.Default.Encode(s)))
); );

View File

@ -0,0 +1,255 @@
using System;
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
{
public class DomEventListener : IDomEventListener
{
private Dictionary<string, IDisposable> _dotNetObjectStore = new();
private bool? _isResizeObserverSupported = null;
private readonly IJSRuntime _jsRuntime;
private readonly DomEventSubscriptionStore _domEventSubscriptionsStore;
private readonly string _id;
public DomEventListener(IJSRuntime jsRuntime, DomEventSubscriptionStore domEventSubscriptionStore)
{
_jsRuntime = jsRuntime;
_domEventSubscriptionsStore = domEventSubscriptionStore;
_id = Guid.NewGuid().ToString();
}
private string FormatKey(object dom, string eventName)
{
var selector = dom is ElementReference eleRef ? eleRef.Id : dom.ToString();
if (selector.IsIn("window", "document"))
{
return $"DEL-{selector}-{eventName}";
}
return $"DEL-{_id}-{selector}-{eventName}";
}
public void AddExclusive<T>(object dom, string eventName, Action<T> callback, bool preventDefault = false)
{
var key = FormatKey(dom, eventName);
if (_dotNetObjectStore.ContainsKey(key))
return;
var dotNetObject = DotNetObjectReference.Create(new Invoker<T>((p) =>
{
callback(p);
}));
_jsRuntime.InvokeAsync<string>(JSInteropConstants.AddDomEventListener, dom, eventName, preventDefault, dotNetObject);
_dotNetObjectStore.Add(key, dotNetObject);
}
public void RemoveExclusive(object dom, string eventName)
{
var key = FormatKey(dom, eventName);
if (_dotNetObjectStore.TryGetValue(key, out IDisposable value))
{
value.Dispose();
}
_dotNetObjectStore.Remove(key);
}
public void DisposeExclusive()
{
foreach (var (k, v) in _dotNetObjectStore)
{
v.Dispose();
}
_dotNetObjectStore.Clear();
}
#region SharedEventListerner
public virtual void AddShared<T>(object dom, string eventName, Action<T> callback, bool preventDefault = false)
{
string key = FormatKey(dom, eventName);
if (!_domEventSubscriptionsStore.ContainsKey(key))
{
_domEventSubscriptionsStore[key] = new List<DomEventSubscription>();
var dotNetObject = DotNetObjectReference.Create(new Invoker<string>((p) =>
{
for (var i = 0; i < _domEventSubscriptionsStore[key].Count; i++)
{
var subscription = _domEventSubscriptionsStore[key][i];
object tP = JsonSerializer.Deserialize(p, subscription.Type);
subscription.Delegate.DynamicInvoke(tP);
}
}));
_jsRuntime.InvokeAsync<string>(JSInteropConstants.AddDomEventListener, dom, eventName, preventDefault, dotNetObject);
}
_domEventSubscriptionsStore[key].Add(new DomEventSubscription(callback, typeof(T), _id));
}
public void RemoveShared<T>(object dom, string eventName, Action<T> callback)
{
string key = FormatKey(dom, eventName);
if (_domEventSubscriptionsStore.ContainsKey(key))
{
var subscription = _domEventSubscriptionsStore[key].SingleOrDefault(s => s.Delegate == (Delegate)callback);
if (subscription != null && subscription.Id == _id)
{
_domEventSubscriptionsStore[key].Remove(subscription);
}
}
}
public void DisposeShared()
{
bool find = true;
while (find)
{
var (key, subscription) = _domEventSubscriptionsStore.FindDomEventSubscription(_id);
if (!string.IsNullOrEmpty(key) && subscription != null)
{
_domEventSubscriptionsStore[key].Remove(subscription);
}
else
find = false;
}
}
#endregion
#region ResizeObserver
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() });
AddShared<JsonElement>("window", "resize", action, false);
}
else
{
if (!_domEventSubscriptionsStore.ContainsKey(key))
{
_domEventSubscriptionsStore[key] = new List<DomEventSubscription>();
await _jsRuntime.InvokeVoidAsync(JSInteropConstants.ObserverConstants.Resize.Create, key, DotNetObjectReference.Create(new Invoker<string>((p) =>
{
for (var i = 0; i < _domEventSubscriptionsStore[key].Count; i++)
{
var subscription = _domEventSubscriptionsStore[key][i];
object tP = JsonSerializer.Deserialize(p, subscription.Type);
subscription.Delegate.DynamicInvoke(tP);
}
})));
await _jsRuntime.InvokeVoidAsync(JSInteropConstants.ObserverConstants.Resize.Observe, key, dom);
}
_domEventSubscriptionsStore[key].Add(new DomEventSubscription(callback, typeof(List<ResizeObserverEntry>), _id));
}
}
public async ValueTask RemoveResizeObserver(ElementReference dom, Action<List<ResizeObserverEntry>> callback)
{
string key = FormatKey(dom.Id, nameof(JSInteropConstants.ObserverConstants.Resize));
if (_domEventSubscriptionsStore.ContainsKey(key))
{
var subscription = _domEventSubscriptionsStore[key].SingleOrDefault(s => s.Delegate == (Delegate)callback);
if (subscription != null)
{
_domEventSubscriptionsStore[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);
}
_domEventSubscriptionsStore.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 (_domEventSubscriptionsStore.ContainsKey(key))
{
_domEventSubscriptionsStore[key].Clear();
}
}
private async ValueTask<bool> IsResizeObserverSupported() => _isResizeObserverSupported ??= await _jsRuntime.IsResizeObserverSupported();
#endregion
#region EventListenerToFirstChild
public void AddEventListenerToFirstChild(object dom, string eventName, Action<JsonElement> callback, bool preventDefault = false)
{
AddEventListenerToFirstChildInternal<string>(dom, eventName, preventDefault, (e) =>
{
JsonElement jsonElement = JsonDocument.Parse(e).RootElement;
callback(jsonElement);
});
}
public void AddEventListenerToFirstChild<T>(object dom, string eventName, Action<T> callback, bool preventDefault = false)
{
AddEventListenerToFirstChildInternal<string>(dom, eventName, preventDefault, (e) =>
{
T obj = JsonSerializer.Deserialize<T>(e);
callback(obj);
});
}
private void AddEventListenerToFirstChildInternal<T>(object dom, string eventName, bool preventDefault, Action<T> callback)
{
var key = FormatKey(dom, eventName);
if (!_dotNetObjectStore.ContainsKey(key))
{
var dotNetObject = DotNetObjectReference.Create(new Invoker<T>((p) =>
{
callback?.Invoke(p);
}));
_jsRuntime.InvokeAsync<string>(JSInteropConstants.AddDomEventListenerToFirstChild, dom, eventName, preventDefault, dotNetObject);
_dotNetObjectStore.Add(key, dotNetObject);
}
}
#endregion
public void Dispose()
{
DisposeExclusive();
DisposeShared();
}
}
public class Invoker<T>
{
private Action<T> _action;
public Invoker(Action<T> invoker)
{
_action = invoker;
}
[JSInvokable]
public void Invoke(T param)
{
_action.Invoke(param);
}
}
}

View File

@ -1,187 +1,21 @@
using System; using Microsoft.JSInterop;
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 namespace AntDesign.JsInterop
{ {
public class DomEventService public class DomEventService
{ {
private ConcurrentDictionary<string, List<DomEventSubscription>> _domEventListeners = new ConcurrentDictionary<string, List<DomEventSubscription>>(); private DomEventSubscriptionStore _domEventSubscriptionStore = new();
private readonly IJSRuntime _jsRuntime; private readonly IJSRuntime _jsRuntime;
private bool? _isResizeObserverSupported = null;
public DomEventService(IJSRuntime jsRuntime) public DomEventService(IJSRuntime jsRuntime)
{ {
_jsRuntime = jsRuntime; _jsRuntime = jsRuntime;
} }
private void AddEventListenerToFirstChildInternal<T>(object dom, string eventName, bool preventDefault, Action<T> callback) public virtual IDomEventListener CreateDomEventListerner()
{ {
if (!_domEventListeners.ContainsKey(FormatKey(dom, eventName))) return new DomEventListener(_jsRuntime, _domEventSubscriptionStore);
{
_jsRuntime.InvokeAsync<string>(JSInteropConstants.AddDomEventListenerToFirstChild, dom, eventName, preventDefault, DotNetObjectReference.Create(new Invoker<T>((p) =>
{
callback?.Invoke(p);
})));
}
}
public void AddEventListener(object dom, string eventName, Action<JsonElement> callback, bool exclusive = true, bool preventDefault = false)
{
AddEventListener<JsonElement>(dom, eventName, callback, exclusive, preventDefault);
}
public virtual void AddEventListener<T>(object dom, string eventName, Action<T> callback, bool exclusive = true, bool preventDefault = false)
{
if (exclusive)
{
_jsRuntime.InvokeAsync<string>(JSInteropConstants.AddDomEventListener, dom, eventName, preventDefault, DotNetObjectReference.Create(new Invoker<T>((p) =>
{
callback(p);
})));
}
else
{
string key = FormatKey(dom, eventName);
if (!_domEventListeners.ContainsKey(key))
{
_domEventListeners[key] = new List<DomEventSubscription>();
_jsRuntime.InvokeAsync<string>(JSInteropConstants.AddDomEventListener, dom, eventName, preventDefault, 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);
}
})));
}
_domEventListeners[key].Add(new DomEventSubscription(callback, typeof(T)));
}
}
public void AddEventListenerToFirstChild(object dom, string eventName, Action<JsonElement> callback, bool preventDefault = false)
{
AddEventListenerToFirstChildInternal<string>(dom, eventName, preventDefault, (e) =>
{
JsonElement jsonElement = JsonDocument.Parse(e).RootElement;
callback(jsonElement);
});
}
public void AddEventListenerToFirstChild<T>(object dom, string eventName, Action<T> callback, bool preventDefault = false)
{
AddEventListenerToFirstChildInternal<string>(dom, eventName, preventDefault, (e) =>
{
T obj = JsonSerializer.Deserialize<T>(e);
callback(obj);
});
}
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)
{
string key = FormatKey(dom, eventName);
if (_domEventListeners.ContainsKey(key))
{
var subscription = _domEventListeners[key].SingleOrDefault(s => s.Delegate == (Delegate)callback);
if (subscription != null)
{
_domEventListeners[key].Remove(subscription);
}
}
}
private async ValueTask<bool> IsResizeObserverSupported() => _isResizeObserverSupported ??= await _jsRuntime.IsResizeObserverSupported();
}
public class Invoker<T>
{
private Action<T> _action;
public Invoker(Action<T> invoker)
{
this._action = invoker;
}
[JSInvokable]
public void Invoke(T param)
{
_action.Invoke(param);
} }
} }
} }

View File

@ -1,16 +1,47 @@
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace AntDesign.JsInterop namespace AntDesign.JsInterop
{ {
internal class DomEventSubscription public class DomEventSubscriptionStore : ConcurrentDictionary<string, List<DomEventSubscription>>
{
public (string key, DomEventSubscription subscription) FindDomEventSubscription(string id)
{
string key = string.Empty;
DomEventSubscription subscription = null;
foreach (var (k, subscriptionList) in this)
{
key = k;
foreach (var item in subscriptionList)
{
if (item.Id == id)
{
subscription = item;
break;
}
}
if (subscription != null)
break;
}
return (key, subscription);
}
}
public class DomEventSubscription
{ {
internal Delegate Delegate { get; set; } internal Delegate Delegate { get; set; }
internal Type Type { get; set; } internal Type Type { get; set; }
internal string Id { get; set; }
public DomEventSubscription(Delegate @delegate, Type type) public DomEventSubscription(Delegate @delegate, Type type, string id)
{ {
Delegate = @delegate; Delegate = @delegate;
Type = type; Type = type;
Id = id;
} }
} }
} }

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
using AntDesign.Core.JsInterop.ObservableApi;
using Microsoft.AspNetCore.Components;
namespace AntDesign.JsInterop
{
public interface IDomEventListener
{
void AddEventListenerToFirstChild(object dom, string eventName, Action<JsonElement> callback, bool preventDefault = false);
void AddEventListenerToFirstChild<T>(object dom, string eventName, Action<T> callback, bool preventDefault = false);
void AddExclusive<T>(object dom, string eventName, Action<T> callback, bool preventDefault = false);
ValueTask AddResizeObserver(ElementReference dom, Action<List<ResizeObserverEntry>> callback);
void AddShared<T>(object dom, string eventName, Action<T> callback, bool preventDefault = false);
ValueTask DisconnectResizeObserver(ElementReference dom);
void DisposeExclusive();
ValueTask DisposeResizeObserver(ElementReference dom);
void RemoveExclusive(object dom, string eventName);
ValueTask RemoveResizeObserver(ElementReference dom, Action<List<ResizeObserverEntry>> callback);
void RemoveShared<T>(object dom, string eventName, Action<T> callback);
void DisposeShared();
void Dispose();
}
}

View File

@ -15,6 +15,8 @@ namespace AntDesign
public static string GetScroll => DomInfoHelper.GetScroll; public static string GetScroll => DomInfoHelper.GetScroll;
public static string HasFocus => DomInfoHelper.HasFocus; public static string HasFocus => DomInfoHelper.HasFocus;
public static string GetInnerText => DomInfoHelper.GetInnerText; public static string GetInnerText => DomInfoHelper.GetInnerText;
public static string GetMaxZIndex => DomInfoHelper.GetMaxZIndex;
#endregion #endregion
#region styleManipulation #region styleManipulation
@ -87,12 +89,12 @@ namespace AntDesign
#region overlay #region overlay
public static string AddPreventEnterOnOverlayVisible => OverlayComponentHelper.AddPreventEnterOnOverlayVisible; public static string AddPreventEnterOnOverlayVisible => OverlayComponentHelper.AddPreventEnterOnOverlayVisible;
public static string RemovePreventEnterOnOverlayVisible => OverlayComponentHelper.RemovePreventEnterOnOverlayVisible; public static string RemovePreventEnterOnOverlayVisible => OverlayComponentHelper.RemovePreventEnterOnOverlayVisible;
public static string GetMaxZIndex => OverlayComponentHelper.GetMaxZIndex; //public static string AddOverlayToContainer => OverlayComponentHelper.AddOverlayToContainer;
#endregion #endregion
#region table #region table
public static string BindTableHeaderAndBodyScroll => TableComponentHelper.BindTableHeaderAndBodyScroll; public static string BindTableScroll => TableComponentHelper.BindTableScroll;
public static string UnbindTableHeaderAndBodyScroll => TableComponentHelper.UnbindTableHeaderAndBodyScroll; public static string UnbindTableScroll => TableComponentHelper.UnbindTableScroll;
#endregion #endregion
public static string DisposeObj => $"{FUNC_PREFIX}state.disposeObj"; public static string DisposeObj => $"{FUNC_PREFIX}state.disposeObj";
@ -109,6 +111,7 @@ namespace AntDesign
public static string GetScroll => $"{FUNC_PREFIX}getScroll"; public static string GetScroll => $"{FUNC_PREFIX}getScroll";
public static string HasFocus => $"{FUNC_PREFIX}hasFocus"; public static string HasFocus => $"{FUNC_PREFIX}hasFocus";
public static string GetInnerText => $"{FUNC_PREFIX}getInnerText"; public static string GetInnerText => $"{FUNC_PREFIX}getInnerText";
public static string GetMaxZIndex => $"{FUNC_PREFIX}getMaxZIndex";
} }
public static class EventHelper public static class EventHelper
@ -184,6 +187,7 @@ namespace AntDesign
{ {
private const string FUNC_PREFIX = JSInteropConstants.FUNC_PREFIX + "inputHelper."; private const string FUNC_PREFIX = JSInteropConstants.FUNC_PREFIX + "inputHelper.";
public static string RegisterResizeTextArea => $"{FUNC_PREFIX}registerResizeTextArea"; public static string RegisterResizeTextArea => $"{FUNC_PREFIX}registerResizeTextArea";
public static string GetTextAreaInfo => $"{FUNC_PREFIX}getTextAreaInfo";
public static string DisposeResizeTextArea => $"{FUNC_PREFIX}disposeResizeTextArea"; public static string DisposeResizeTextArea => $"{FUNC_PREFIX}disposeResizeTextArea";
public static string SetSelectionStart => $"{FUNC_PREFIX}setSelectionStart"; public static string SetSelectionStart => $"{FUNC_PREFIX}setSelectionStart";
} }
@ -210,14 +214,16 @@ namespace AntDesign
private const string FUNC_PREFIX = JSInteropConstants.FUNC_PREFIX + "overlayHelper."; private const string FUNC_PREFIX = JSInteropConstants.FUNC_PREFIX + "overlayHelper.";
public static string AddPreventEnterOnOverlayVisible => $"{FUNC_PREFIX}addPreventEnterOnOverlayVisible"; public static string AddPreventEnterOnOverlayVisible => $"{FUNC_PREFIX}addPreventEnterOnOverlayVisible";
public static string RemovePreventEnterOnOverlayVisible => $"{FUNC_PREFIX}removePreventEnterOnOverlayVisible"; public static string RemovePreventEnterOnOverlayVisible => $"{FUNC_PREFIX}removePreventEnterOnOverlayVisible";
public static string GetMaxZIndex => $"{FUNC_PREFIX}getMaxZIndex"; public static string AddOverlayToContainer => $"{FUNC_PREFIX}addOverlayToContainer";
public static string UpdateOverlayPosition => $"{FUNC_PREFIX}updateOverlayPosition";
public static string DeleteOverlayFromContainer => $"{FUNC_PREFIX}deleteOverlayFromContainer";
} }
public static class TableComponentHelper public static class TableComponentHelper
{ {
private const string FUNC_PREFIX = JSInteropConstants.FUNC_PREFIX + "tableHelper."; private const string FUNC_PREFIX = JSInteropConstants.FUNC_PREFIX + "tableHelper.";
public static string BindTableHeaderAndBodyScroll => $"{FUNC_PREFIX}bindTableHeaderAndBodyScroll"; public static string BindTableScroll => $"{FUNC_PREFIX}bindTableScroll";
public static string UnbindTableHeaderAndBodyScroll => $"{FUNC_PREFIX}unbindTableHeaderAndBodyScroll"; public static string UnbindTableScroll => $"{FUNC_PREFIX}unbindTableScroll";
} }
public static class UploadComponentHelper public static class UploadComponentHelper

View File

@ -0,0 +1,45 @@
import { infoHelper as domInfoHelper } from '../modules/dom/infoHelper';
export class intersectionObserver {
// @ts-ignore: TS2304: Cannot find name 'IntersectionObserver'
private static intersectionObservers: Map<string, IntersectionObserver> = new Map<string, IntersectionObserver>();
static create(key: string, invoker, isDotNetInvoker: boolean = true) {
// @ts-ignore: TS2304: Cannot find name 'IntersectionObserver'
let observer;
if (isDotNetInvoker) {
observer = new IntersectionObserver(mutations => intersectionObserver.observerCallback(mutations, invoker))
} else {
observer = new IntersectionObserver(mutations => invoker(mutations))
}
intersectionObserver.intersectionObservers.set(key, observer)
}
static observe(key: string, element, options?: IntersectionObserverInit) {
const observer = intersectionObserver.intersectionObservers.get(key);
if (observer) {
let domElement = domInfoHelper.get(element);
observer.observe(domElement);
}
}
static disconnect(key: string): void {
const observer = this.intersectionObservers.get(key)
if (observer) {
observer.disconnect()
}
}
static dispose(key: string): void {
this.disconnect(key)
this.intersectionObservers.delete(key)
}
private static observerCallback(mutations, invoker) {
//TODO: serialize a proper object (check resizeObserver.ts for sample)
const entriesJson = JSON.stringify(mutations)
invoker.invokeMethodAsync('Invoke', entriesJson)
}
}

View File

@ -0,0 +1,45 @@
import { infoHelper as domInfoHelper } from '../modules/dom/infoHelper';
export class mutationObserver {
// @ts-ignore: TS2304: Cannot find name 'MutationObserver'
private static mutationObservers: Map<string, MutationObserver> = new Map<string, MutationObserver>();
static create(key: string, invoker, isDotNetInvoker: boolean = true) {
// @ts-ignore: TS2304: Cannot find name 'MutationObserver'
let observer;
if (isDotNetInvoker) {
observer = new MutationObserver(mutations => mutationObserver.observerCallback(mutations, invoker))
} else {
observer = new MutationObserver(mutations => invoker(mutations))
}
mutationObserver.mutationObservers.set(key, observer)
}
static observe(key: string, element, options?: MutationObserverInit) {
const observer = mutationObserver.mutationObservers.get(key);
if (observer) {
let domElement = domInfoHelper.get(element);
observer.observe(domElement, options);
}
}
static disconnect(key: string): void {
const observer = this.mutationObservers.get(key)
if (observer) {
observer.disconnect()
}
}
static dispose(key: string): void {
this.disconnect(key)
this.mutationObservers.delete(key)
}
private static observerCallback(mutations, invoker) {
//TODO: serialize a proper object (check resizeObserver.ts for sample)
const entriesJson = JSON.stringify(mutations)
invoker.invokeMethodAsync('Invoke', entriesJson)
}
}

View File

@ -1 +1,2 @@
export { resizeObserver as resize } from './resizeObserver'; export { resizeObserver as resize } from './resizeObserver';
export { mutationObserver } from './mutationObserver';

View File

@ -17,9 +17,15 @@ export class resizeObserver {
// @ts-ignore: TS2304: Cannot find name 'ResizeObserver' // @ts-ignore: TS2304: Cannot find name 'ResizeObserver'
private static resizeObservers: Map<string, ResizeObserver> = new Map<string, ResizeObserver>(); private static resizeObservers: Map<string, ResizeObserver> = new Map<string, ResizeObserver>();
static create(key, invoker) { static create(key, invoker, isDotNetInvoker: boolean = true ) {
// @ts-ignore: TS2304: Cannot find name 'ResizeObserver' // @ts-ignore: TS2304: Cannot find name 'ResizeObserver'
const observer = new ResizeObserver((entries, observer) => resizeObserver.observerCallBack(entries, observer, invoker)); let observer;
if (isDotNetInvoker) {
observer = new ResizeObserver((entries, observer) => resizeObserver.observerCallBack(entries, observer, invoker));
} else {
observer = new ResizeObserver((entries, observer) => invoker(entries, observer));
}
resizeObserver.resizeObservers.set(key, observer) resizeObserver.resizeObservers.set(key, observer)
} }
@ -48,17 +54,14 @@ export class resizeObserver {
} }
static dispose(key: string): void { static dispose(key: string): void {
console.log("dispose", key);
this.disconnect(key) this.disconnect(key)
this.resizeObservers.delete(key) this.resizeObservers.delete(key)
} }
private static observerCallBack(entries, observer, invoker) { private static observerCallBack(entries, observer, invoker) {
console.log("observerCallBack start", entries)
if (invoker) { if (invoker) {
const mappedEntries = new Array<ResizeObserverEntry>() const mappedEntries = new Array<ResizeObserverEntry>()
entries.forEach(entry => { entries.forEach(entry => {
console.log("observerCallBack entry", entry)
if (entry) { if (entry) {
const mEntry = new ResizeObserverEntry() const mEntry = new ResizeObserverEntry()
if (entry.borderBoxSize) { if (entry.borderBoxSize) {

View File

@ -1,13 +1,16 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using System.Text.Json.Serialization;
namespace AntDesign.JsInterop namespace AntDesign.JsInterop
{ {
public class Window public class Window
{ {
public decimal innerHeight { get; set; } [JsonPropertyName("innerWidth")]
public decimal InnerWidth { get; set; }
public decimal innerWidth { get; set; } [JsonPropertyName("innerHeight")]
public decimal InnerHeight { get; set; }
} }
} }

View File

@ -0,0 +1,28 @@
namespace AntDesign.Core.JsInterop.Modules.Components
{
public class OverlayPosition
{
public decimal? Top { get; set; }
public string TopCss => $"top: " + GetAsString(Top);
public decimal? Bottom { get; set; }
public string BottomCss => $"bottom: " + GetAsString(Bottom);
public decimal? Left { get; set; }
public string LeftCss => $"left: " + GetAsString(Left);
public decimal? Right { get; set; }
public string RightCss => $"right: " + GetAsString(Right);
public string PositionCss => ZIndexCss + LeftCss + RightCss + TopCss + BottomCss;
public int ZIndex { get; set; }
public string ZIndexCss => $"z-index:{ZIndex};";
public Placement Placement { get; set; }
private string GetAsString(decimal? value)
{
if (value is null)
return "unset;";
else
return value.ToString() + "px;";
}
}
}

View File

@ -1,10 +1,10 @@
import { domInfoHelper, eventHelper } from '../dom/exports' import { domInfoHelper } from '../dom/exports'
import { state } from '../stateProvider'; import { state } from '../stateProvider';
export class inputHelper { export class inputHelper {
private static getTextAreaInfo(element) { static getTextAreaInfo(element) {
var result = {}; var result = {};
var dom = domInfoHelper.get(element); var dom = domInfoHelper.get(element);
if (!dom) return null; if (!dom) return null;
@ -32,7 +32,7 @@ export class inputHelper {
return result; return result;
} }
static registerResizeTextArea(element, minRows, maxRows, objReference) { static registerResizeTextArea(element: HTMLTextAreaElement, minRows: number, maxRows: number, objReference) {
if (!objReference) { if (!objReference) {
this.disposeResizeTextArea(element); this.disposeResizeTextArea(element);
} }
@ -44,22 +44,25 @@ export class inputHelper {
} }
} }
static disposeResizeTextArea(element) { static disposeResizeTextArea(element: HTMLTextAreaElement) {
element.removeEventListener("input", state.eventCallbackRegistry[element.id + "input"]); element.removeEventListener("input", state.eventCallbackRegistry[element.id + "input"]);
state.objReferenceDict[element.id] = null; state.objReferenceDict[element.id] = null;
state.eventCallbackRegistry[element.id + "input"] = null; state.eventCallbackRegistry[element.id + "input"] = null;
} }
static resizeTextArea(element, minRows, maxRows) { static resizeTextArea(element: HTMLTextAreaElement, minRows: number, maxRows: number) {
var dims = this.getTextAreaInfo(element); let dims = this.getTextAreaInfo(element);
var rowHeight = dims["lineHeight"]; let rowHeight = dims["lineHeight"];
var offsetHeight = dims["paddingTop"] + dims["paddingBottom"] + dims["borderTop"] + dims["borderBottom"]; let offsetHeight = dims["paddingTop"] + dims["paddingBottom"] + dims["borderTop"] + dims["borderBottom"];
var oldHeight = parseFloat(element.style.height); let oldHeight = parseFloat(element.style.height);
element.style.height = 'auto'; //use rows attribute to evaluate real scroll height
let oldRows = element.rows;
element.rows = minRows;
element.style.height = 'auto';
var rows = Math.trunc(element.scrollHeight / rowHeight); var rows = Math.trunc(element.scrollHeight / rowHeight);
rows = Math.max(minRows, rows); element.rows = oldRows;
rows = Math.max(minRows, rows);
var newHeight = 0; var newHeight = 0;
if (rows > maxRows) { if (rows > maxRows) {
rows = maxRows; rows = maxRows;
@ -75,7 +78,7 @@ export class inputHelper {
} }
if (oldHeight !== newHeight) { if (oldHeight !== newHeight) {
let textAreaObj = state.objReferenceDict[element.id]; let textAreaObj = state.objReferenceDict[element.id];
textAreaObj.invokeMethodAsync("ChangeSizeAsyncJs", parseFloat(element.scrollWidth), newHeight); textAreaObj.invokeMethodAsync("ChangeSizeAsyncJs", element.scrollWidth, newHeight);
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,77 @@
import { domInfoHelper, eventHelper } from '../dom/exports' import { domInfoHelper, eventHelper, domManipulationHelper, domTypes } from '../dom/exports'
import { Placement, TriggerBoundyAdjustMode, overlayConstraints, overlayPosition, Overlay } from './overlay'
import { state } from '../stateProvider'; import { state } from '../stateProvider';
export class overlayHelper { export class overlayHelper {
static overlayRegistry: { [key: string]: Overlay} = {};
static addOverlayToContainer(blazorId: string,
overlaySelector, triggerSelector, placement: Placement, containerSelector: string,
triggerBoundyAdjustMode: TriggerBoundyAdjustMode, triggerIsWrappedInDiv: boolean, triggerPrefixCls: string,
verticalOffset: number, horizontalOffset: number, arrowPointAtCenter: boolean,
overlayTop?: number, overlayLeft?: number
): overlayPosition {
const overlayElement = domInfoHelper.get(overlaySelector) as HTMLDivElement;
const containerElement = domInfoHelper.get(containerSelector) as HTMLElement;
const triggerElement = domInfoHelper.get(triggerSelector) as HTMLElement;
if (!domManipulationHelper.addElementTo(overlaySelector, containerElement)) {
console.log("Failed to add overlay. Details:", {
triggerPrefixCls: triggerPrefixCls,
overlaySelector: overlaySelector,
containerElement: containerElement
} );
return null;
}
let overlayPresets: domTypes.position;
if (overlayTop || overlayLeft) {
overlayPresets = { x: overlayLeft, y: overlayTop };
}
let overlayConstraints: overlayConstraints = {
verticalOffset: verticalOffset,
horizontalOffset: horizontalOffset,
arrowPointAtCenter: arrowPointAtCenter
};
let overlay = new Overlay(blazorId, overlayElement, containerElement, triggerElement, placement, triggerBoundyAdjustMode, triggerIsWrappedInDiv, triggerPrefixCls, overlayConstraints);
//register object in store, so it can be retrieved during update/dispose
this.overlayRegistry[blazorId] = overlay;
return overlay.calculatePosition(false, true, overlayPresets);
}
static updateOverlayPosition(blazorId: string, overlaySelector, triggerSelector, placement: Placement, containerSelector: string,
triggerBoundyAdjustMode: TriggerBoundyAdjustMode, triggerIsWrappedInDiv: boolean, triggerPrefixCls: string,
verticalOffset: number, horizontalOffset: number, arrowPointAtCenter: boolean,
overlayTop?: number, overlayLeft?: number): overlayPosition {
const overlay = this.overlayRegistry[blazorId];
if (overlay){
let overlayPresets: domTypes.position;
if (overlayTop || overlayLeft) {
overlayPresets = { x: overlayLeft, y: overlayTop };
}
return overlay.calculatePosition(false, false, overlayPresets);
} else {
//When page is slow, it may happen that rendering of an overlay may not happen, even if
//blazor thinks it did happen. In such a case, when overlay object is not found, just try
//to render it again.
return overlayHelper.addOverlayToContainer(blazorId, overlaySelector, triggerSelector, placement, containerSelector,triggerBoundyAdjustMode, triggerIsWrappedInDiv, triggerPrefixCls,
verticalOffset, horizontalOffset, arrowPointAtCenter,
overlayTop, overlayLeft);
}
}
static deleteOverlayFromContainer(blazorId: string) {
const overlay = this.overlayRegistry[blazorId];
if (overlay) {
overlay.dispose();
delete this.overlayRegistry[blazorId];
}
}
static addPreventEnterOnOverlayVisible(element, overlayElement) { static addPreventEnterOnOverlayVisible(element, overlayElement) {
if (element && overlayElement) { if (element && overlayElement) {
let dom: HTMLElement = domInfoHelper.get(element); let dom: HTMLElement = domInfoHelper.get(element);
@ -21,8 +91,5 @@ export class overlayHelper {
} }
} }
} }
static getMaxZIndex() {
return [...document.all].reduce((r, e) => Math.max(r, +window.getComputedStyle(e).zIndex || 0), 0)
}
} }

View File

@ -1,14 +1,52 @@
export class tableHelper { export class tableHelper {
static bindTableHeaderAndBodyScroll(bodyRef, headerRef) { static bindTableScroll(bodyRef, tableRef, headerRef, scrollX, scrollY) {
bodyRef.bindScrollLeftToHeader = () => { bodyRef.bindScroll = () => {
headerRef.scrollLeft = bodyRef.scrollLeft; if (scrollX) {
tableHelper.SetScrollPositionClassName(bodyRef, tableRef);
}
if (scrollY) {
headerRef.scrollLeft = bodyRef.scrollLeft;
}
} }
bodyRef.addEventListener('scroll', bodyRef.bindScrollLeftToHeader); bodyRef.bindScroll();
bodyRef.addEventListener('scroll', bodyRef.bindScroll);
window.addEventListener('resize', bodyRef.bindScroll);
} }
static unbindTableHeaderAndBodyScroll(bodyRef) { static unbindTableScroll(bodyRef) {
if (bodyRef) { if (bodyRef) {
bodyRef.removeEventListener('scroll', bodyRef.bindScrollLeftToHeader); bodyRef.removeEventListener('scroll', bodyRef.bindScroll);
window.removeEventListener('resize', bodyRef.bindScroll);
} }
} }
static SetScrollPositionClassName(bodyRef, tableRef) {
let scrollLeft = bodyRef.scrollLeft;
let scrollWidth = bodyRef.scrollWidth;
let clientWidth = bodyRef.clientWidth;
let pingLeft = false;
let pingRight = false;
if ((scrollWidth == clientWidth && scrollWidth != 0)) {
pingLeft = false;
pingRight = false;
}
else if (scrollLeft == 0) {
pingLeft = false;
pingRight = true;
}
else if (Math.abs(scrollWidth - (scrollLeft + clientWidth)) <= 1) {
pingRight = false;
pingLeft = true;
}
else {
pingLeft = true;
pingRight = true;
}
pingLeft ? tableRef.classList.add("ant-table-ping-left") : tableRef.classList.remove("ant-table-ping-left");
pingRight ? tableRef.classList.add("ant-table-ping-right") : tableRef.classList.remove("ant-table-ping-right");
}
} }

View File

@ -5,20 +5,20 @@ export class eventHelper {
static triggerEvent(element: HTMLInputElement, eventType: string, eventName: string) { static triggerEvent(element: HTMLInputElement, eventType: string, eventName: string) {
//TODO: replace with event constructors https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent //TODO: replace with event constructors https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent
//Not used //Not used
var evt = document.createEvent(eventType); const evt = document.createEvent(eventType);
evt.initEvent(eventName); evt.initEvent(eventName);
return element.dispatchEvent(evt); return element.dispatchEvent(evt);
} }
static addDomEventListener(element, eventName: string, preventDefault: boolean, invoker: any) { static addDomEventListener(element, eventName: string, preventDefault: boolean, invoker: any) {
let callback = args => { const callback = args => {
const obj = {}; const obj = {};
for (let k in args) { for (let k in args) {
if (k !== 'originalTarget') { //firefox occasionally raises Permission Denied when this property is being stringified if (k !== 'originalTarget') { //firefox occasionally raises Permission Denied when this property is being stringified
obj[k] = args[k]; obj[k] = args[k];
} }
} }
let json = JSON.stringify(obj, (k, v) => { const json = JSON.stringify(obj, (k, v) => {
if (v instanceof Node) return 'Node'; if (v instanceof Node) return 'Node';
if (v instanceof Window) return 'Window'; if (v instanceof Window) return 'Window';
return v; return v;
@ -29,14 +29,14 @@ export class eventHelper {
} }
}; };
if (element == 'window') { if (element === 'window') {
if (eventName == 'resize') { if (eventName === 'resize') {
window.addEventListener(eventName, this.debounce(() => callback({ innerWidth: window.innerWidth, innerHeight: window.innerHeight }), 200, false)); window.addEventListener(eventName, this.debounce(() => callback({ innerWidth: window.innerWidth, innerHeight: window.innerHeight }), 200, false));
} else { } else {
window.addEventListener(eventName, callback); window.addEventListener(eventName, callback);
} }
} else { } else {
let dom = domInfoHelper.get(element); const dom = domInfoHelper.get(element);
if (dom) { if (dom) {
(dom as HTMLElement).addEventListener(eventName, callback); (dom as HTMLElement).addEventListener(eventName, callback);
} }
@ -44,7 +44,7 @@ export class eventHelper {
} }
static addDomEventListenerToFirstChild(element, eventName, preventDefault, invoker) { static addDomEventListenerToFirstChild(element, eventName, preventDefault, invoker) {
var dom = domInfoHelper.get(element); const dom = domInfoHelper.get(element);
if (dom && dom.firstElementChild) { if (dom && dom.firstElementChild) {
this.addDomEventListener(dom.firstElementChild, eventName, preventDefault, invoker); this.addDomEventListener(dom.firstElementChild, eventName, preventDefault, invoker);
@ -53,7 +53,7 @@ export class eventHelper {
static addPreventKeys(inputElement, keys: string[]) { static addPreventKeys(inputElement, keys: string[]) {
if (inputElement) { if (inputElement) {
let dom = domInfoHelper.get(inputElement); const dom = domInfoHelper.get(inputElement);
keys = keys.map(function (x) { return x.toUpperCase(); }) keys = keys.map(function (x) { return x.toUpperCase(); })
state.eventCallbackRegistry[inputElement.id + "keydown"] = (e) => this.preventKeys(e, keys); state.eventCallbackRegistry[inputElement.id + "keydown"] = (e) => this.preventKeys(e, keys);
(dom as HTMLElement).addEventListener("keydown", state.eventCallbackRegistry[inputElement.id + "keydown"], false); (dom as HTMLElement).addEventListener("keydown", state.eventCallbackRegistry[inputElement.id + "keydown"], false);
@ -69,7 +69,7 @@ export class eventHelper {
static removePreventKeys(inputElement) { static removePreventKeys(inputElement) {
if (inputElement) { if (inputElement) {
let dom = domInfoHelper.get(inputElement); const dom = domInfoHelper.get(inputElement);
if (dom) { if (dom) {
(dom as HTMLElement).removeEventListener("keydown", state.eventCallbackRegistry[inputElement.id + "keydown"]); (dom as HTMLElement).removeEventListener("keydown", state.eventCallbackRegistry[inputElement.id + "keydown"]);
state.eventCallbackRegistry[inputElement.id + "keydown"] = null; state.eventCallbackRegistry[inputElement.id + "keydown"] = null;

View File

@ -47,7 +47,7 @@ export class infoHelper {
} }
static getElementAbsolutePos(element: any): domTypes.position { static getElementAbsolutePos(element: any): domTypes.position {
let res: domTypes.position = { const res: domTypes.position = {
x: 0, x: 0,
y: 0 y: 0
}; };
@ -68,7 +68,7 @@ export class infoHelper {
static getBoundingClientRect(element: any): domTypes.domRect { static getBoundingClientRect(element: any): domTypes.domRect {
const domElement = this.get(element); const domElement = this.get(element);
if (domElement && domElement.getBoundingClientRect) { if (domElement && domElement.getBoundingClientRect) {
let rect = domElement.getBoundingClientRect(); const rect = domElement.getBoundingClientRect();
// Fixes #1468. This wrapping is necessary for old browsers. Remove this when one day we no longer support them. // Fixes #1468. This wrapping is necessary for old browsers. Remove this when one day we no longer support them.
return { return {
width: rect.width, width: rect.width,
@ -107,13 +107,43 @@ export class infoHelper {
} }
static hasFocus(selector) { static hasFocus(selector) {
let dom = this.get(selector); const dom = this.get(selector);
return (document.activeElement === dom); return (document.activeElement === dom);
} }
static getInnerText(element) { static getInnerText(element) {
let dom = this.get(element); const dom = this.get(element);
if (dom) return dom.innerText; if (dom) return dom.innerText;
return null; return null;
} }
static getMaxZIndex(): number {
return [...document.querySelectorAll("*")].reduce((r, e) => Math.max(r, +window.getComputedStyle(e).zIndex || 0), 0)
}
static isFixedPosition(element) {
let node = this.get(element);
while (node && node.nodeName.toLowerCase() !== 'body') {
if (window.getComputedStyle(node).getPropertyValue('position').toLowerCase() === 'fixed')
{ return true; }
node = node.parentNode;
}
return false;
}
static findAncestorWithZIndex(element: HTMLElement): number {
let node = this.get(element);
let zIndexAsString: string;
let zIndex: number;
while (node && node.nodeName.toLowerCase() !== 'body') {
zIndexAsString = window.getComputedStyle(node).zIndex;
zIndex = Number.parseInt(zIndexAsString);
if (!Number.isNaN(zIndex)) {
return zIndex;
}
node = node.parentNode;
}
return null;
}
} }

View File

@ -12,11 +12,17 @@ export class manipulationHelper {
document.body.removeChild(element); document.body.removeChild(element);
} }
static addElementTo(addElement, elementSelector) { static addElementTo(addElement, elementSelector): boolean {
let parent = domInfoHelper.get(elementSelector); let parent = domInfoHelper.get(elementSelector);
if (parent && addElement) { if (parent && addElement) {
parent.appendChild(addElement); if (parent instanceof Node && addElement instanceof Node) {
parent.appendChild(addElement);
return true;
} else {
console.log("does not implement node", parent, addElement);
}
} }
return false;
} }
static delElementFrom(delElement, elementSelector) { static delElementFrom(delElement, elementSelector) {
@ -95,6 +101,7 @@ export class manipulationHelper {
} }
} }
static blur(selector) { static blur(selector) {
let dom = domInfoHelper.get(selector); let dom = domInfoHelper.get(selector);
if (dom) { if (dom) {
@ -102,20 +109,21 @@ export class manipulationHelper {
} }
} }
static scrollTo(selector: Element | string) { static scrollTo(selector: Element | string, parentElement?: HTMLElement) {
let element = domInfoHelper.get(selector); const element = domInfoHelper.get(selector);
if (parentElement && element && element instanceof HTMLElement) {
if (element && element instanceof HTMLElement) { parentElement.scrollTop = element.offsetTop;
element.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' }); } else if (element && element instanceof HTMLElement) {
} element.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' });
} }
}
static slideTo(targetPageY) { static slideTo(targetPageY) {
var timer = setInterval(function () { const timer = setInterval(function () {
var currentY = document.documentElement.scrollTop || document.body.scrollTop; const currentY = document.documentElement.scrollTop || document.body.scrollTop;
var distance = targetPageY > currentY ? targetPageY - currentY : currentY - targetPageY; const distance = targetPageY > currentY ? targetPageY - currentY : currentY - targetPageY;
var speed = Math.ceil(distance / 10); const speed = Math.ceil(distance / 10);
if (currentY == targetPageY) { if (currentY === targetPageY) {
clearInterval(timer); clearInterval(timer);
} else { } else {
window.scrollTo(0, targetPageY > currentY ? currentY + speed : currentY - speed); window.scrollTo(0, targetPageY > currentY ? currentY + speed : currentY - speed);

View File

@ -60,5 +60,4 @@ export class styleHelper {
} }
} }
} }
} }

View File

@ -7,12 +7,13 @@
<OverlayTrigger @ref="@_dropDown" <OverlayTrigger @ref="@_dropDown"
Visible="Open" Visible="Open"
IsButton="@true" IsButton="@true"
BoundaryAdjustMode="@BoundaryAdjustMode"
Disabled="IsDisabled(0)" Disabled="IsDisabled(0)"
PopupContainerSelector="@PopupContainerSelector" PopupContainerSelector="@PopupContainerSelector"
OnVisibleChange="OverlayVisibleChange" OnVisibleChange="OverlayVisibleChange"
OverlayEnterCls="ant-slide-up-enter ant-slide-up-enter-active ant-slide-up" 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" OverlayLeaveCls="ant-slide-up-leave ant-slide-up-leave-active ant-slide-up"
Trigger="new TriggerType[] { TriggerType.Click }"> Trigger="new[] { Trigger.Click }">
<Overlay> <Overlay>
<div class="@(PrefixCls)-panel-container" @onclick="@PickerClicked"> <div class="@(PrefixCls)-panel-container" @onclick="@PickerClicked">
<div class="@_panelClassMapper.Class"> <div class="@_panelClassMapper.Class">

View File

@ -9,11 +9,12 @@
IsButton="@true" IsButton="@true"
Disabled="IsDisabled()" Disabled="IsDisabled()"
PopupContainerSelector="@PopupContainerSelector" PopupContainerSelector="@PopupContainerSelector"
BoundaryAdjustMode="@BoundaryAdjustMode"
OnVisibleChange="OverlayVisibleChange" OnVisibleChange="OverlayVisibleChange"
OverlayClassName="ant-picker-dropdown-range" OverlayClassName="ant-picker-dropdown-range"
OverlayEnterCls="ant-slide-up-enter ant-slide-up-enter-active ant-slide-up" 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" OverlayLeaveCls="ant-slide-up-leave ant-slide-up-leave-active ant-slide-up"
Trigger="new TriggerType[] { TriggerType.Click }"> Trigger="new[] { Trigger.Click }">
<Overlay> <Overlay>
<div class="@(PrefixCls)-range-arrow" style="@_rangeArrowStyle" /> <div class="@(PrefixCls)-range-arrow" style="@_rangeArrowStyle" />
<div class="@(PrefixCls)-panel-container" @onclick="@PickerClicked"> <div class="@(PrefixCls)-panel-container" @onclick="@PickerClicked">

View File

@ -70,6 +70,12 @@ namespace AntDesign
[Parameter] [Parameter]
public OneOf<bool, bool[]> Disabled { get; set; } = new bool[] { false, false }; public OneOf<bool, bool[]> Disabled { get; set; } = new bool[] { false, false };
/// <summary>
/// Overlay adjustment strategy (when for example browser resize is happening)
/// </summary>
[Parameter]
public TriggerBoundaryAdjustMode BoundaryAdjustMode { get; set; } = TriggerBoundaryAdjustMode.InView;
[Parameter] [Parameter]
public bool Bordered { get; set; } = true; public bool Bordered { get; set; } = true;

View File

@ -47,7 +47,7 @@ namespace AntDesign
private List<List<(IDescriptionsItem item, int realSpan)>> _itemMatrix = new List<List<(IDescriptionsItem item, int realSpan)>>(); private List<List<(IDescriptionsItem item, int realSpan)>> _itemMatrix = new List<List<(IDescriptionsItem item, int realSpan)>>();
[Inject] [Inject]
public DomEventService DomEventService { get; set; } private IDomEventListener DomEventListener { get; set; }
private int _realColumn; private int _realColumn;
@ -84,7 +84,6 @@ namespace AntDesign
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
SetClassMap(); SetClassMap();
await base.OnInitializedAsync(); await base.OnInitializedAsync();
} }
@ -92,7 +91,7 @@ namespace AntDesign
{ {
if (firstRender && Column.IsT1) if (firstRender && Column.IsT1)
{ {
DomEventService.AddEventListener<object>("window", "resize", OnResize, false); DomEventListener.AddShared<object>("window", "resize", OnResize);
} }
base.OnAfterRender(firstRender); base.OnAfterRender(firstRender);
@ -100,9 +99,8 @@ namespace AntDesign
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
DomEventListener.Dispose();
base.Dispose(disposing); base.Dispose(disposing);
DomEventService.RemoveEventListerner<object>("window", "resize", OnResize);
} }
private async void OnResize(object o) private async void OnResize(object o)

View File

@ -1,55 +1,56 @@
@namespace AntDesign @namespace AntDesign
@inherits AntDomComponentBase @inherits AntDomComponentBase
<CascadingValue Value="@($"#ant-drawer-wrap_{Id}")" Name="PopupContainerSelector">
<div class="@ClassMapper.Class" @ref="@Ref" style="@_drawerStyle @InnerZIndexStyle @Style" id="@Id">
@if (Mask)
{
<div class="ant-drawer-mask" @onclick="_=>MaskClick()" style="@MaskStyle"></div>
}
<div class="ant-drawer-content-wrapper @WrapClassName " style="@WrapperStyle" id="@($"ant-drawer-wrap_{Id}")">
<div class="ant-drawer-content">
<div class="ant-drawer-wrapper-body" style="@(IsLeftOrRight?"height:100%":"")">
@if (_title.Value != null || Closable)
{
<div class="@ClassMapper.Class" @ref="@Ref" style="@_drawerStyle @InnerZIndexStyle @Style" id="@Id"> <div class="@TitleClassMapper.Class">
@if (Mask) @if (_title.Value != null)
{ {
<div class="ant-drawer-mask" @onclick="_=>MaskClick()" style="@MaskStyle"></div> <div class="ant-drawer-title">
} @if (TitleTemplate != null)
<div class="ant-drawer-content-wrapper @WrapClassName " style="@WrapperStyle"> {
<div class="ant-drawer-content"> @TitleTemplate
<div class="ant-drawer-wrapper-body" style="@(IsLeftOrRight?"height:100%":"")"> }
@if (_title.Value != null || Closable) @if (!string.IsNullOrEmpty(TitleString))
{ {
@((MarkupString)TitleString)
<div class="@TitleClassMapper.Class"> }
@if (_title.Value != null) </div>
}
@if (Closable)
{
<button @onclick="_=>CloseClick()" aria-label="Close" class="ant-drawer-close">
<Icon Type="close" />
</button>
}
</div>
}
<div class="ant-drawer-body" style="@BodyStyle">
@if (ContentTemplate != null)
{ {
<div class="ant-drawer-title"> @ContentTemplate
@if (TitleTemplate != null)
{
@TitleTemplate
}
@if (!string.IsNullOrEmpty(TitleString))
{
@((MarkupString)TitleString)
}
</div>
} }
@if (Closable) @if (string.IsNullOrEmpty(ContentString))
{ {
<button @onclick="_=>CloseClick()" aria-label="Close" class="ant-drawer-close"> @((MarkupString)ContentString)
<Icon Type="close" />
</button>
} }
@ChildContent
</div> </div>
}
<div class="ant-drawer-body" style="@BodyStyle">
@if (ContentTemplate != null)
{
@ContentTemplate
}
@if (string.IsNullOrEmpty(ContentString))
{
@((MarkupString)ContentString)
}
@ChildContent
</div> </div>
</div> </div>
@if (Handler != null)
{
@Handler
}
</div> </div>
@if (Handler != null)
{
@Handler
}
</div> </div>
</div> </CascadingValue>

View File

@ -389,5 +389,11 @@ namespace AntDesign
_drawerStyle = style; _drawerStyle = style;
} }
protected override void Dispose(bool disposing)
{
_timer?.Dispose();
base.Dispose(disposing);
}
} }
} }

View File

@ -186,7 +186,7 @@ namespace AntDesign
{ {
string prefixCls = "ant-btn"; string prefixCls = "ant-btn";
ClassMapper.Clear(); ClassMapper.Clear();
Placement = PlacementType.BottomRight; Placement = Placement.BottomRight;
base.OnInitialized(); base.OnInitialized();
ClassMapper.If($"{prefixCls}-block", () => Block); ClassMapper.If($"{prefixCls}-block", () => Block);
@ -208,25 +208,20 @@ namespace AntDesign
if (firstRender) if (firstRender)
{ {
Ref = RefBack.Current; Ref = RefBack.Current;
DomEventService.AddEventListener(Ref, "click", OnUnboundClick, true);
DomEventService.AddEventListener(Ref, "mouseover", OnUnboundMouseEnter, true); DomEventListener.AddExclusive<JsonElement>(Ref, "click", OnUnboundClick);
DomEventService.AddEventListener(Ref, "mouseout", OnUnboundMouseLeave, true); DomEventListener.AddExclusive<JsonElement>(Ref, "mouseover", OnUnboundMouseEnter);
DomEventService.AddEventListener(Ref, "focusin", OnUnboundFocusIn, true); DomEventListener.AddExclusive<JsonElement>(Ref, "mouseout", OnUnboundMouseLeave);
DomEventService.AddEventListener(Ref, "focusout", OnUnboundFocusOut, true); DomEventListener.AddExclusive<JsonElement>(Ref, "focusin", OnUnboundFocusIn);
DomEventService.AddEventListener(Ref, "contextmenu", OnContextMenu, true, true); DomEventListener.AddExclusive<JsonElement>(Ref, "focusout", OnUnboundFocusOut);
DomEventListener.AddExclusive<JsonElement>(Ref, "contextmenu", OnContextMenu, true);
} }
return base.OnAfterRenderAsync(firstRender); return base.OnAfterRenderAsync(firstRender);
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
DomEventService.RemoveEventListerner<JsonElement>(Ref, "click", OnUnboundClick); DomEventListener.DisposeExclusive();
DomEventService.RemoveEventListerner<JsonElement>(Ref, "mouseover", OnUnboundMouseEnter);
DomEventService.RemoveEventListerner<JsonElement>(Ref, "mouseout", OnUnboundMouseLeave);
DomEventService.RemoveEventListerner<JsonElement>(Ref, "focusin", OnUnboundFocusIn);
DomEventService.RemoveEventListerner<JsonElement>(Ref, "focusout", OnUnboundFocusOut);
DomEventService.RemoveEventListerner<JsonElement>(Ref, "contextmenu", OnContextMenu);
base.Dispose(disposing); base.Dispose(disposing);
} }

View File

@ -284,6 +284,11 @@ namespace AntDesign
_formItems.Add(formItem); _formItems.Add(formItem);
} }
void IForm.RemoveFormItem(IFormItem formItem)
{
_formItems.Remove(formItem);
}
void IForm.AddControl(IControlValueAccessor valueAccessor) void IForm.AddControl(IControlValueAccessor valueAccessor)
{ {
this._controls.Add(valueAccessor); this._controls.Add(valueAccessor);

View File

@ -270,6 +270,8 @@ namespace AntDesign
CurrentEditContext.OnValidationStateChanged -= _validationStateChangedHandler; CurrentEditContext.OnValidationStateChanged -= _validationStateChangedHandler;
} }
Form?.RemoveFormItem(this);
base.Dispose(disposing); base.Dispose(disposing);
} }

View File

@ -21,6 +21,8 @@ namespace AntDesign.Internal
internal void AddFormItem(IFormItem formItem); internal void AddFormItem(IFormItem formItem);
internal void RemoveFormItem(IFormItem formItem);
internal void AddControl(IControlValueAccessor valueAccessor); internal void AddControl(IControlValueAccessor valueAccessor);
internal void RemoveControl(IControlValueAccessor valueAccessor); internal void RemoveControl(IControlValueAccessor valueAccessor);

View File

@ -1,4 +1,5 @@
using System.Collections; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using AntDesign.JsInterop; using AntDesign.JsInterop;
@ -52,13 +53,13 @@ namespace AntDesign
/// Used to set gutter during pre-rendering /// Used to set gutter during pre-rendering
/// </summary> /// </summary>
[Parameter] [Parameter]
public BreakpointType DefaultBreakpoint { get; set; } = BreakpointType.Xxl; public BreakpointType? DefaultBreakpoint { get; set; } = BreakpointType.Xxl;
[Inject] [Inject]
public DomEventService DomEventService { get; set; } private IDomEventListener DomEventListener { get; set; }
private string _gutterStyle; private string _gutterStyle;
private BreakpointType _currentBreakPoint; private BreakpointType? _currentBreakPoint;
private IList<Col> _cols = new List<Col>(); private IList<Col> _cols = new List<Col>();
@ -89,7 +90,7 @@ namespace AntDesign
if (DefaultBreakpoint != null) if (DefaultBreakpoint != null)
{ {
SetGutterStyle(DefaultBreakpoint.Name); SetGutterStyle(DefaultBreakpoint);
} }
await base.OnInitializedAsync(); await base.OnInitializedAsync();
@ -100,8 +101,8 @@ namespace AntDesign
if (firstRender) if (firstRender)
{ {
var dimensions = await JsInvokeAsync<Window>(JSInteropConstants.GetWindow); var dimensions = await JsInvokeAsync<Window>(JSInteropConstants.GetWindow);
DomEventService.AddEventListener<Window>("window", "resize", OnResize, false); DomEventListener.AddShared<Window>("window", "resize", OnResize);
OptimizeSize(dimensions.innerWidth); OptimizeSize(dimensions.InnerWidth);
} }
await base.OnAfterRenderAsync(firstRender); await base.OnAfterRenderAsync(firstRender);
@ -110,7 +111,7 @@ namespace AntDesign
internal void AddCol(Col col) internal void AddCol(Col col)
{ {
this._cols.Add(col); this._cols.Add(col);
var gutter = this.GetGutter((_currentBreakPoint ?? DefaultBreakpoint).Name); var gutter = this.GetGutter(_currentBreakPoint ?? DefaultBreakpoint);
col.RowGutterChanged(gutter); col.RowGutterChanged(gutter);
} }
@ -119,9 +120,9 @@ namespace AntDesign
this._cols.Remove(col); this._cols.Remove(col);
} }
private async void OnResize(Window window) private void OnResize(Window window)
{ {
OptimizeSize(window.innerWidth); OptimizeSize(window.InnerWidth);
} }
private void OptimizeSize(decimal windowWidth) private void OptimizeSize(decimal windowWidth)
@ -129,7 +130,7 @@ namespace AntDesign
BreakpointType actualBreakpoint = _breakpoints[_breakpoints.Length - 1]; BreakpointType actualBreakpoint = _breakpoints[_breakpoints.Length - 1];
for (int i = 0; i < _breakpoints.Length; i++) for (int i = 0; i < _breakpoints.Length; i++)
{ {
if (windowWidth <= _breakpoints[i].Width && (windowWidth >= (i > 0 ? _breakpoints[i - 1].Width : 0))) if (windowWidth <= (int)_breakpoints[i] && (windowWidth >= (i > 0 ? (int)_breakpoints[i - 1] : 0)))
{ {
actualBreakpoint = _breakpoints[i]; actualBreakpoint = _breakpoints[i];
} }
@ -137,7 +138,7 @@ namespace AntDesign
this._currentBreakPoint = actualBreakpoint; this._currentBreakPoint = actualBreakpoint;
SetGutterStyle(actualBreakpoint.Name); SetGutterStyle(actualBreakpoint);
if (OnBreakpoint.HasDelegate) if (OnBreakpoint.HasDelegate)
{ {
@ -147,7 +148,7 @@ namespace AntDesign
StateHasChanged(); StateHasChanged();
} }
private void SetGutterStyle(string breakPoint) private void SetGutterStyle(BreakpointType? breakPoint)
{ {
var gutter = this.GetGutter(breakPoint); var gutter = this.GetGutter(breakPoint);
_cols.ForEach(x => x.RowGutterChanged(gutter)); _cols.ForEach(x => x.RowGutterChanged(gutter));
@ -162,27 +163,28 @@ namespace AntDesign
StateHasChanged(); StateHasChanged();
} }
private (int horizontalGutter, int verticalGutter) GetGutter(string breakPoint) private (int horizontalGutter, int verticalGutter) GetGutter(BreakpointType? breakPoint)
{ {
GutterType gutter = 0; GutterType gutter = 0;
if (this.Gutter.Value != null) if (this.Gutter.Value != null)
gutter = this.Gutter; gutter = this.Gutter;
var breakPointName = Enum.GetName(typeof(BreakpointType), breakPoint);
return gutter.Match( return gutter.Match(
num => (num, 0), num => (num, 0),
dic => breakPoint != null && dic.ContainsKey(breakPoint) ? (dic[breakPoint], 0) : (0, 0), dic => breakPoint != null && dic.ContainsKey(breakPointName) ? (dic[breakPointName], 0) : (0, 0),
tuple => tuple, tuple => tuple,
tupleDicInt => (tupleDicInt.Item1.ContainsKey(breakPoint) ? tupleDicInt.Item1[breakPoint] : 0, tupleDicInt.Item2), tupleDicInt => (tupleDicInt.Item1.ContainsKey(breakPointName) ? tupleDicInt.Item1[breakPointName] : 0, tupleDicInt.Item2),
tupleIntDic => (tupleIntDic.Item1, tupleIntDic.Item2.ContainsKey(breakPoint) ? tupleIntDic.Item2[breakPoint] : 0), tupleIntDic => (tupleIntDic.Item1, tupleIntDic.Item2.ContainsKey(breakPointName) ? tupleIntDic.Item2[breakPointName] : 0),
tupleDicDic => (tupleDicDic.Item1.ContainsKey(breakPoint) ? tupleDicDic.Item1[breakPoint] : 0, tupleDicDic.Item2.ContainsKey(breakPoint) ? tupleDicDic.Item2[breakPoint] : 0) tupleDicDic => (tupleDicDic.Item1.ContainsKey(breakPointName) ? tupleDicDic.Item1[breakPointName] : 0, tupleDicDic.Item2.ContainsKey(breakPointName) ? tupleDicDic.Item2[breakPointName] : 0)
); );
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
DomEventListener.Dispose();
base.Dispose(disposing); base.Dispose(disposing);
DomEventService.RemoveEventListerner<Window>("window", "resize", OnResize);
} }
} }

View File

@ -12,7 +12,7 @@
</span> </span>
</div> </div>
<div class="ant-input-number-input-wrap"> <div class="ant-input-number-input-wrap">
<input @ref="Ref" role="spinbutton" aria-valuemin="@Min" aria-valuemax="@Max" autocomplete="off" max="@Max" min="@Min" step="@Step" <input @ref="Ref" role="spinbutton" aria-valuemin="@Min" aria-valuemax="@Max" autocomplete="off" max="@Max" min="@Min" step="@Step" inputmode="@_inputNumberMode"
aria-valuenow="@CurrentValue" class="ant-input-number-input" @bind="@CurrentValueAsString" @oninput="OnInput" @onkeydown="OnKeyDown" @onfocus="@OnFocus" @onblur="@OnBlurAsync" disabled="@Disabled" /> aria-valuenow="@CurrentValue" class="ant-input-number-input" @bind="@CurrentValueAsString" @oninput="OnInput" @onkeydown="OnKeyDown" @onfocus="@OnFocusAsync" @onblur="@OnBlurAsync" disabled="@Disabled" />
</div> </div>
</div> </div>

View File

@ -74,6 +74,9 @@ namespace AntDesign
[Parameter] [Parameter]
public EventCallback<TValue> OnChange { get; set; } public EventCallback<TValue> OnChange { get; set; }
[Parameter]
public EventCallback<FocusEventArgs> OnFocus { get; set; }
private readonly bool _isNullable; private readonly bool _isNullable;
private bool _hasDefaultValue; private bool _hasDefaultValue;
@ -115,40 +118,40 @@ namespace AntDesign
private static readonly Dictionary<Type, object> _defaultMaximum = new Dictionary<Type, object>() private static readonly Dictionary<Type, object> _defaultMaximum = new Dictionary<Type, object>()
{ {
{ typeof(sbyte),sbyte.MaxValue }, { typeof(sbyte), sbyte.MaxValue },
{ typeof(byte), byte.MaxValue }, { typeof(byte), byte.MaxValue },
{ typeof(short),short.MaxValue }, { typeof(short), short.MaxValue },
{ typeof(ushort),ushort.MaxValue }, { typeof(ushort), ushort.MaxValue },
{ typeof(int),int.MaxValue }, { typeof(int), int.MaxValue },
{ typeof(uint),uint.MaxValue }, { typeof(uint), uint.MaxValue },
{ typeof(long),long.MaxValue }, { typeof(long), long.MaxValue },
{ typeof(ulong),ulong.MaxValue }, { typeof(ulong), ulong.MaxValue },
{ typeof(float),float.PositiveInfinity }, { typeof(float), float.PositiveInfinity },
{ typeof(double),double.PositiveInfinity }, { typeof(double), double.PositiveInfinity },
{ typeof(decimal),decimal.MaxValue }, { typeof(decimal), decimal.MaxValue },
}; };
private static readonly Dictionary<Type, object> _defaultMinimum = new Dictionary<Type, object>() private static readonly Dictionary<Type, object> _defaultMinimum = new Dictionary<Type, object>()
{ {
{ typeof(sbyte),sbyte.MinValue }, { typeof(sbyte), sbyte.MinValue },
{ typeof(byte), byte.MinValue }, { typeof(byte), byte.MinValue },
{ typeof(short),short.MinValue }, { typeof(short), short.MinValue },
{ typeof(ushort),ushort.MinValue }, { typeof(ushort), ushort.MinValue },
{ typeof(int),int.MinValue }, { typeof(int), int.MinValue },
{ typeof(uint),uint.MinValue }, { typeof(uint), uint.MinValue },
{ typeof(long),long.MinValue }, { typeof(long), long.MinValue },
{ typeof(ulong),ulong.MinValue }, { typeof(ulong), ulong.MinValue },
{ typeof(float),float.NegativeInfinity}, { typeof(float), float.NegativeInfinity},
{ typeof(double),double.NegativeInfinity }, { typeof(double), double.NegativeInfinity },
{ typeof(decimal),decimal.MinValue }, { typeof(decimal), decimal.MinValue },
}; };
private static Type[] _floatTypes = new Type[] { typeof(float), typeof(double), typeof(decimal) }; private static Type[] _floatTypes = new Type[] { typeof(float), typeof(double), typeof(decimal) };
@ -160,6 +163,8 @@ namespace AntDesign
private CancellationTokenSource _decreaseTokenSource; private CancellationTokenSource _decreaseTokenSource;
private TValue _defaultValue; private TValue _defaultValue;
private string _inputNumberMode = "numeric";
public InputNumber() public InputNumber()
{ {
_isNullable = _surfaceType.IsGenericType && _surfaceType.GetGenericTypeDefinition() == typeof(Nullable<>); _isNullable = _surfaceType.IsGenericType && _surfaceType.GetGenericTypeDefinition() == typeof(Nullable<>);
@ -203,6 +208,7 @@ namespace AntDesign
MethodCallExpression expRound = Expression.Call(null, typeof(InputNumberMath).GetMethod(nameof(InputNumberMath.Round), new Type[] { _surfaceType, typeof(int) }), num, decimalPlaces); MethodCallExpression expRound = Expression.Call(null, typeof(InputNumberMath).GetMethod(nameof(InputNumberMath.Round), new Type[] { _surfaceType, typeof(int) }), num, decimalPlaces);
var lambdaRound = Expression.Lambda<Func<TValue, int, TValue>>(expRound, num, decimalPlaces); var lambdaRound = Expression.Lambda<Func<TValue, int, TValue>>(expRound, num, decimalPlaces);
_roundFunc = lambdaRound.Compile(); _roundFunc = lambdaRound.Compile();
_inputNumberMode = "decimal";
} }
if (_defaultMaximum.ContainsKey(underlyingType)) Max = (TValue)_defaultMaximum[underlyingType]; if (_defaultMaximum.ContainsKey(underlyingType)) Max = (TValue)_defaultMaximum[underlyingType];
@ -404,10 +410,15 @@ namespace AntDesign
_inputString = args.Value?.ToString(); _inputString = args.Value?.ToString();
} }
private void OnFocus() private async Task OnFocusAsync(FocusEventArgs args)
{ {
_focused = true; _focused = true;
CurrentValue = Value; CurrentValue = Value;
if (OnFocus.HasDelegate)
{
await OnFocus.InvokeAsync(args);
}
} }
private async Task OnBlurAsync() private async Task OnBlurAsync()

View File

@ -1,5 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -32,7 +31,7 @@ namespace AntDesign
protected virtual bool EnableOnPressEnter => OnPressEnter.HasDelegate; protected virtual bool EnableOnPressEnter => OnPressEnter.HasDelegate;
[Inject] [Inject]
public DomEventService DomEventService { get; set; } protected IDomEventListener DomEventListener { get; set; }
/// <summary> /// <summary>
/// The label text displayed before (on the left side of) the input field. /// The label text displayed before (on the left side of) the input field.
@ -52,6 +51,13 @@ namespace AntDesign
[Parameter] [Parameter]
public bool AllowClear { get; set; } public bool AllowClear { get; set; }
/// <summary>
/// Controls the autocomplete attribute of the input HTML element.
/// Default = true
/// </summary>
[Parameter]
public bool AutoComplete { get; set; } = true;
[Parameter] [Parameter]
public bool AutoFocus public bool AutoFocus
{ {
@ -168,8 +174,11 @@ namespace AntDesign
[Parameter] [Parameter]
public bool ReadOnly { get; set; } public bool ReadOnly { get; set; }
/// <summary>
/// Controls onclick & blur event propagation.
/// </summary>
[Parameter] [Parameter]
public bool AutoComplete { get; set; } = true; public bool StopPropagation { get; set; }
/// <summary> /// <summary>
/// The suffix icon for the Input. /// The suffix icon for the Input.
@ -451,9 +460,10 @@ namespace AntDesign
return; return;
} }
_debounceTimer?.Dispose();
if (_debounceTimer != null) if (_debounceTimer != null)
{ {
await _debounceTimer.DisposeAsync();
_debounceTimer = null; _debounceTimer = null;
} }
} }
@ -477,23 +487,21 @@ namespace AntDesign
if (firstRender) if (firstRender)
{ {
DomEventService.AddEventListener(Ref, "compositionstart", OnCompositionStart); DomEventListener.AddExclusive<JsonElement>(Ref, "compositionstart", OnCompositionStart);
DomEventService.AddEventListener(Ref, "compositionend", OnCompositionEnd); DomEventListener.AddExclusive<JsonElement>(Ref, "compositionend", OnCompositionEnd);
if (this.AutoFocus) if (this.AutoFocus)
{ {
IsFocused = true; IsFocused = true;
await this.FocusAsync(Ref); await this.FocusAsync(Ref);
} }
DomEventService.AddEventListener(Ref, "focus", OnFocusInternal, true);
DomEventListener.AddExclusive<JsonElement>(Ref, "focus", OnFocusInternal);
} }
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
DomEventService.RemoveEventListerner<JsonElement>(Ref, "compositionstart", OnCompositionStart); DomEventListener.DisposeExclusive();
DomEventService.RemoveEventListerner<JsonElement>(Ref, "compositionend", OnCompositionEnd);
DomEventService.RemoveEventListerner<JsonElement>(Ref, "focus", OnFocusInternal);
_debounceTimer?.Dispose(); _debounceTimer?.Dispose();
base.Dispose(disposing); base.Dispose(disposing);
@ -641,6 +649,13 @@ namespace AntDesign
//TODO: Use built in @onfocus once https://github.com/dotnet/aspnetcore/issues/30070 is solved //TODO: Use built in @onfocus once https://github.com/dotnet/aspnetcore/issues/30070 is solved
//builder.AddAttribute(76, "onfocus", CallbackFactory.Create(this, OnFocusAsync)); //builder.AddAttribute(76, "onfocus", CallbackFactory.Create(this, OnFocusAsync));
builder.AddAttribute(77, "onmouseup", CallbackFactory.Create(this, OnMouseUpAsync)); builder.AddAttribute(77, "onmouseup", CallbackFactory.Create(this, OnMouseUpAsync));
if (StopPropagation)
{
builder.AddEventStopPropagationAttribute(78, "onchange", true);
builder.AddEventStopPropagationAttribute(79, "onblur", true);
}
builder.AddElementReferenceCapture(90, r => Ref = r); builder.AddElementReferenceCapture(90, r => Ref = r);
builder.CloseElement(); builder.CloseElement();

View File

@ -1,7 +1,5 @@
using System; using System.Threading.Tasks;
using System.Threading.Tasks;
using AntDesign.Core.Extensions; using AntDesign.Core.Extensions;
using AntDesign.JsInterop;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.Web;

View File

@ -1,6 +1,5 @@
@namespace AntDesign @namespace AntDesign
@inherits Input<string> @inherits Input<string>
<!--TODO: minheight, maxheight, onResize-->
@{ @{
Dictionary<string, object> attributes = Dictionary<string, object> attributes =
@ -19,7 +18,7 @@
{ "style", Style }, { "style", Style },
{ "class", ClassMapper.Class }, { "class", ClassMapper.Class },
{ "disabled", Disabled }, { "disabled", Disabled },
{ "readonly", ReadOnly }, { "readonly", ReadOnly },
}; };
if (Attributes != null) if (Attributes != null)
@ -34,13 +33,13 @@
@if (Suffix != null) @if (Suffix != null)
{ {
<span class="@_warpperClassMapper.Class"> <span class="@_warpperClassMapper.Class">
<textarea @ref="Ref" @attributes="attributes"/> <textarea @ref="Ref" @attributes="attributes" @onchange:stopPropagation="@StopPropagation" @onblur:stopPropagation="@StopPropagation"/>
@Suffix @Suffix
</span> </span>
} }
else else
{ {
<textarea @ref="Ref" @attributes="attributes"/> <textarea @ref="Ref" @attributes="attributes" @onchange:stopPropagation="@StopPropagation" @onblur:stopPropagation="@StopPropagation"/>
} }
<AntDesign.Text Style="float: right; pointer-events: none; white-space: nowrap; color: rgba(0, 0, 0, 0.45)">&nbsp;@($"/ {MaxLength}")</AntDesign.Text> <AntDesign.Text Style="float: right; pointer-events: none; white-space: nowrap; color: rgba(0, 0, 0, 0.45)">&nbsp;@($"/ {MaxLength}")</AntDesign.Text>
</div> </div>
@ -50,12 +49,12 @@ else
@if (Suffix != null) @if (Suffix != null)
{ {
<span class="@_warpperClassMapper.Class"> <span class="@_warpperClassMapper.Class">
<textarea @ref="Ref" @attributes="attributes"/> <textarea @ref="Ref" @attributes="attributes" @onchange:stopPropagation="@StopPropagation" @onblur:stopPropagation="@StopPropagation"/>
@Suffix @Suffix
</span> </span>
} }
else else
{ {
<textarea @ref="Ref" @attributes="attributes"/> <textarea @ref="Ref" @attributes="attributes" @onchange:stopPropagation="@StopPropagation" @onblur:stopPropagation="@StopPropagation"/>
} }
} }

View File

@ -1,6 +1,4 @@
using System; using System.Diagnostics;
using System.Diagnostics;
using System.Globalization;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using AntDesign.JsInterop; using AntDesign.JsInterop;
@ -28,10 +26,39 @@ namespace AntDesign
private uint _minRows = DEFAULT_MIN_ROWS; private uint _minRows = DEFAULT_MIN_ROWS;
private uint _maxRows = uint.MaxValue; private uint _maxRows = uint.MaxValue;
private bool _hasMinOrMaxSet; private bool _hasMinOrMaxSet;
private bool _hasMinSet;
private DotNetObjectReference<TextArea> _reference; private DotNetObjectReference<TextArea> _reference;
/// <summary>
/// Will adjust (grow or shrink) the `TextArea` according to content.
/// Can work in connection with `MaxRows` & `MinRows`.
/// Sets resize attribute of the textarea HTML element to: none.
/// </summary>
[Parameter] [Parameter]
public bool AutoSize { get; set; } public bool AutoSize
{
get => _autoSize;
set
{
if (_hasMinOrMaxSet && !value)
{
Debug.WriteLine("AntBlazor.TextArea: AutoSize cannot be set to false when either MinRows or MaxRows has been set.AutoSize has been switched to true.");
_autoSize = true;
}
else
{
_autoSize = value;
}
if (_autoSize)
{
_resizeStyle = "resize: none";
}
else
{
_resizeStyle = "";
}
}
}
/// <summary> /// <summary>
/// When `false`, value will be set to `null` when content is empty /// When `false`, value will be set to `null` when content is empty
@ -58,12 +85,13 @@ namespace AntDesign
if (value >= MinRows) if (value >= MinRows)
{ {
_maxRows = value; _maxRows = value;
Debug.WriteLineIf(!AutoSize, "AntBlazor.TextArea: AutoSize cannot be set to false when either MinRows or MaxRows has been set.AutoSize has been switched to true.");
AutoSize = true; AutoSize = true;
} }
else else
{ {
_maxRows = uint.MaxValue; _maxRows = uint.MaxValue;
Debug.WriteLine($"Value of {nameof(MaxRows)}({MaxRows}) has to be between {nameof(MinRows)}({MinRows}) and {uint.MaxValue}"); Debug.WriteLine($"AntBlazor.TextArea: Value of {nameof(MaxRows)}({MaxRows}) has to be between {nameof(MinRows)}({MinRows}) and {uint.MaxValue}");
} }
} }
} }
@ -83,19 +111,28 @@ namespace AntDesign
set set
{ {
_hasMinOrMaxSet = true; _hasMinOrMaxSet = true;
_hasMinSet = true;
if (value >= DEFAULT_MIN_ROWS && value <= MaxRows) if (value >= DEFAULT_MIN_ROWS && value <= MaxRows)
{ {
_minRows = value; _minRows = value;
Debug.WriteLineIf(!AutoSize, "AntBlazor.TextArea: AutoSize cannot be set to false when either MinRows or MaxRows has been set.AutoSize has been switched to true.");
AutoSize = true; AutoSize = true;
} }
else else
{ {
_minRows = DEFAULT_MIN_ROWS; _minRows = DEFAULT_MIN_ROWS;
Debug.WriteLine($"Value of {nameof(MinRows)}({MinRows}) has to be between {DEFAULT_MIN_ROWS} and {nameof(MaxRows)}({MaxRows})"); Debug.WriteLine($"AntBlazor.TextArea: Value of {nameof(MinRows)}({MinRows}) has to be between {DEFAULT_MIN_ROWS} and {nameof(MaxRows)}({MaxRows})");
} }
} }
} }
/// <summary>
/// Sets the height of the TextArea expressed in number of rows.
/// Default value is 3.
/// </summary>
[Parameter]
public uint Rows { get; set; } = 3;
/// <summary> /// <summary>
/// Callback when the size changes /// Callback when the size changes
/// </summary> /// </summary>
@ -132,41 +169,25 @@ namespace AntDesign
if (AutoSize) if (AutoSize)
{ {
DomEventService.AddEventListener("window", "beforeunload", Reloading, false); DomEventListener.AddShared<JsonElement>("window", "beforeunload", Reloading);
await CalculateRowHeightAsync();
} }
await CalculateRowHeightAsync();
} }
protected override bool TryParseValueFromString(string value, out string result, out string validationErrorMessage) protected override bool TryParseValueFromString(string value, out string result, out string validationErrorMessage)
{ {
validationErrorMessage = null;
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
{ {
if (DefaultToEmptyString) if (DefaultToEmptyString)
result = string.Empty; result = string.Empty;
else else
result = default; result = default;
validationErrorMessage = null;
return true; return true;
} }
result = value;
return true;
var success = BindConverter.TryConvertTo<string>(
value, CultureInfo.CurrentCulture, out var parsedValue);
if (success)
{
result = parsedValue;
validationErrorMessage = null;
return true;
}
else
{
result = default;
validationErrorMessage = $"{FieldIdentifier.FieldName} field isn't valid.";
return false;
}
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
@ -174,7 +195,7 @@ namespace AntDesign
if (AutoSize && !_isReloading) if (AutoSize && !_isReloading)
{ {
_reference?.Dispose(); _reference?.Dispose();
DomEventService.RemoveEventListerner<JsonElement>("window", "beforeunload", Reloading); DomEventListener.Dispose();
_ = InvokeAsync(async () => _ = InvokeAsync(async () =>
{ {
@ -189,6 +210,8 @@ namespace AntDesign
/// Indicates that a page is being refreshed /// Indicates that a page is being refreshed
/// </summary> /// </summary>
private bool _isReloading; private bool _isReloading;
private bool _autoSize;
private string _resizeStyle = "";
private void Reloading(JsonElement jsonElement) => _isReloading = true; private void Reloading(JsonElement jsonElement) => _isReloading = true;
@ -205,33 +228,39 @@ namespace AntDesign
{ {
_reference = DotNetObjectReference.Create<TextArea>(this); _reference = DotNetObjectReference.Create<TextArea>(this);
} }
var textAreaInfo = await JsInvokeAsync<TextAreaInfo>(JSInteropConstants.RegisterResizeTextArea, Ref, MinRows, MaxRows, _reference);
// var textAreaInfo = await JsInvokeAsync<TextAreaInfo>(JSInteropConstants.GetTextAreaInfo, Ref); uint rows = Rows;
if (_hasMinSet)
rows = MinRows;
TextAreaInfo textAreaInfo;
if (AutoSize)
{
textAreaInfo = await JsInvokeAsync<TextAreaInfo>(
JSInteropConstants.InputComponentHelper.RegisterResizeTextArea, Ref, rows, MaxRows, _reference);
}
else
{
textAreaInfo = await JsInvokeAsync<TextAreaInfo>(
JSInteropConstants.InputComponentHelper.GetTextAreaInfo, Ref);
}
_rowHeight = textAreaInfo.LineHeight; _rowHeight = textAreaInfo.LineHeight;
_offsetHeight = textAreaInfo.PaddingTop + textAreaInfo.PaddingBottom _offsetHeight = textAreaInfo.PaddingTop + textAreaInfo.PaddingBottom
+ textAreaInfo.BorderTop + textAreaInfo.BorderBottom; + textAreaInfo.BorderTop + textAreaInfo.BorderBottom;
uint rows = (uint)(textAreaInfo.ScrollHeight / _rowHeight);
if (_hasMinOrMaxSet)
rows = Math.Max((uint)MinRows, rows);
double height = 0;
if (rows > MaxRows) if (rows > MaxRows)
{ {
rows = MaxRows; Style = $"height: {MaxRows * _rowHeight + _offsetHeight}px;{_resizeStyle};overflow-x: hidden";
height = rows * _rowHeight + _offsetHeight;
Style = $"height: {height}px;";
} }
else else
{ {
height = rows * _rowHeight + _offsetHeight; string overflow = _autoSize ? "hidden" : "visible";
Style = $"height: {height}px;overflow-y: hidden;"; Style = $"height: {rows * _rowHeight + _offsetHeight}px;overflow-y: {overflow};{_resizeStyle};overflow-x: hidden";
} }
} }
private class TextAreaInfo internal class TextAreaInfo
{ {
public double ScrollHeight { get; set; } public double ScrollHeight { get; set; }
public double LineHeight { get; set; } public double LineHeight { get; set; }

View File

@ -1,19 +1,12 @@
namespace AntDesign namespace AntDesign
{ {
public sealed class BreakpointType : EnumValue<BreakpointType> public enum BreakpointType
{ {
public static readonly BreakpointType Xs = new BreakpointType(nameof(Xs).ToLowerInvariant(), 1, 480); Xs = 480,
public static readonly BreakpointType Sm = new BreakpointType(nameof(Sm).ToLowerInvariant(), 2, 576); Sm = 576,
public static readonly BreakpointType Md = new BreakpointType(nameof(Md).ToLowerInvariant(), 3, 768); Md = 768,
public static readonly BreakpointType Lg = new BreakpointType(nameof(Lg).ToLowerInvariant(), 4, 992); Lg = 992,
public static readonly BreakpointType Xl = new BreakpointType(nameof(Xl).ToLowerInvariant(), 5, 1200); Xl = 1200,
public static readonly BreakpointType Xxl = new BreakpointType(nameof(Xxl).ToLowerInvariant(), 6, 1600); Xxl = 1600,
public int Width { get; private set; }
private BreakpointType(string name, int value, int width) : base(name, value)
{
Width = width;
}
} }
} }

View File

@ -22,7 +22,7 @@ namespace AntDesign
[CascadingParameter] public Layout Parent { get; set; } [CascadingParameter] public Layout Parent { get; set; }
[Parameter] public BreakpointType Breakpoint { get; set; } [Parameter] public BreakpointType? Breakpoint { get; set; }
[Parameter] public SiderTheme Theme { get; set; } = SiderTheme.Dark; [Parameter] public SiderTheme Theme { get; set; } = SiderTheme.Dark;
@ -50,7 +50,8 @@ namespace AntDesign
[Parameter] [Parameter]
public EventCallback<bool> OnBreakpoint { get; set; } public EventCallback<bool> OnBreakpoint { get; set; }
[Inject] public DomEventService DomEventService { get; set; } [Inject]
private IDomEventListener DomEventListener { get; set; }
private int ComputedWidth => _isCollapsed ? CollapsedWidth : Width; private int ComputedWidth => _isCollapsed ? CollapsedWidth : Width;
@ -112,14 +113,14 @@ namespace AntDesign
if (firstRender && Breakpoint != null) if (firstRender && Breakpoint != null)
{ {
var dimensions = await JsInvokeAsync<Window>(JSInteropConstants.GetWindow); var dimensions = await JsInvokeAsync<Window>(JSInteropConstants.GetWindow);
DomEventService.AddEventListener<Window>("window", "resize", OnResize, false); DomEventListener.AddShared<Window>("window", "resize", OnResize);
OptimizeSize(dimensions.innerWidth); OptimizeSize(dimensions.InnerWidth);
} }
} }
private void OnResize(Window window) private void OnResize(Window window)
{ {
OptimizeSize(window.innerWidth); OptimizeSize(window.InnerWidth);
} }
public void ToggleCollapsed() public void ToggleCollapsed()
@ -138,7 +139,7 @@ namespace AntDesign
var originalCollapsed = _isCollapsed; var originalCollapsed = _isCollapsed;
var originlBrokenPoint = _brokenPoint; var originlBrokenPoint = _brokenPoint;
if (windowWidth < Breakpoint?.Width) if (windowWidth < (int)Breakpoint)
{ {
_brokenPoint = true; _brokenPoint = true;
_isCollapsed = true; _isCollapsed = true;
@ -171,9 +172,8 @@ namespace AntDesign
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
DomEventListener.Dispose();
base.Dispose(disposing); base.Dispose(disposing);
DomEventService.RemoveEventListerner<Window>("window", "resize", OnResize);
} }
} }
} }

View File

@ -3,7 +3,6 @@
@typeparam TItem @typeparam TItem
<div class="@ClassMapper.Class" style="@Style" Id="@Id" @ref="Ref"> <div class="@ClassMapper.Class" style="@Style" Id="@Id" @ref="Ref">
@if (Header != null) @if (Header != null)
{ {
<div class="@PrefixName-header"> <div class="@PrefixName-header">
@ -11,31 +10,27 @@
</div> </div>
} }
@if (DataSource != null && DataSource.Any()) @if (DataSource?.Any() == true)
{ {
<Spin Spinning="Loading"> <Spin Spinning="Loading">
@if (Grid != null) @if (Grid != null)
{ {
<Row Gutter="Grid.Gutter"> <Row Gutter="Grid.Gutter" OnBreakpoint="OnBreakpoint">
@foreach (var item in DataSource) @foreach (var item in DataSource)
{ {
<CascadingValue Value="ItemLayout"> <CascadingValue Value="this" IsFixed>
<CascadingValue Value="()=>HandleItemClick(item)" TValue="Action" Name="ItemClick"> @ChildContent(item)
@ChildContent(item) </CascadingValue>
</CascadingValue> }
</CascadingValue> </Row>
}
</Row>
} }
else else
{ {
<ul class="ant-list-items"> <ul class="ant-list-items">
@foreach (var item in DataSource) @foreach (var item in DataSource)
{ {
<CascadingValue Value="ItemLayout"> <CascadingValue Value="this" IsFixed>
<CascadingValue Value="()=>HandleItemClick(item)" TValue="Action" Name="ItemClick"> @ChildContent(item)
@ChildContent(item)
</CascadingValue>
</CascadingValue> </CascadingValue>
} }
</ul> </ul>

View File

@ -16,7 +16,7 @@ namespace AntDesign
public int Xxl { get; set; } public int Xxl { get; set; }
} }
public partial class AntList<TItem> : AntDomComponentBase public partial class AntList<TItem> : AntDomComponentBase, IAntList
{ {
public string PrefixName { get; set; } = "ant-list"; public string PrefixName { get; set; } = "ant-list";
@ -40,8 +40,6 @@ namespace AntDesign
[Parameter] public bool Split { get; set; } = true; [Parameter] public bool Split { get; set; } = true;
[Parameter] public EventCallback<TItem> OnItemClick { get; set; }
[Parameter] public ListGridType Grid { get; set; } [Parameter] public ListGridType Grid { get; set; }
[Parameter] public PaginationOptions Pagination { get; set; } [Parameter] public PaginationOptions Pagination { get; set; }
@ -56,49 +54,63 @@ namespace AntDesign
} }
} }
private string SizeCls => Size switch
{
"large" => "lg",
"small" => "sm",
_ => string.Empty
};
ListGridType IAntList.Grid => Grid;
ListItemLayout IAntList.ItemLayout => ItemLayout;
double IAntList.ColumnWidth => _columnWidth;
double _columnWidth;
protected override void OnInitialized() protected override void OnInitialized()
{ {
SetClassMap(); SetClassMap();
if (Grid?.Column > 0)
{
_columnWidth = 100d / Grid.Column;
}
base.OnInitialized(); base.OnInitialized();
} }
protected void SetClassMap() protected void SetClassMap()
{ {
// large => lg
// small => sm
string sizeCls = string.Empty;
switch (Size)
{
case "large":
sizeCls = "lg";
break;
case "small":
sizeCls = "sm";
break;
default:
break;
}
ClassMapper.Clear() ClassMapper.Clear()
.Add(PrefixName) .Add(PrefixName)
.If($"{PrefixName}-split", () => Split) .If($"{PrefixName}-split", () => Split)
.If($"{PrefixName}-rtl", () => RTL) .If($"{PrefixName}-rtl", () => RTL)
.If($"{PrefixName}-bordered", () => Bordered) .If($"{PrefixName}-bordered", () => Bordered)
.GetIf(() => $"{PrefixName}-{sizeCls}", () => !string.IsNullOrEmpty(sizeCls)) .GetIf(() => $"{PrefixName}-{SizeCls}", () => !string.IsNullOrEmpty(SizeCls))
.If($"{PrefixName}-vertical", () => ItemLayout == ListItemLayout.Vertical) .If($"{PrefixName}-vertical", () => ItemLayout == ListItemLayout.Vertical)
.If($"{PrefixName}-loading", () => (Loading)) .If($"{PrefixName}-loading", () => (Loading))
.If($"{PrefixName}-grid", () => Grid != null) .If($"{PrefixName}-grid", () => Grid != null)
.If($"{PrefixName}-something-after-last-item", () => IsSomethingAfterLastItem); .If($"{PrefixName}-something-after-last-item", () => IsSomethingAfterLastItem);
} }
private void HandleItemClick(TItem item) private void OnBreakpoint(BreakpointType breakPoint)
{ {
if (OnItemClick.HasDelegate) var column = breakPoint switch
{ {
OnItemClick.InvokeAsync(item); BreakpointType.Xs => Grid.Xs,
BreakpointType.Sm => Grid.Sm,
BreakpointType.Md => Grid.Md,
BreakpointType.Lg => Grid.Lg,
BreakpointType.Xl => Grid.Xl,
BreakpointType.Xxl => Grid.Xxl,
_ => 4,
};
if (column > 0)
{
_columnWidth = 100d / column;
} }
} }
} }

View File

@ -0,0 +1,20 @@
// 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.Text;
using Microsoft.AspNetCore.Components;
namespace AntDesign
{
internal interface IAntList
{
internal ListGridType Grid { get; }
internal ListItemLayout ItemLayout { get; }
internal double ColumnWidth { get; }
}
}

View File

@ -9,18 +9,20 @@
} }
else else
{ {
<AntDesign.Col Flex="1" Style="@ColStyle"> <div style="width: @(AntList.ColumnWidth)%; max-width: @(AntList.ColumnWidth)%;">
<div class="@ClassMapper.Class" style="@Style" Id="@Id" @onclick="HandleClick" @onclick:stopPropagation @ref="Ref"> <AntDesign.Col Flex="1" >
@itemChildren(this) <div class="@ClassMapper.Class" style="@Style" Id="@Id" @onclick="HandleClick" @onclick:stopPropagation @ref="Ref">
</div> @itemChildren(this)
</AntDesign.Col> </div>
</AntDesign.Col>
</div>
} }
@code{ @code{
RenderFragment<ListItem> itemChildren = content => RenderFragment<ListItem> itemChildren = content =>
@<Template> @<Template>
@if (content.ItemLayout == ListItemLayout.Vertical && content.Extra != null) @if (content.IsVerticalAndExtra)
{ {
<div class="@content.PrefixName-main"> <div class="@content.PrefixName-main">
@content.ChildContent @content.ChildContent

View File

@ -1,9 +1,4 @@
using System; using System.Threading.Tasks;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using AntDesign.JsInterop; using AntDesign.JsInterop;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
@ -19,8 +14,6 @@ namespace AntDesign
[Parameter] public RenderFragment[] Actions { get; set; } [Parameter] public RenderFragment[] Actions { get; set; }
[Parameter] public ListGridType Grid { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; } [Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public string ColStyle { get; set; } [Parameter] public string ColStyle { get; set; }
@ -31,86 +24,21 @@ namespace AntDesign
[Parameter] public bool NoFlex { get; set; } [Parameter] public bool NoFlex { get; set; }
[CascadingParameter] public ListItemLayout ItemLayout { get; set; } [CascadingParameter]
private IAntList AntList { get; set; }
[CascadingParameter(Name = "ItemClick")] public Action ItemClick { get; set; }
[Inject] [Inject]
public DomEventService DomEventService { get; set; } private IDomEventListener DomEventListener { get; set; }
public bool IsVerticalAndExtra() public bool IsVerticalAndExtra => AntList?.ItemLayout == ListItemLayout.Vertical && this.Extra != null;
{ private ListGridType Grid => AntList.Grid;
return this.ItemLayout == ListItemLayout.Vertical && this.Extra != null;
}
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
SetClassMap(); SetClassMap();
await base.OnInitializedAsync(); await base.OnInitializedAsync();
} }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && Grid != null)
{
await this.SetGutterStyle();
DomEventService.AddEventListener<object>("window", "resize", OnResize, false);
}
await base.OnAfterRenderAsync(firstRender);
}
private async void OnResize(object o)
{
await SetGutterStyle();
}
private static Hashtable _gridResponsiveMap = new Hashtable()
{
[nameof(BreakpointEnum.xs)] = "(max-width: 575px)",
[nameof(BreakpointEnum.sm)] = "(max-width: 576px)",
[nameof(BreakpointEnum.md)] = "(max-width: 768px)",
[nameof(BreakpointEnum.lg)] = "(max-width: 992px)",
[nameof(BreakpointEnum.xl)] = "(max-width: 1200px)",
[nameof(BreakpointEnum.xxl)] = "(max-width: 1600px)",
};
private async Task SetGutterStyle()
{
string breakPoint = null;
await typeof(BreakpointEnum).GetEnumNames().ForEachAsync(async bp =>
{
if (await JsInvokeAsync<bool>(JSInteropConstants.MatchMedia, _gridResponsiveMap[bp]))
{
breakPoint = bp;
}
});
var column = GetColumn(breakPoint);
int columnCount = column > 0 ? column : (Grid?.Column ?? 0);
if (Grid != null && columnCount > 0)
{
ColStyle = $"width:{100 / columnCount}%;max-width:{100 / columnCount}%";
}
InvokeStateHasChanged();
}
private int GetColumn(string breakPoint)
{
var column = 0;
if (Grid != null && !string.IsNullOrEmpty(breakPoint))
{
var value = GetModelValue(breakPoint, Grid);
int.TryParse(value, out column);
return column;
}
return column;
}
protected void SetClassMap() protected void SetClassMap()
{ {
ClassMapper.Clear() ClassMapper.Clear()
@ -118,48 +46,18 @@ namespace AntDesign
.If($"{PrefixName}-no-flex", () => NoFlex); .If($"{PrefixName}-no-flex", () => NoFlex);
} }
private bool IsFlexMode()
{
if (ItemLayout == ListItemLayout.Vertical)
{
return Extra != null;
}
return (Actions != null || Grid != null) && ItemCount > 1;
}
private string GetModelValue(string fieldName, object obj)
{
try
{
if (obj == null || string.IsNullOrEmpty(fieldName)) return null;
var o = obj.GetType().GetProperty(fieldName, BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance).GetValue(obj, null);
var value = o?.ToString() ?? null;
if (string.IsNullOrEmpty(value)) return null;
return value;
}
catch (Exception)
{
return null;
}
}
private void HandleClick() private void HandleClick()
{ {
if (OnClick.HasDelegate) if (OnClick.HasDelegate)
{ {
OnClick.InvokeAsync(this); OnClick.InvokeAsync(this);
} }
ItemClick?.Invoke();
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
DomEventListener.Dispose();
base.Dispose(disposing); base.Dispose(disposing);
DomEventService.RemoveEventListerner<object>("window", "resize", OnResize);
} }
} }
} }

View File

@ -36,7 +36,6 @@ namespace AntDesign
public Dictionary<string, object> Attributes { get; set; } public Dictionary<string, object> Attributes { get; set; }
[Inject] private DomEventService DomEventService { get; set; }
internal List<MentionsOption> LstOriginalOptions { get; set; } = new List<MentionsOption>(); internal List<MentionsOption> LstOriginalOptions { get; set; } = new List<MentionsOption>();
private string DropdownStyle { get; set; } private string DropdownStyle { get; set; }
@ -63,9 +62,6 @@ namespace AntDesign
{ {
Value = DefaultValue; Value = DefaultValue;
} }
//DomEventService.AddEventListener(Ref, "keyup", OnTextAreaKeyup);
// DomEventService.AddEventListener(Ref, "onmouseup", OnTextAreaMouseUp);
} }
internal bool FirstTime { get; set; } = true; internal bool FirstTime { get; set; } = true;

View File

@ -20,6 +20,7 @@ namespace AntDesign
[Parameter] [Parameter]
public MenuTheme Theme { get; set; } = MenuTheme.Light; public MenuTheme Theme { get; set; } = MenuTheme.Light;
internal MenuMode? InitialMode { get; private set; }
[Parameter] [Parameter]
public MenuMode Mode public MenuMode Mode
{ {
@ -110,7 +111,7 @@ namespace AntDesign
public EventCallback<string[]> SelectedKeysChanged { get; set; } public EventCallback<string[]> SelectedKeysChanged { get; set; }
[Parameter] [Parameter]
public TriggerType TriggerSubMenuAction { get; set; } = TriggerType.Hover; public Trigger TriggerSubMenuAction { get; set; } = Trigger.Hover;
internal MenuMode InternalMode { get; private set; } internal MenuMode InternalMode { get; private set; }
@ -223,7 +224,7 @@ namespace AntDesign
throw new ArgumentException($"{nameof(Menu)} in the {Mode} mode cannot be {nameof(InlineCollapsed)}"); throw new ArgumentException($"{nameof(Menu)} in the {Mode} mode cannot be {nameof(InlineCollapsed)}");
InternalMode = Mode; InternalMode = Mode;
InitialMode = Mode;
Parent?.AddMenu(this); Parent?.AddMenu(this);
OpenKeys = DefaultOpenKeys?.ToArray() ?? OpenKeys; OpenKeys = DefaultOpenKeys?.ToArray() ?? OpenKeys;

View File

@ -2,43 +2,67 @@
@inherits AntDomComponentBase @inherits AntDomComponentBase
<CascadingValue Value="this" IsFixed> <CascadingValue Value="this" IsFixed>
<Tooltip Title="@content(this)" Placement="@PlacementType.Right" Disabled="TooltipDisabled"> @* There is no need to render the tooltip if there is no inline mode. Tooltip will be only showing menu content if menu is collapsed to icon version && only for root menu *@
<Unbound> @if (RootMenu.Mode == MenuMode.Inline && ParentMenu is null)
<li class="@ClassMapper.Class" role="menuitem" style=" @(PaddingLeft>0? $"padding-left:{PaddingLeft}px;":"") @Style" @onclick="HandleOnClick" @key="Key" @ref="context.Current"> {
<Tooltip Title="@content(this)" Placement="@Placement.Right" Disabled="TooltipDisabled">
<Unbound Context="tooltip">
<li class="@ClassMapper.Class" role="menuitem" style=" @(PaddingLeft>0? $"padding-left:{PaddingLeft}px;":"") @Style" @onclick="HandleOnClick" @key="Key" @ref="tooltip.Current">
@if (IconTemplate != null)
{
@IconTemplate
}
else if (Icon != null)
{
<Icon Type="@Icon" />
}
<span class="ant-menu-title-content">
@if (RouterLink == null)
{
@content(this)
}
else
{
<MenuLink Href="@RouterLink" Match="@RouterMatch">@content(this)</MenuLink>
}
</span>
</li>
</Unbound>
</Tooltip>
}
else
{
<li class="@ClassMapper.Class" role="menuitem" style=" @(PaddingLeft>0? $"padding-left:{PaddingLeft}px;":"") @Style" @onclick="HandleOnClick" @key="Key">
@if (Icon != null)
{
<Icon Type="@Icon" />
}
@if (IconTemplate != null) <span class="ant-menu-title-content">
{ @if (RouterLink == null)
@IconTemplate {
} @content(this)
else if (Icon != null) }
{ else
<Icon Type="@Icon" /> {
} <MenuLink Href="@RouterLink" Match="@RouterMatch">@content(this)</MenuLink>
}
@if (RouterLink == null) </span>
{ </li>
@content(this) }
}
else
{
<MenuLink Href="@RouterLink" Match="@RouterMatch">@content(this)</MenuLink>
}
</li>
</Unbound>
</Tooltip>
</CascadingValue> </CascadingValue>
@code { @code {
RenderFragment<MenuItem> content = item =>@<Template> RenderFragment<MenuItem> content = item =>
<span> @<Template>
@if (item.Title != null) @if (item.Title != null)
{ {
@item.Title @item.Title
} }
else else
{ {
@item.ChildContent @item.ChildContent
} }
</span> </Template>
</Template>; ;
} }

View File

@ -31,7 +31,7 @@ else
{ {
if (Placement == null) if (Placement == null)
{ {
Placement = (RootMenu.Mode == MenuMode.Horizontal && Parent == null) ? PlacementType.BottomLeft : PlacementType.RightTop; Placement = (RootMenu.Mode == MenuMode.Horizontal && Parent == null) ? AntDesign.Placement.BottomLeft : AntDesign.Placement.RightTop;
} }
<CascadingValue Value="this" Name="SubMenu" IsFixed="@true"> <CascadingValue Value="this" Name="SubMenu" IsFixed="@true">
@ -41,11 +41,11 @@ else
ComplexAutoCloseAndVisible="true" ComplexAutoCloseAndVisible="true"
BoundaryAdjustMode="@TriggerBoundaryAdjustMode.None" BoundaryAdjustMode="@TriggerBoundaryAdjustMode.None"
Disabled="Disabled" Disabled="Disabled"
Placement="Placement" Placement="Placement.Value"
OnVisibleChange="OnOverlayVisibleChange" OnVisibleChange="OnOverlayVisibleChange"
OnOverlayHiding="OnOverlayHiding" OnOverlayHiding="OnOverlayHiding"
Trigger="new[] { RootMenu?.TriggerSubMenuAction }" Trigger="new Trigger[] { RootMenu?.TriggerSubMenuAction ?? Trigger.Hover }"
PlacementCls="@($"{prefixCls}-placement-{Placement.Name} {prefixCls}-popup")" PlacementCls="@($"{prefixCls}-placement-{_placement.Name} {prefixCls}-popup")"
OverlayEnterCls="@($"{prefixCls}-{RootMenu.Theme} ")" OverlayEnterCls="@($"{prefixCls}-{RootMenu.Theme} ")"
OverlayLeaveCls="@($"{prefixCls}-{RootMenu.Theme} ")" OverlayLeaveCls="@($"{prefixCls}-{RootMenu.Theme} ")"
OverlayHiddenCls="@($"{RootMenu.PrefixCls}-hidden")"> OverlayHiddenCls="@($"{RootMenu.PrefixCls}-hidden")">

View File

@ -16,7 +16,21 @@ namespace AntDesign
public SubMenu Parent { get; set; } public SubMenu Parent { get; set; }
[Parameter] [Parameter]
public PlacementType Placement { get; set; } public Placement? Placement
{
get { return _placement?.Placement; }
set
{
if (value is null)
{
_placement = null;
}
else
{
_placement = PlacementType.Create(value.Value);
}
}
}
[Parameter] [Parameter]
public string Title { get; set; } public string Title { get; set; }
@ -57,6 +71,7 @@ namespace AntDesign
private OverlayTrigger _overlayTrigger; private OverlayTrigger _overlayTrigger;
internal bool _overlayVisible; internal bool _overlayVisible;
private PlacementType? _placement;
private void SetClass() private void SetClass()
{ {
@ -136,7 +151,16 @@ namespace AntDesign
base.OnParametersSet(); base.OnParametersSet();
if (!RootMenu.InlineCollapsed && RootMenu.OpenKeys.Contains(Key)) if (!RootMenu.InlineCollapsed && RootMenu.OpenKeys.Contains(Key))
IsOpen = true; {
if (RootMenu.InitialMode != RootMenu.Mode)
{
IsOpen = false;
}
else
{
IsOpen = true;
}
}
} }
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)

View File

@ -7,16 +7,18 @@
{ {
<div class=@($"{Config.PrefixCls}-mask {GetMaskClsName()}") style="@Config.MaskStyle"></div> <div class=@($"{Config.PrefixCls}-mask {GetMaskClsName()}") style="@Config.MaskStyle"></div>
} }
<div tabindex="-1" id=@($"{Config.PrefixCls}-wrap_{DialogWrapperId}") class=@($"{Config.PrefixCls}-wrap {Config.GetWrapClassNameExtended()}") role="dialog" <div tabindex="-1" class=@($"{Config.PrefixCls}-wrap {Config.GetWrapClassNameExtended()}") role="dialog"
@onclick="@EventUtil.AsNonRenderingEventHandler(OnMaskClick)" @onclick="@EventUtil.AsNonRenderingEventHandler(OnMaskClick)"
@onmouseup="@EventUtil.AsNonRenderingEventHandler(OnMaskMouseUp)" @onmouseup="@EventUtil.AsNonRenderingEventHandler(OnMaskMouseUp)"
@onkeydown="@OnKeyDown" @onkeydown="@OnKeyDown"
style="@_wrapStyle"> style="@_wrapStyle">
<div @ref="@_modal" role="document" class=@($"{Config.PrefixCls} {GetModalClsName()}") <div @ref="@_modal" role="document" class=@($"{Config.PrefixCls} {GetModalClsName()}")
@onclick:stopPropagation
@onmousedown="@EventUtil.AsNonRenderingEventHandler(OnDialogMouseDown)" @onmousedown="@EventUtil.AsNonRenderingEventHandler(OnDialogMouseDown)"
style="@GetStyle()"> style="@GetStyle()"
>
<div id="@_sentinelStart" tabindex="0" aria-hidden="true" style="width: 0px; height: 0px; overflow: hidden; outline: none;"></div> <div id="@_sentinelStart" tabindex="0" aria-hidden="true" style="width: 0px; height: 0px; overflow: hidden; outline: none;"></div>
<div class=@($"{Config.PrefixCls}-content")> <div class=@($"{Config.PrefixCls}-content") id=@($"{Config.PrefixCls}-wrap_{DialogWrapperId}") >
@if (Config.Closable) @if (Config.Closable)
{ {
<button type="button" aria-label="Close" class=@($"{Config.PrefixCls}-close") @onclick="@OnCloserClick"> <button type="button" aria-label="Close" class=@($"{Config.PrefixCls}-close") @onclick="@OnCloserClick">

View File

@ -25,6 +25,7 @@
Size="@(IsSmall ? "small" : "default")" Size="@(IsSmall ? "small" : "default")"
Class="@($"{prefixCls}-size-changer")" Class="@($"{prefixCls}-size-changer")"
DefaultValue="@(PageSize > 0 ? PageSize : pageSizeOptions[0])" DefaultValue="@(PageSize > 0 ? PageSize : pageSizeOptions[0])"
BoundaryAdjustMode="@TriggerBoundaryAdjustMode.InView"
OnSelectedItemChanged="@(i => {if (ChangeSize.HasDelegate)ChangeSize.InvokeAsync(i);})"> OnSelectedItemChanged="@(i => {if (ChangeSize.HasDelegate)ChangeSize.InvokeAsync(i);})">
<SelectOptions> <SelectOptions>
@foreach(var opt in pageSizeOptions) @foreach(var opt in pageSizeOptions)

View File

@ -54,13 +54,13 @@ namespace AntDesign
public Popconfirm() public Popconfirm()
{ {
PrefixCls = "ant-popover"; PrefixCls = "ant-popover";
Placement = PlacementType.Top; Placement = Placement.Top;
Trigger = new[] { TriggerType.Click }; Trigger = new[] { AntDesign.Trigger.Click };
} }
internal override async Task Show(int? overlayLeft = null, int? overlayTop = null) internal override async Task Show(int? overlayLeft = null, int? overlayTop = null)
{ {
if (Trigger.Contains(TriggerType.Hover)) if (Trigger.Contains(AntDesign.Trigger.Hover))
{ {
await Task.Delay((int)(MouseEnterDelay * 1000)); await Task.Delay((int)(MouseEnterDelay * 1000));
} }
@ -70,7 +70,7 @@ namespace AntDesign
internal override async Task Hide(bool force = false) internal override async Task Hide(bool force = false)
{ {
if (Trigger.Contains(TriggerType.Hover)) if (Trigger.Contains(AntDesign.Trigger.Hover))
{ {
await Task.Delay((int)(MouseLeaveDelay * 1000)); await Task.Delay((int)(MouseLeaveDelay * 1000));
} }

View File

@ -32,7 +32,7 @@ namespace AntDesign
public Popover() public Popover()
{ {
PrefixCls = "ant-popover"; PrefixCls = "ant-popover";
Placement = PlacementType.Top; Placement = Placement.Top;
} }
internal override string GetOverlayEnterClass() internal override string GetOverlayEnterClass()
@ -47,7 +47,7 @@ namespace AntDesign
internal override async Task Show(int? overlayLeft = null, int? overlayTop = null) internal override async Task Show(int? overlayLeft = null, int? overlayTop = null)
{ {
if (Trigger.Contains(TriggerType.Hover)) if (Trigger.Contains(AntDesign.Trigger.Hover))
{ {
await Task.Delay((int)(MouseEnterDelay * 1000)); await Task.Delay((int)(MouseEnterDelay * 1000));
} }
@ -57,7 +57,7 @@ namespace AntDesign
internal override async Task Hide(bool force = false) internal override async Task Hide(bool force = false)
{ {
if (Trigger.Contains(TriggerType.Hover)) if (Trigger.Contains(AntDesign.Trigger.Hover))
{ {
await Task.Delay((int)(MouseLeaveDelay * 1000)); await Task.Delay((int)(MouseLeaveDelay * 1000));
} }

View File

@ -11,8 +11,9 @@
<OverlayTrigger @ref="@_dropDown" <OverlayTrigger @ref="@_dropDown"
Visible="Open" Visible="Open"
Disabled="Disabled" Disabled="Disabled"
Trigger="new[] { TriggerType.Click }" Trigger="new[] { Trigger.Click }"
HiddenMode HiddenMode
BoundaryAdjustMode="@BoundaryAdjustMode"
OnMouseEnter="@(() => { OnMouseEnter?.Invoke(); })" OnMouseEnter="@(() => { OnMouseEnter?.Invoke(); })"
OnMouseLeave="@(() => { OnMouseLeave?.Invoke(); })" OnMouseLeave="@(() => { OnMouseLeave?.Invoke(); })"
OnVisibleChange="@OnOverlayVisibleChangeAsync" OnVisibleChange="@OnOverlayVisibleChangeAsync"
@ -23,7 +24,7 @@
<div style="@_dropdownStyle"> <div style="@_dropdownStyle">
@if (SelectOptions != null) @if (SelectOptions != null)
{ {
<div class="" style="max-height: @PopupContainerMaxHeight; overflow-y: auto;"> <div class="" style="max-height: @PopupContainerMaxHeight; overflow-y: auto;" @ref="_scrollableSelectDiv">
<div> <div>
<div class="" role="listbox" style="display: flex; flex-direction: column;"> <div class="" role="listbox" style="display: flex; flex-direction: column;">
@if (CustomTagSelectOptionItem != null) @if (CustomTagSelectOptionItem != null)

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using AntDesign.Core.Helpers.MemberPath; using AntDesign.Core.Helpers.MemberPath;
@ -21,84 +22,70 @@ namespace AntDesign
{ {
#region Parameters #region Parameters
/// <summary>
/// Overlay adjustment strategy (when for example browser resize is happening)
/// </summary>
[Parameter]
public TriggerBoundaryAdjustMode BoundaryAdjustMode { get; set; } = TriggerBoundaryAdjustMode.None;
/// <summary> /// <summary>
/// Toggle the border style. /// Toggle the border style.
/// </summary> /// </summary>
[Parameter] public bool Bordered { get; set; } = true; [Parameter] public bool Bordered { get; set; } = true;
bool _dataSourceHasChanged = false;
IEnumerable<TItem> _dataSourceCopy;
IEnumerable<TItem> _dataSourceShallowCopy;
//private bool? _isTItemPrimitive;
//private bool IsTItemPrimitive
//{
// get
// {
// if (_isTItemPrimitive is null)
// {
// _isTItemPrimitive = IsSimpleType(typeof(TItem));
// }
// return _isTItemPrimitive!.Value;
// }
//}
/// <summary> /// <summary>
/// The datasource for this component. /// MethodInfo will contain attached MemberwiseClone protected
/// method. Due to its protection level, it has to be accessed
/// using reflection. It will be used during generation of
/// the DataSource shallow copy (which is a new list of DataSource
/// items with shallow copy of each item).
/// </summary> /// </summary>
[Parameter] private MethodInfo _dataSourceItemShallowCopyMehtod;
public IEnumerable<TItem> DataSource private MethodInfo GetDataSourceItemCloneMethod()
{ {
get => _datasource; if (_dataSourceItemShallowCopyMehtod is null)
set
{ {
if (value == null && _datasource == null) _dataSourceItemShallowCopyMehtod = this.GetType().GetGenericArguments()[1]
.GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic);
if (DataSourceEqualityComparer is null)
{ {
return; DataSourceEqualityComparer = new DataSourceEqualityComparer<TItemValue, TItem>(this);
}
if (value == null && _datasource != null)
{
if (!_isInitialized)
{
_selectedValue = default;
}
else
{
SelectOptionItems.Clear();
SelectedOptionItems.Clear();
Value = default;
_datasource = null;
OnDataSourceChanged?.Invoke();
}
return;
}
if (value != null && !value.Any() && SelectOptionItems.Any())
{
SelectOptionItems.Clear();
SelectedOptionItems.Clear();
Value = default;
var sameObject = object.ReferenceEquals(_datasource, value);
_datasource = value;
if (!sameObject)
OnDataSourceChanged?.Invoke();
return;
}
if (value != null)
{
bool hasChanged;
if (_datasource == null)
{
hasChanged = true;
}
else
{
hasChanged = !value.SequenceEqual(_datasource);
}
if (hasChanged)
{
OnDataSourceChanged?.Invoke();
_datasource = value;
}
} }
} }
return _dataSourceItemShallowCopyMehtod;
} }
/// <summary>
/// The datasource for this component.
/// </summary>
[Parameter]
public IEnumerable<TItem> DataSource { get; set; }
/// <summary>
/// EqualityComparer that will be used during DataSource change
/// detection. If no comparer set, default .Net is going to be
/// used.
/// </summary>
[Parameter]
public IEqualityComparer<TItem> DataSourceEqualityComparer { get; set; }
/// <summary> /// <summary>
/// Activates the first item that is not deactivated. /// Activates the first item that is not deactivated.
/// </summary> /// </summary>
@ -315,6 +302,7 @@ namespace AntDesign
if (_valueHasChanged) if (_valueHasChanged)
{ {
_selectedValue = value; _selectedValue = value;
_valueHasChanged = _isInitialized;
} }
} }
} }
@ -339,13 +327,9 @@ namespace AntDesign
} }
} }
#endregion Parameters #endregion Parameters
[Inject] private DomEventService DomEventService { get; set; } [Inject] private IDomEventListener DomEventListener { get; set; }
#region Properties #region Properties
@ -359,12 +343,14 @@ namespace AntDesign
} }
internal ElementReference DropDownRef => _dropDown.GetOverlayComponent().Ref; internal ElementReference DropDownRef => _dropDown.GetOverlayComponent().Ref;
private ElementReference _scrollableSelectDiv;
private string _dropdownStyle = string.Empty; private string _dropdownStyle = string.Empty;
private TItemValue _selectedValue; private TItemValue _selectedValue;
private TItemValue _defaultValue; private TItemValue _defaultValue;
private bool _defaultValueIsNotNull; private bool _defaultValueIsNotNull;
private IEnumerable<TItem> _datasource; private IEnumerable<TItem> _datasource;
private bool _afterFirstRender;
private bool _optionsHasInitialized; private bool _optionsHasInitialized;
private bool _defaultValueApplied; private bool _defaultValueApplied;
private bool _defaultActiveFirstOptionApplied; private bool _defaultActiveFirstOptionApplied;
@ -376,7 +362,7 @@ namespace AntDesign
private string _labelName; private string _labelName;
private Func<TItem, string> _getLabel; internal Func<TItem, string> _getLabel;
private string _groupName = string.Empty; private string _groupName = string.Empty;
@ -389,7 +375,7 @@ namespace AntDesign
private string _valueName; private string _valueName;
private Func<TItem, TItemValue> _getValue; internal Func<TItem, TItemValue> _getValue;
private bool _disableSubmitFormOnEnter; private bool _disableSubmitFormOnEnter;
private bool _showArrowIcon = true; private bool _showArrowIcon = true;
@ -441,10 +427,15 @@ namespace AntDesign
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
EvaluateDataSourceChange();
if (SelectOptions == null) if (SelectOptions == null)
{ {
CreateDeleteSelectOptions(); if (!_optionsHasInitialized || _dataSourceHasChanged)
_optionsHasInitialized = true; {
CreateDeleteSelectOptions();
_optionsHasInitialized = true;
_dataSourceHasChanged = false;
}
} }
if (_valueHasChanged && _optionsHasInitialized) if (_valueHasChanged && _optionsHasInitialized)
@ -456,10 +447,116 @@ namespace AntDesign
EditContext?.NotifyFieldChanged(FieldIdentifier); EditContext?.NotifyFieldChanged(FieldIdentifier);
} }
} }
base.OnParametersSet(); base.OnParametersSet();
} }
private void EvaluateDataSourceChange()
{
if (DataSource == null && _datasource == null)
{
return;
}
if (DataSource == null && _datasource != null)
{
SelectOptionItems.Clear();
SelectedOptionItems.Clear();
Value = default;
_datasource = null;
_dataSourceCopy = null;
_dataSourceShallowCopy = null;
OnDataSourceChanged?.Invoke();
return;
}
if (DataSource != null && !DataSource.Any() && SelectOptionItems.Any())
{
SelectOptionItems.Clear();
SelectedOptionItems.Clear();
Value = default;
_datasource = DataSource;
_dataSourceShallowCopy = new List<TItem>();
_dataSourceCopy = new List<TItem>();
OnDataSourceChanged?.Invoke();
return;
}
if (DataSource != null)
{
if (_datasource == null)
{
_dataSourceHasChanged = true;
}
else if (_isPrimitive)
{
_dataSourceHasChanged = !DataSource.SequenceEqual(_dataSourceCopy);
}
else if (_getValue is null)
{
_dataSourceHasChanged = !DataSource.SequenceEqual(_dataSourceCopy) ||
!DataSource.SequenceEqual(_dataSourceShallowCopy, DataSourceEqualityComparer);
}
else
{
_dataSourceHasChanged = !DataSource.SequenceEqual(_dataSourceShallowCopy, DataSourceEqualityComparer);
}
if (_dataSourceHasChanged)
{
OnDataSourceChanged?.Invoke();
_datasource = DataSource;
if (_isPrimitive)
{
_dataSourceCopy = _datasource.ToList();
}
else
{
if (_getValue is null)
{
_dataSourceCopy = _datasource.ToList();
}
var cloneMethod = GetDataSourceItemCloneMethod();
_dataSourceShallowCopy = _datasource.Select(x => (TItem)cloneMethod.Invoke(x, null)).ToList();
}
}
}
}
/// <summary>
/// Used only when ChildElement SelectOptions is used.
/// Will run this process if after initalization an item
/// is added that is also marked as selected.
/// </summary>
/// <returns></returns>
internal async Task ProcessSelectedSelectOptions()
{
if (_isInitialized && _afterFirstRender)
{
if (Mode == "default")
{
if (LastValueBeforeReset is not null)
{
OnValueChange(LastValueBeforeReset);
LastValueBeforeReset = default;
}
else
{
OnValueChange(Value);
}
}
else
{
await OnValuesChangeAsync(Values);
}
}
}
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
if (SelectOptions != null) if (SelectOptions != null)
@ -471,7 +568,7 @@ namespace AntDesign
{ {
await SetInitialValuesAsync(); await SetInitialValuesAsync();
DomEventService.AddEventListener("window", "resize", OnWindowResize, false); DomEventListener.AddShared<JsonElement>("window", "resize", OnWindowResize);
await SetDropdownStyleAsync(); await SetDropdownStyleAsync();
_defaultValueApplied = !(_defaultValueIsNotNull || _defaultValuesHasItems); _defaultValueApplied = !(_defaultValueIsNotNull || _defaultValuesHasItems);
@ -511,11 +608,12 @@ namespace AntDesign
} }
await base.OnAfterRenderAsync(firstRender); await base.OnAfterRenderAsync(firstRender);
_afterFirstRender = true;
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
DomEventService.RemoveEventListerner<JsonElement>("window", "resize", OnWindowResize); DomEventListener.Dispose();
base.Dispose(disposing); base.Dispose(disposing);
} }
@ -567,7 +665,7 @@ namespace AntDesign
foreach (var item in _datasource) foreach (var item in _datasource)
{ {
TItemValue value = _getValue == null ? THelper.ChangeType<TItemValue>(item) : _getValue(item); TItemValue value = _getValue == null ? (TItemValue)(object)item : _getValue(item);
var exists = false; var exists = false;
SelectOptionItem<TItemValue, TItem> selectOption; SelectOptionItem<TItemValue, TItem> selectOption;
@ -725,7 +823,7 @@ namespace AntDesign
/// <returns></returns> /// <returns></returns>
private async Task ElementScrollIntoViewAsync(ElementReference element) private async Task ElementScrollIntoViewAsync(ElementReference element)
{ {
await JsInvokeAsync(JSInteropConstants.ScrollTo, element); await JsInvokeAsync(JSInteropConstants.ScrollTo, element, _scrollableSelectDiv);
} }
@ -960,14 +1058,20 @@ namespace AntDesign
return newItem; return newItem;
} }
protected virtual string GetLabel(TItem item) protected virtual string GetLabel(TItem item)
{ {
return item.ToString(); return item.ToString();
} }
#region Events #region Events
/// <summary>
/// When newly set Value is not found in SelectOptionItems, it is reset to
/// default. This property holds the value before reset. It may be needed
/// to be reaplied (for example when new Value is set at the same time
/// as new SelectOption is added, but Value in the component is set
/// before new SelectOptionItem has been created).
/// </summary>
internal TItemValue LastValueBeforeReset { get; set; }
/// <summary> /// <summary>
/// The Method is called every time if the value of the @bind-Value was changed by the two-way binding. /// The Method is called every time if the value of the @bind-Value was changed by the two-way binding.
@ -987,6 +1091,11 @@ namespace AntDesign
if (result == null) if (result == null)
{ {
if (SelectOptions is not null)
{
LastValueBeforeReset = value;
}
if (!AllowClear) if (!AllowClear)
_ = TrySetDefaultValueAsync(); _ = TrySetDefaultValueAsync();
else else
@ -1399,9 +1508,6 @@ namespace AntDesign
currentSelected.IsActive = true; currentSelected.IsActive = true;
ActiveOption = currentSelected; ActiveOption = currentSelected;
// ToDo: Sometime the element does not scroll, you have to call the function twice
await ElementScrollIntoViewAsync(currentSelected.Ref);
await Task.Delay(1);
await ElementScrollIntoViewAsync(currentSelected.Ref); await ElementScrollIntoViewAsync(currentSelected.Ref);
} }
@ -1504,9 +1610,6 @@ namespace AntDesign
currentSelected.IsActive = true; currentSelected.IsActive = true;
ActiveOption = currentSelected; ActiveOption = currentSelected;
// ToDo: Sometime the element does not scroll, you have to call the function twice
await ElementScrollIntoViewAsync(currentSelected.Ref);
await Task.Delay(1);
await ElementScrollIntoViewAsync(currentSelected.Ref); await ElementScrollIntoViewAsync(currentSelected.Ref);
} }
@ -1703,9 +1806,6 @@ namespace AntDesign
currentSelected.IsActive = true; currentSelected.IsActive = true;
ActiveOption = currentSelected; ActiveOption = currentSelected;
// ToDo: Sometime the element does not scroll, you have to call the function twice
await ElementScrollIntoViewAsync(currentSelected.Ref);
await Task.Delay(1);
await ElementScrollIntoViewAsync(currentSelected.Ref); await ElementScrollIntoViewAsync(currentSelected.Ref);
} }
else if (ActiveOption == null)//position on first element in the list else if (ActiveOption == null)//position on first element in the list

View File

@ -161,6 +161,7 @@ namespace AntDesign
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
bool isAlreadySelected = false;
if (SelectParent.SelectOptions == null) if (SelectParent.SelectOptions == null)
{ {
// The SelectOptionItem was already created, now only the SelectOption has to be // The SelectOptionItem was already created, now only the SelectOption has to be
@ -176,8 +177,9 @@ namespace AntDesign
GroupName = Model.GroupName; GroupName = Model.GroupName;
Value = Model.Value; Value = Model.Value;
Model.ChildComponent = this; Model.ChildComponent = this;
isAlreadySelected = IsAlreadySelected(Model);
} }
else else
{ {
// The SelectOption was not created by using a DataSource, a SelectOptionItem must be created. // The SelectOption was not created by using a DataSource, a SelectOptionItem must be created.
InternalId = Guid.NewGuid(); InternalId = Guid.NewGuid();
@ -194,11 +196,27 @@ namespace AntDesign
}; };
SelectParent.SelectOptionItems.Add(newSelectOptionItem); SelectParent.SelectOptionItems.Add(newSelectOptionItem);
isAlreadySelected = IsAlreadySelected(newSelectOptionItem);
} }
SetClassMap(); SetClassMap();
await base.OnInitializedAsync(); await base.OnInitializedAsync();
if (isAlreadySelected)
{
await SelectParent.ProcessSelectedSelectOptions();
}
}
private bool IsAlreadySelected(SelectOptionItem<TItemValue, TItem> selectOption)
{
if (SelectParent.Mode == "default")
{
return selectOption.Value.Equals(SelectParent.Value) || selectOption.Value.Equals(SelectParent.LastValueBeforeReset);
}
else
{
return SelectParent.Values is null || SelectParent.Values.Contains(selectOption.Value);
}
} }
protected void SetClassMap() protected void SetClassMap()

View File

@ -0,0 +1,38 @@
using System.Collections.Generic;
namespace AntDesign
{
internal class DataSourceEqualityComparer<TItemValue, TItem> : IEqualityComparer<TItem>
{
private Select<TItemValue, TItem> SelectParent { get; }
public DataSourceEqualityComparer(Select<TItemValue, TItem> selectParent)
{
SelectParent = selectParent;
}
public bool Equals(TItem x, TItem y)
{
if (SelectParent._getLabel is null)
{
if (SelectParent._getValue is null)
{
return x.ToString().Equals(y.ToString());
}
return x.ToString().Equals(y.ToString())
&& SelectParent._getValue(x).Equals(SelectParent._getValue(y));
}
if (SelectParent._getValue is null)
{
return SelectParent._getLabel(x).Equals(SelectParent._getLabel(y));
}
return SelectParent._getLabel(x).Equals(SelectParent._getLabel(y))
&& SelectParent._getValue(x).Equals(SelectParent._getValue(y));
}
public int GetHashCode(TItem obj)
{
return obj.GetHashCode();
}
}
}

View File

@ -13,7 +13,7 @@ using Microsoft.JSInterop;
namespace AntDesign.Select.Internal namespace AntDesign.Select.Internal
{ {
public partial class SelectContent<TItemValue, TItem>: IDisposable public partial class SelectContent<TItemValue, TItem> : IDisposable
{ {
[CascadingParameter(Name = "ParentSelect")] internal SelectBase<TItemValue, TItem> ParentSelect { get; set; } [CascadingParameter(Name = "ParentSelect")] internal SelectBase<TItemValue, TItem> ParentSelect { get; set; }
[CascadingParameter(Name = "ParentLabelTemplate")] internal RenderFragment<TItem> ParentLabelTemplate { get; set; } [CascadingParameter(Name = "ParentLabelTemplate")] internal RenderFragment<TItem> ParentLabelTemplate { get; set; }
@ -57,7 +57,7 @@ namespace AntDesign.Select.Internal
[Parameter] public string SearchValue { get; set; } [Parameter] public string SearchValue { get; set; }
[Parameter] public ForwardRef RefBack { get; set; } = new ForwardRef(); [Parameter] public ForwardRef RefBack { get; set; } = new ForwardRef();
[Inject] protected IJSRuntime Js { get; set; } [Inject] protected IJSRuntime Js { get; set; }
[Inject] private DomEventService DomEventService { get; set; } [Inject] private IDomEventListener DomEventListener { get; set; }
protected ElementReference Ref protected ElementReference Ref
{ {
get { return _ref; } get { return _ref; }
@ -108,7 +108,7 @@ namespace AntDesign.Select.Internal
{ {
if (ParentSelect.EnableSearch) if (ParentSelect.EnableSearch)
{ {
DomEventService.AddEventListener("window", "beforeunload", Reloading, false); DomEventListener.AddShared<JsonElement>("window", "beforeunload", Reloading);
await Js.InvokeVoidAsync(JSInteropConstants.AddPreventKeys, ParentSelect._inputRef, new[] { "ArrowUp", "ArrowDown" }); await Js.InvokeVoidAsync(JSInteropConstants.AddPreventKeys, ParentSelect._inputRef, new[] { "ArrowUp", "ArrowDown" });
await Js.InvokeVoidAsync(JSInteropConstants.AddPreventEnterOnOverlayVisible, ParentSelect._inputRef, ParentSelect._dropDownRef); await Js.InvokeVoidAsync(JSInteropConstants.AddPreventEnterOnOverlayVisible, ParentSelect._inputRef, ParentSelect._dropDownRef);
} }
@ -135,11 +135,11 @@ namespace AntDesign.Select.Internal
_suffixElement = await Js.InvokeAsync<DomRect>(JSInteropConstants.GetBoundingClientRect, _suffixRef); _suffixElement = await Js.InvokeAsync<DomRect>(JSInteropConstants.GetBoundingClientRect, _suffixRef);
_suffixElement.Width += 7; _suffixElement.Width += 7;
} }
await DomEventService.AddResizeObserver(_overflow, OnOveralyResize); await DomEventListener.AddResizeObserver(_overflow, OnOveralyResize);
await CalculateResponsiveTags(); await CalculateResponsiveTags();
} }
DomEventService.AddEventListener(ParentSelect._inputRef, "focusout", OnBlurInternal, true); DomEventListener.AddExclusive<JsonElement>(ParentSelect._inputRef, "focusout", OnBlurInternal);
DomEventService.AddEventListener(ParentSelect._inputRef, "focus", OnFocusInternal, true); DomEventListener.AddExclusive<JsonElement>(ParentSelect._inputRef, "focus", OnFocusInternal);
} }
else if (_currentItemCount != ParentSelect.SelectedOptionItems.Count) else if (_currentItemCount != ParentSelect.SelectedOptionItems.Count)
{ {
@ -418,14 +418,12 @@ namespace AntDesign.Select.Internal
{ {
await Task.Delay(100); await Task.Delay(100);
if (ParentSelect.IsResponsive) if (ParentSelect.IsResponsive)
await DomEventService.DisposeResizeObserver(_overflow); await DomEventListener.DisposeResizeObserver(_overflow);
await Js.InvokeVoidAsync(JSInteropConstants.RemovePreventKeys, ParentSelect._inputRef); await Js.InvokeVoidAsync(JSInteropConstants.RemovePreventKeys, ParentSelect._inputRef);
await Js.InvokeVoidAsync(JSInteropConstants.RemovePreventEnterOnOverlayVisible, ParentSelect._inputRef); await Js.InvokeVoidAsync(JSInteropConstants.RemovePreventEnterOnOverlayVisible, ParentSelect._inputRef);
}); });
} }
DomEventService.RemoveEventListerner<JsonElement>(ParentSelect._inputRef, "focus", OnFocusInternal); DomEventListener.Dispose();
DomEventService.RemoveEventListerner<JsonElement>(ParentSelect._inputRef, "focusout", OnBlurInternal);
DomEventService.RemoveEventListerner<JsonElement>("window", "beforeunload", Reloading);
if (IsDisposed) return; if (IsDisposed) return;

View File

@ -120,7 +120,7 @@ namespace AntDesign
} }
[Inject] [Inject]
private DomEventService DomEventService { get; set; } private IDomEventListener DomEventListener { get; set; }
#region Parameters #region Parameters
@ -261,8 +261,6 @@ namespace AntDesign
(double, double) typedValue = DataConvertionExtensions.Convert<TValue, (double, double)>(CurrentValue); (double, double) typedValue = DataConvertionExtensions.Convert<TValue, (double, double)>(CurrentValue);
if (value != typedValue.Item1) if (value != typedValue.Item1)
CurrentValue = DataConvertionExtensions.Convert<(double, double), TValue>((_leftValue, RightValue)); CurrentValue = DataConvertionExtensions.Convert<(double, double), TValue>((_leftValue, RightValue));
if (_toolTipLeft != null)
_toolTipLeft.ChildElementMoved();
} }
} }
} }
@ -303,8 +301,6 @@ namespace AntDesign
//CurrentValue = DoubleToGeneric(_rightValue); //CurrentValue = DoubleToGeneric(_rightValue);
CurrentValue = DataConvertionExtensions.Convert<double, TValue>(_rightValue); CurrentValue = DataConvertionExtensions.Convert<double, TValue>(_rightValue);
} }
if (_toolTipRight != null)
_toolTipRight.ChildElementMoved();
} }
} }
} }
@ -366,7 +362,7 @@ namespace AntDesign
/// Set Tooltip display position. Ref Tooltip /// Set Tooltip display position. Ref Tooltip
/// </summary> /// </summary>
[Parameter] [Parameter]
public PlacementType TooltipPlacement { get; set; } public Placement TooltipPlacement { get; set; }
/// <summary> /// <summary>
/// If true, Tooltip will show always, or it will not show anyway, even if dragging or hovering. /// If true, Tooltip will show always, or it will not show anyway, even if dragging or hovering.
@ -404,11 +400,6 @@ namespace AntDesign
#endregion Parameters #endregion Parameters
protected override void OnInitialized()
{
base.OnInitialized();
}
public async override Task SetParametersAsync(ParameterView parameters) public async override Task SetParametersAsync(ParameterView parameters)
{ {
await base.SetParametersAsync(parameters); await base.SetParametersAsync(parameters);
@ -460,9 +451,9 @@ namespace AntDesign
if (!dict.ContainsKey(nameof(TooltipPlacement))) if (!dict.ContainsKey(nameof(TooltipPlacement)))
{ {
if (Vertical) if (Vertical)
TooltipPlacement = PlacementType.Right; TooltipPlacement = Placement.Right;
else else
TooltipPlacement = PlacementType.Top; TooltipPlacement = Placement.Top;
} }
} }
@ -487,8 +478,8 @@ namespace AntDesign
{ {
if (firstRender) if (firstRender)
{ {
DomEventService.AddEventListener("window", "mousemove", OnMouseMove, false); DomEventListener.AddShared<JsonElement>("window", "mousemove", OnMouseMove);
DomEventService.AddEventListener("window", "mouseup", OnMouseUp, false); DomEventListener.AddShared<JsonElement>("window", "mouseup", OnMouseUp);
} }
base.OnAfterRender(firstRender); base.OnAfterRender(firstRender);
@ -496,9 +487,7 @@ namespace AntDesign
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
DomEventService.RemoveEventListerner<JsonElement>("window", "mousemove", OnMouseMove); DomEventListener.Dispose();
DomEventService.RemoveEventListerner<JsonElement>("window", "mouseup", OnMouseUp);
base.Dispose(disposing); base.Dispose(disposing);
} }
@ -509,11 +498,9 @@ namespace AntDesign
if (_toolTipRight != null && HasTooltip) if (_toolTipRight != null && HasTooltip)
{ {
_rightHandle = _toolTipRight.Ref; _rightHandle = _toolTipRight.Ref;
await _toolTipRight.ChildElementMoved();
if (_toolTipLeft != null) if (_toolTipLeft != null)
{ {
_leftHandle = _toolTipLeft.Ref; _leftHandle = _toolTipLeft.Ref;
await _toolTipLeft.ChildElementMoved();
} }
} }
} }
@ -668,7 +655,7 @@ namespace AntDesign
_right = false; _right = false;
if (_mouseDown) if (_mouseDown)
RightValue = _initialLeftValue; RightValue = _initialLeftValue;
LeftValue = rightV; LeftValue = rightV;
await FocusAsync(_leftHandle); await FocusAsync(_leftHandle);
} }
else else
@ -715,7 +702,7 @@ namespace AntDesign
_right = true; _right = true;
if (_mouseDown) if (_mouseDown)
LeftValue = _initialRightValue; LeftValue = _initialRightValue;
RightValue = leftV; RightValue = leftV;
await FocusAsync(_rightHandle); await FocusAsync(_rightHandle);
} }
else else

View File

@ -106,5 +106,11 @@ namespace AntDesign
_isLoading = Spinning; _isLoading = Spinning;
InvokeAsync(StateHasChanged); InvokeAsync(StateHasChanged);
} }
protected override void Dispose(bool disposing)
{
_delayTimer?.Dispose();
base.Dispose(disposing);
}
} }
} }

View File

@ -1,72 +1,89 @@
@namespace AntDesign @namespace AntDesign
@using AntDesign.TableModels
@inherits ColumnBase @inherits ColumnBase
@if (IsInitialize) @if (IsInitialize)
{ {
return; return;
} }
else if (IsPlaceholder) else if (IsPlaceholder)
{ {
<td style="padding: 0px; border: 0px; height: 0px;"></td> <td style="padding: 0px; border: 0px; height: 0px;"></td>
} }
else if (IsMeasure) else if (IsMeasure)
{ {
<td style="padding: 0px; border: 0px; height: 0px;"><div style="height: 0px; overflow: hidden;">&nbsp;</div></td> <td style="padding: 0px; border: 0px; height: 0px;"><div style="height: 0px; overflow: hidden;">&nbsp;</div></td>
} }
else if (IsColGroup) else if (IsColGroup)
{ {
@if (AppendExpandColumn) @if (AppendExpandColumn)
{ {
<col class="ant-table-expand-icon-col"> <col class="ant-table-expand-icon-col">
} }
if (Width != null) if (Width != null)
{ {
<col style="width: @((CssSizeLength)Width); min-width: @((CssSizeLength)Width);"> <col style="width: @((CssSizeLength)Width); min-width: @((CssSizeLength)Width);">
} }
else else
{ {
<col /> <col />
} }
} }
else if (IsHeader && HeaderColSpan != 0) else if (IsHeader && HeaderColSpan != 0)
{ {
@if (AppendExpandColumn) @if (AppendExpandColumn)
{ {
<th class="ant-table-cell ant-table-row-expand-icon-cell"></th> <th class="ant-table-cell ant-table-row-expand-icon-cell"></th>
} }
<th class="@ClassMapper.Class" style="@FixedStyle @HeaderStyle" colspan="@HeaderColSpan"> <th class="@ClassMapper.Class" style="@FixedStyle @HeaderStyle" colspan="@HeaderColSpan">
@if (TitleTemplate != null)@TitleTemplate else @Title @if (TitleTemplate != null)
</th> {
@TitleTemplate
}
else
{
@Title
}
</th>
} }
else if (!IsHeader && RowSpan != 0 && ColSpan != 0) else if (!IsHeader && RowSpan != 0 && ColSpan != 0)
{ {
@if (AppendExpandColumn) @if (AppendExpandColumn)
{ {
<td class="ant-table-cell ant-table-row-expand-icon-cell"> <td class="ant-table-cell ant-table-row-expand-icon-cell">
@if (Table.RowExpandable(RowData) && (!Table.TreeMode || !RowData.HasChildren)) @if (Table.RowExpandable(RowData) && (!Table.TreeMode || !RowData.HasChildren))
{ {
<button type="button" @onclick="ToggleTreeNode" <button type="button" @onclick="ToggleTreeNode"
class="ant-table-row-expand-icon @(RowData.Expanded?"ant-table-row-expand-icon-expanded":"ant-table-row-expand-icon-collapsed")" class="ant-table-row-expand-icon @(RowData.Expanded?"ant-table-row-expand-icon-expanded":"ant-table-row-expand-icon-collapsed")"
aria-label="@(RowData.Expanded?Table.Locale.Collapse:Table.Locale.Expand)"></button> aria-label="@(RowData.Expanded?Table.Locale.Collapse:Table.Locale.Expand)"></button>
} }
</td> </td>
} }
<td class="@ClassMapper.Class" style="@FixedStyle @Style" rowspan="@RowSpan" colspan="@ColSpan" data-label="@Context.HeaderColumns[ColIndex].Title"> <td class="@ClassMapper.Class" style="@FixedStyle @Style" rowspan="@RowSpan" colspan="@ColSpan" data-label="@Context.HeaderColumns[ColIndex].Title">
@if (ColIndex == Table.TreeExpandIconColumnIndex && Table.TreeMode) @if (ColIndex == Table.TreeExpandIconColumnIndex && Table.TreeMode)
{ {
<span class="ant-table-row-indent indent-level-@RowData.Level" style="padding-left: @((CssSizeLength)(RowData.Level * Table.IndentSize));"></span> <span class="ant-table-row-indent indent-level-@RowData.Level" style="padding-left: @((CssSizeLength)(RowData.Level * Table.IndentSize));"></span>
@if (RowData.HasChildren) @if (RowData.HasChildren)
{ {
<button type="button" @onclick="ToggleTreeNode" class="ant-table-row-expand-icon @(RowData?.Expanded==true?"ant-table-row-expand-icon-expanded":"ant-table-row-expand-icon-collapsed")" aria-label="@(RowData?.Expanded==true?Table.Locale.Collapse:Table.Locale.Expand)"></button> <button type="button" @onclick="ToggleTreeNode" class="ant-table-row-expand-icon @(RowData?.Expanded==true?"ant-table-row-expand-icon-expanded":"ant-table-row-expand-icon-collapsed")" aria-label="@(RowData?.Expanded==true?Table.Locale.Collapse:Table.Locale.Expand)"></button>
} }
else else
{ {
<button type="button" class="ant-table-row-expand-icon ant-table-row-expand-icon-spaced" aria-label="@Table.Locale.Expand"></button> <button type="button" class="ant-table-row-expand-icon ant-table-row-expand-icon-spaced" aria-label="@Table.Locale.Expand"></button>
} }
} }
@ChildContent
</td> @if (CellRender != null)
{
var cellData = new CellData(RowData);
@CellRender(cellData)
}
else
{
@ChildContent
}
</td>
} }

View File

@ -8,386 +8,404 @@
@if (IsInitialize) @if (IsInitialize)
{ {
return; return;
} }
else if (Hidden) else if (Hidden)
{ {
return; return;
} }
else if (IsPlaceholder) else if (IsPlaceholder)
{ {
<td style="padding: 0px; border: 0px; height: 0px;"></td> <td style="padding: 0px; border: 0px; height: 0px;"></td>
} }
else if (IsMeasure) else if (IsMeasure)
{ {
<td style="padding: 0px; border: 0px; height: 0px;"><div style="height: 0px; overflow: hidden;">&nbsp;</div></td> <td style="padding: 0px; border: 0px; height: 0px;"><div style="height: 0px; overflow: hidden;">&nbsp;</div></td>
} }
else if (IsColGroup) else if (IsColGroup)
{ {
@if (AppendExpandColumn) @if (AppendExpandColumn)
{ {
<col class="ant-table-expand-icon-col"> <col class="ant-table-expand-icon-col">
} }
if (Width != null) if (Width != null)
{ {
<col style="width: @((CssSizeLength)Width); min-width: @((CssSizeLength)Width);"> <col style="width: @((CssSizeLength)Width); min-width: @((CssSizeLength)Width);">
} }
else else
{ {
<col /> <col />
} }
} }
else if (IsHeader && HeaderColSpan != 0) else if (IsHeader && HeaderColSpan != 0)
{ {
@if (AppendExpandColumn) @if (AppendExpandColumn)
{ {
<th class="ant-table-cell ant-table-row-expand-icon-cell"></th> <th class="ant-table-cell ant-table-row-expand-icon-cell"></th>
} }
var headerCellAttributes = OnHeaderCell?.Invoke(); var headerCellAttributes = OnHeaderCell?.Invoke();
<CascadingValue Name="IsHeader" Value="false" IsFixed> <CascadingValue Name="IsHeader" Value="false" IsFixed>
<th class="@ClassMapper.Class" style="@FixedStyle @HeaderStyle" colspan="@(HeaderColSpan > 1 ? HeaderColSpan : false)" title="@(Ellipsis ? Title : false)" @attributes="headerCellAttributes"> <th class="@ClassMapper.Class" style="@FixedStyle @HeaderStyle" colspan="@(HeaderColSpan > 1 ? HeaderColSpan : false)" title="@(Ellipsis ? HeaderTitle : false)" @attributes="headerCellAttributes">
@if (Sortable || (_filterable && _filters?.Any() == true)) @if (Sortable || (_filterable && _filters?.Any() == true))
{ {
@FilterToolTipSorter @FilterToolTipSorter
} }
else if (TitleTemplate != null) else if (TitleTemplate != null)
{ {
@TitleTemplate @TitleTemplate
} }
else else
{ {
@Title @HeaderTitle
} }
</th> </th>
</CascadingValue> </CascadingValue>
} }
else if (IsBody && RowSpan != 0 && ColSpan != 0) else if (IsBody && RowSpan != 0 && ColSpan != 0)
{ {
var fieldText = !string.IsNullOrWhiteSpace(Format) ? Formatter<TData>.Format(Field, Format) : Field?.ToString(); var fieldText = !string.IsNullOrWhiteSpace(Format) ? Formatter<TData>.Format(Field, Format) : Field?.ToString();
@if (AppendExpandColumn) @if (AppendExpandColumn)
{ {
<td class="ant-table-cell ant-table-row-expand-icon-cell"> <td class="ant-table-cell ant-table-row-expand-icon-cell">
@if (Table.RowExpandable(RowData) && (!Table.TreeMode || !RowData.HasChildren)) @if (Table.RowExpandable(RowData) && (!Table.TreeMode || !RowData.HasChildren))
{ {
<button type="button" @onclick="ToggleTreeNode" @onclick:stopPropagation <button type="button" @onclick="ToggleTreeNode" @onclick:stopPropagation
class="ant-table-row-expand-icon @(RowData.Expanded ? "ant-table-row-expand-icon-expanded" : "ant-table-row-expand-icon-collapsed")" class="ant-table-row-expand-icon @(RowData.Expanded ? "ant-table-row-expand-icon-expanded" : "ant-table-row-expand-icon-collapsed")"
aria-label="@(RowData.Expanded ? Table.Locale.Collapse : Table.Locale.Expand)"></button> aria-label="@(RowData.Expanded ? Table.Locale.Collapse : Table.Locale.Expand)"></button>
} }
</td> </td>
} }
var cellData = new CellData(RowData);
var cellAttributes = OnCell?.Invoke(cellData);
<CascadingValue Name="IsBody" Value="false" IsFixed>
<td class="@ClassMapper.Class" style="@FixedStyle @Style" rowspan="@RowSpan" colspan="@ColSpan" title="@(Ellipsis ? fieldText : false)" @attributes="cellAttributes">
@if (ColIndex == Table.TreeExpandIconColumnIndex && Table.TreeMode)
{
<span class="ant-table-row-indent indent-level-@RowData.Level" style="padding-left: @((CssSizeLength)(RowData.Level * Table.IndentSize));"></span>
@if (RowData.HasChildren)
{
<button type="button" @onclick="ToggleTreeNode" @onclick:stopPropagation class="ant-table-row-expand-icon @(RowData?.Expanded == true ? "ant-table-row-expand-icon-expanded" : "ant-table-row-expand-icon-collapsed")" aria-label="@(RowData?.Expanded == true ? Table.Locale.Collapse : Table.Locale.Expand)"></button>
}
else
{
<button type="button" class="ant-table-row-expand-icon ant-table-row-expand-icon-spaced" aria-label="@Table.Locale.Expand"></button>
}
}
var cellAttributes = OnCell?.Invoke(RowData); @if (CellRender != null)
<CascadingValue Name="IsBody" Value="false" IsFixed> {
<td class="@ClassMapper.Class" style="@FixedStyle @Style" rowspan="@RowSpan" colspan="@ColSpan" title="@(Ellipsis ? fieldText : false)" @attributes="cellAttributes"> @CellRender(cellData)
@if (ColIndex == Table.TreeExpandIconColumnIndex && Table.TreeMode) }
{ else if (ChildContent != null)
<span class="ant-table-row-indent indent-level-@RowData.Level" style="padding-left: @((CssSizeLength)(RowData.Level * Table.IndentSize));"></span> {
@if (RowData.HasChildren) @ChildContent
{ }
<button type="button" @onclick="ToggleTreeNode" @onclick:stopPropagation class="ant-table-row-expand-icon @(RowData?.Expanded == true ? "ant-table-row-expand-icon-expanded" : "ant-table-row-expand-icon-collapsed")" aria-label="@(RowData?.Expanded == true ? Table.Locale.Collapse : Table.Locale.Expand)"></button> else
} {
else if (!string.IsNullOrWhiteSpace(Format))
{ {
<button type="button" class="ant-table-row-expand-icon ant-table-row-expand-icon-spaced" aria-label="@Table.Locale.Expand"></button> @(Formatter<TData>.Format(Field, Format))
} }
} else
{
@if (CellRender != null) @Field
{ }
@CellRender(Field) }
} </td>
else if (ChildContent != null) </CascadingValue>
{
@ChildContent
}
else
{
if (!string.IsNullOrWhiteSpace(Format))
{
@(Formatter<TData>.Format(Field, Format))
}
else
{
@Field
}
}
</td>
</CascadingValue>
} }
@code @code
{ {
string HeaderTitle => Title ?? DisplayName ?? FieldName;
RenderFragment SortHeader => RenderFragment SortHeader =>
@<Template> @<Template>
<span> <span>
@if (TitleTemplate != null) @TitleTemplate else @Title @if (TitleTemplate != null)
</span> {
@{ @TitleTemplate
bool hasDescendingSorter = SortDirection.Descending.IsIn(SortDirections); }
bool hasAscendingSorter = SortDirection.Ascending.IsIn(SortDirections); else
} {
<span class="ant-table-column-sorter@(hasDescendingSorter && hasAscendingSorter ? " ant-table-column-sorter-full" : "")"> @HeaderTitle
<span class="ant-table-column-sorter-inner"> }
@if (hasAscendingSorter) </span>
{ @{
<Icon Type="caret-up" Class=@($"ant-table-column-sorter-up{(_sortDirection == SortDirection.Ascending ? " active" : "")}") /> bool hasDescendingSorter = SortDirection.Descending.IsIn(SortDirections);
} bool hasAscendingSorter = SortDirection.Ascending.IsIn(SortDirections);
@if (hasDescendingSorter) }
{ <span class="ant-table-column-sorter@(hasDescendingSorter && hasAscendingSorter ? " ant-table-column-sorter-full" : "")">
<Icon Type="caret-down" Class=@($"ant-table-column-sorter-down{(_sortDirection == SortDirection.Descending ? " active" : "")}") /> <span class="ant-table-column-sorter-inner">
} @if (hasAscendingSorter)
</span> {
</span> <Icon Type="caret-up" Class=@($"ant-table-column-sorter-up{(_sortDirection == SortDirection.Ascending ? " active" : "")}") />
</Template>; }
@if (hasDescendingSorter)
{
<Icon Type="caret-down" Class=@($"ant-table-column-sorter-down{(_sortDirection == SortDirection.Descending ? " active" : "")}") />
}
</span>
</span>
</Template>
;
RenderFragment ToolTipSorter => RenderFragment ToolTipSorter =>
@<Template> @<Template>
@if (ShowSorterTooltip) @if (ShowSorterTooltip)
{ {
<Tooltip Title="@SorterTooltip"> <Tooltip Title="@SorterTooltip">
<Unbound> <Unbound>
<div class="ant-table-column-sorters" @ref="context.Current" @onclick="HandleSort"> <div class="ant-table-column-sorters" @ref="context.Current" @onclick="HandleSort">
@SortHeader @SortHeader
</div> </div>
</Unbound> </Unbound>
</Tooltip> </Tooltip>
} }
else else
{ {
<div class="ant-table-column-sorters" @onclick="HandleSort"> <div class="ant-table-column-sorters" @onclick="HandleSort">
@SortHeader @SortHeader
</div> </div>
} }
</Template>; </Template>
;
RenderFragment FilterToolTipSorter => RenderFragment FilterToolTipSorter =>
@<Template> @<Template>
@if (_filterable && _filters?.Any() == true) @if (_filterable && _filters?.Any() == true)
{ {
<div class="ant-table-filter-column"> <div class="ant-table-filter-column">
@if (Sortable) @if (Sortable)
{ {
@ToolTipSorter @ToolTipSorter
} }
else if (TitleTemplate != null) else if (TitleTemplate != null)
{ {
@TitleTemplate @TitleTemplate
} }
else else
{ {
@Title @HeaderTitle
} }
<Dropdown Trigger="new[] { TriggerType.Click }" Visible="_filterOpened" Placement="PlacementType.BottomRight" OnMaskClick="() => { if (_filterOpened) FilterConfirm(); }"> <Dropdown Trigger="new[] { Trigger.Click }" Visible="_filterOpened" Placement="Placement.BottomRight" OnMaskClick="() => { if (_filterOpened) FilterConfirm(); }">
<Unbound> <Unbound>
<span @ref="context.Current" role="button" tabindex="-1" class="ant-dropdown-trigger ant-table-filter-trigger@((_filterOpened ? " ant-dropdown-open" : "") + (_hasFilterSelected ? " active" : ""))" <span @ref="context.Current" role="button" tabindex="-1" class="ant-dropdown-trigger ant-table-filter-trigger@((_filterOpened ? " ant-dropdown-open" : "") + (_hasFilterSelected ? " active" : ""))"
@onclick="() => { if (_filterOpened) FilterConfirm(); else _filterOpened = true; }"> @onclick="() => { if (_filterOpened) FilterConfirm(); else _filterOpened = true; }">
<Icon Type="filter" Theme="fill" /> <Icon Type="filter" Theme="fill" />
</span> </span>
</Unbound> </Unbound>
<Overlay> <Overlay>
<div class="ant-table-filter-dropdown"> <div class="ant-table-filter-dropdown">
@if (_filters?.Any() == true && _columnFilterType == TableFilterType.List) @if (_filters?.Any() == true && _columnFilterType == TableFilterType.List)
{ {
<Menu AutoCloseDropdown="false" SelectedKeys="_selectedFilterValues"> <Menu AutoCloseDropdown="false" SelectedKeys="_selectedFilterValues">
@foreach (var filter in _filters) @foreach (var filter in _filters)
{ {
<MenuItem Key="@filter.Value?.ToString()" OnClick="() => FilterSelected(filter)"> <MenuItem Key="@filter.Value?.ToString()" OnClick="() => FilterSelected(filter)">
@if (FilterMultiple) @if (FilterMultiple)
{ {
<Checkbox Value="filter.Selected" ValueChanged="value => FilterSelected(filter)">@filter.Text</Checkbox> <Checkbox Value="filter.Selected" ValueChanged="value => FilterSelected(filter)">@filter.Text</Checkbox>
} }
else else
{ {
<Radio TValue="bool" Checked="filter.Selected" CheckedChanged="value => FilterSelected(filter)">@filter.Text</Radio> <Radio TValue="bool" Checked="filter.Selected" CheckedChanged="value => FilterSelected(filter)">@filter.Text</Radio>
} }
</MenuItem> </MenuItem>
} }
</Menu> </Menu>
} }
else else
{ {
<div id="@("popup-container-for-" + Id)" style="position:relative!important"></div> <div id="@("popup-container-for-" + Id)" style="position:relative!important"></div>
int filterCount = _filters.Count(); int filterCount = _filters.Count();
@for (int index = 0; index < filterCount; index++) @for (int index = 0; index < filterCount; index++)
{ {
var filter = _filters.ElementAt(index); var filter = _filters.ElementAt(index);
var noInput = filter.FilterCompareOperator == TableFilterCompareOperator.IsNull || filter.FilterCompareOperator == TableFilterCompareOperator.IsNotNull; var noInput = filter.FilterCompareOperator == TableFilterCompareOperator.IsNull || filter.FilterCompareOperator == TableFilterCompareOperator.IsNotNull;
<div @key="filter" style="padding:10px;min-width:150px;@(index > 0 ? "border-top:1px solid #f0f0f0" : "")"> <div @key="filter" style="padding:10px;min-width:150px;@(index > 0 ? "border-top:1px solid #f0f0f0" : "")">
@if (index > 0) @if (index > 0)
{ {
<div> <div>
<Space Style="margin-bottom:10px"> <Space Style="margin-bottom:10px">
<SpaceItem> <SpaceItem>
<Select Value="filter.FilterCondition" TItemValue="TableFilterCondition" TItem="TableFilterCondition" ValueChanged="value => SetFilterCondition(filter,value)" PopupContainerSelector="@("#popup-container-for-" + Id)"> <Select Value="filter.FilterCondition" TItemValue="TableFilterCondition" TItem="TableFilterCondition" ValueChanged="value => SetFilterCondition(filter,value)" PopupContainerSelector="@("#popup-container-for-" + Id)" BoundaryAdjustMode="@TriggerBoundaryAdjustMode.None">
<SelectOptions> <SelectOptions>
<SelectOption TItem="TableFilterCondition" TItemValue="TableFilterCondition" Value="@TableFilterCondition.And" Label="@Table?.Locale.FilterOptions.And"></SelectOption> <SelectOption TItem="TableFilterCondition" TItemValue="TableFilterCondition" Value="@TableFilterCondition.And" Label="@Table?.Locale.FilterOptions.And"></SelectOption>
<SelectOption TItem="TableFilterCondition" TItemValue="TableFilterCondition" Value="@TableFilterCondition.Or" Label="@Table?.Locale.FilterOptions.Or"></SelectOption> <SelectOption TItem="TableFilterCondition" TItemValue="TableFilterCondition" Value="@TableFilterCondition.Or" Label="@Table?.Locale.FilterOptions.Or"></SelectOption>
</SelectOptions> </SelectOptions>
</Select> </Select>
</SpaceItem> </SpaceItem>
</Space> </Space>
</div> </div>
} }
<Space Style="width:100%;"> <Space Style="width:100%;">
<SpaceItem Style="flex:auto"> <SpaceItem Style="flex:auto">
<Select Value="filter.FilterCompareOperator" TItemValue="TableFilterCompareOperator" TItem="TableFilterCompareOperator" Style="width: 100%; overflow: visible" ValueChanged="value => SetFilterCompareOperator(filter,value)" PopupContainerSelector="@("#popup-container-for-" + Id)" DropdownMatchSelectWidth="false"> <Select Value="filter.FilterCompareOperator" TItemValue="TableFilterCompareOperator" TItem="TableFilterCompareOperator" Style="width: 100%; overflow: visible" ValueChanged="value => SetFilterCompareOperator(filter,value)" PopupContainerSelector="@("#popup-container-for-" + Id)" BoundaryAdjustMode="@TriggerBoundaryAdjustMode.None" DropdownMatchSelectWidth="false">
<SelectOptions> <SelectOptions>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.Equals" Label="@Table?.Locale.FilterOptions.Equals"></SelectOption> <SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.Equals" Label="@Table?.Locale.FilterOptions.Equals"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.NotEquals" Label="@Table?.Locale.FilterOptions.NotEquals"></SelectOption> <SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.NotEquals" Label="@Table?.Locale.FilterOptions.NotEquals"></SelectOption>
@if (_columnDataType == typeof(string)) @if (_columnDataType == typeof(string))
{ {
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.Contains" Label="@Table?.Locale.FilterOptions.Contains"></SelectOption> <SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.Contains" Label="@Table?.Locale.FilterOptions.Contains"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.StartsWith" Label="@Table?.Locale.FilterOptions.StartsWith"></SelectOption> <SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.StartsWith" Label="@Table?.Locale.FilterOptions.StartsWith"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.EndsWith" Label="@Table?.Locale.FilterOptions.EndsWith"></SelectOption> <SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.EndsWith" Label="@Table?.Locale.FilterOptions.EndsWith"></SelectOption>
} }
else if (_columnDataType.IsEnum) else if (_columnDataType.IsEnum)
{ {
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.Contains" Label="@Table?.Locale.FilterOptions.Contains"></SelectOption> <SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.Contains" Label="@Table?.Locale.FilterOptions.Contains"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.NotContains" Label="@Table?.Locale.FilterOptions.NotContains"></SelectOption> <SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.NotContains" Label="@Table?.Locale.FilterOptions.NotContains"></SelectOption>
} }
else if (_columnDataType == typeof(DateTime)) else if (_columnDataType == typeof(DateTime))
{ {
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.GreaterThan" Label="@Table?.Locale.FilterOptions.GreaterThan"></SelectOption> <SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.GreaterThan" Label="@Table?.Locale.FilterOptions.GreaterThan"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.LessThan" Label="@Table?.Locale.FilterOptions.LessThan"></SelectOption> <SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.LessThan" Label="@Table?.Locale.FilterOptions.LessThan"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.GreaterThanOrEquals" Label="@Table?.Locale.FilterOptions.GreaterThanOrEquals"></SelectOption> <SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.GreaterThanOrEquals" Label="@Table?.Locale.FilterOptions.GreaterThanOrEquals"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.LessThanOrEquals" Label="@Table?.Locale.FilterOptions.LessThanOrEquals"></SelectOption> <SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.LessThanOrEquals" Label="@Table?.Locale.FilterOptions.LessThanOrEquals"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.TheSameDateWith" Label="@Table?.Locale.FilterOptions.TheSameDateWith"></SelectOption> <SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.TheSameDateWith" Label="@Table?.Locale.FilterOptions.TheSameDateWith"></SelectOption>
} }
else if (_columnDataType == typeof(Guid)) { } else if (_columnDataType == typeof(Guid)) { }
else else
{ {
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.GreaterThan" Label="@Table?.Locale.FilterOptions.GreaterThan"></SelectOption> <SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.GreaterThan" Label="@Table?.Locale.FilterOptions.GreaterThan"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.LessThan" Label="@Table?.Locale.FilterOptions.LessThan"></SelectOption> <SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.LessThan" Label="@Table?.Locale.FilterOptions.LessThan"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.GreaterThanOrEquals" Label="@Table?.Locale.FilterOptions.GreaterThanOrEquals"></SelectOption> <SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.GreaterThanOrEquals" Label="@Table?.Locale.FilterOptions.GreaterThanOrEquals"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.LessThanOrEquals" Label="@Table?.Locale.FilterOptions.LessThanOrEquals"></SelectOption> <SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.LessThanOrEquals" Label="@Table?.Locale.FilterOptions.LessThanOrEquals"></SelectOption>
} }
@if (THelper.IsTypeNullable<TData>() || _columnDataType == typeof(string)) @if (THelper.IsTypeNullable<TData>() || _columnDataType == typeof(string))
{ {
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.IsNull" Label="@Table?.Locale.FilterOptions.IsNull"></SelectOption> <SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.IsNull" Label="@Table?.Locale.FilterOptions.IsNull"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.IsNotNull" Label="@Table?.Locale.FilterOptions.IsNotNull"></SelectOption> <SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.IsNotNull" Label="@Table?.Locale.FilterOptions.IsNotNull"></SelectOption>
} }
</SelectOptions> </SelectOptions>
</Select> </Select>
</SpaceItem> </SpaceItem>
@if (!noInput) @if (!noInput)
{ {
<SpaceItem> <SpaceItem>
@FilterInput(filter) @FilterInput(filter)
</SpaceItem> </SpaceItem>
} }
@if (filterCount > 1) @if (filterCount > 1)
{ {
<SpaceItem> <SpaceItem>
<Button Size="small" Icon="close" Type="primary" OnClick="()=> RemoveFilter(filter)"> <Button Size="small" Icon="close" Type="primary" OnClick="()=> RemoveFilter(filter)">
</Button> </Button>
</SpaceItem> </SpaceItem>
} }
</Space> </Space>
</div> </div>
} }
} }
<div class="ant-table-filter-dropdown-btns"> <div class="ant-table-filter-dropdown-btns">
<Button Size="small" Type="link" OnClick="ResetFilters"> <Button Size="small" Type="link" OnClick="ResetFilters">
@Table?.Locale.FilterReset @Table?.Locale.FilterReset
</Button> </Button>
@if (_columnFilterType == TableFilterType.FieldType) @if (_columnFilterType == TableFilterType.FieldType)
{ {
<Button Size="small" Icon="plus" Type="primary" OnClick="AddFilter"> <Button Size="small" Icon="plus" Type="primary" OnClick="AddFilter">
</Button> </Button>
} }
<Button Size="small" Type="primary" OnClick="() => FilterConfirm()"> <Button Size="small" Type="primary" OnClick="() => FilterConfirm()">
@Table?.Locale.FilterConfirm @Table?.Locale.FilterConfirm
</Button> </Button>
</div> </div>
</div> </div>
</Overlay> </Overlay>
</Dropdown> </Dropdown>
</div> </div>
} }
else else
{ {
@ToolTipSorter @ToolTipSorter
} }
</Template>; </Template>
;
RenderFragment<TableFilter> FilterInput => filter => RenderFragment<TableFilter> FilterInput => filter =>
@<Template> @<Template>
@if (_columnDataType == typeof(DateTime)) @if (_columnDataType == typeof(DateTime))
{ {
if (filter.FilterCompareOperator != TableFilterCompareOperator.TheSameDateWith) if (filter.FilterCompareOperator != TableFilterCompareOperator.TheSameDateWith)
{ {
<DatePicker Value="(DateTime?)filter.Value" ShowTime="@true" <DatePicker Value="(DateTime?)filter.Value" ShowTime="@true"
TValue="DateTime?" ValueChanged="value => SetFilterValue(filter, value?.AddMilliseconds(-value.Value.Millisecond))" PopupContainerSelector="@("#popup-container-for-" + Id)"></DatePicker> TValue="DateTime?" ValueChanged="value => SetFilterValue(filter, value?.AddMilliseconds(-value.Value.Millisecond))"
} PopupContainerSelector="@("#popup-container-for-" + Id)"
else BoundaryAdjustMode="@TriggerBoundaryAdjustMode.None"/>
{ }
<DatePicker Value="(DateTime?)filter.Value" TValue="DateTime?" ValueChanged="value => SetFilterValue(filter, value)" PopupContainerSelector="@("#popup-container-for-" + Id)"></DatePicker> else
} {
} <DatePicker Value="(DateTime?)filter.Value" TValue="DateTime?" ValueChanged="value => SetFilterValue(filter, value)"
else if (_columnDataType.IsEnum) PopupContainerSelector="@("#popup-container-for-" + Id)"
{ BoundaryAdjustMode="@TriggerBoundaryAdjustMode.None"/>
<EnumSelect TEnum="TData" Mode="multiple" Values="EnumHelper<TData>.Split(filter.Value)" PopupContainerSelector="@("#popup-container-for-" + Id)" }
Style="width:180px;" ValuesChanged="value => SetFilterValue(filter, EnumHelper<TData>.Combine(value))"> }
</EnumSelect> else if (_columnDataType.IsEnum)
} {
else if (_columnDataType == typeof(byte)) <EnumSelect TEnum="TData" Mode="multiple" Values="EnumHelper<TData>.Split(filter.Value)"
{ PopupContainerSelector="@("#popup-container-for-" + Id)"
<InputNumber Value="(byte?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="byte?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber> Style="width:180px;"
} ValuesChanged="value => SetFilterValue(filter, EnumHelper<TData>.Combine(value))"
else if (_columnDataType == typeof(decimal)) BoundaryAdjustMode="@TriggerBoundaryAdjustMode.None"/>
{ }
<InputNumber Value="(decimal?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="decimal?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber> else if (_columnDataType == typeof(byte))
} {
else if (_columnDataType == typeof(double)) <InputNumber Value="(byte?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="byte?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
{ }
<InputNumber Value="(double?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="double?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber> else if (_columnDataType == typeof(decimal))
} {
else if (_columnDataType == typeof(short)) <InputNumber Value="(decimal?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="decimal?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
{ }
<InputNumber Value="(short?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="short?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber> else if (_columnDataType == typeof(double))
} {
else if (_columnDataType == typeof(int)) <InputNumber Value="(double?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="double?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
{ }
<InputNumber Value="(int?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="int?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber> else if (_columnDataType == typeof(short))
} {
else if (_columnDataType == typeof(long)) <InputNumber Value="(short?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="short?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
{ }
<InputNumber Value="(long?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="long?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber> else if (_columnDataType == typeof(int))
} {
else if (_columnDataType == typeof(sbyte)) <InputNumber Value="(int?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="int?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
{ }
<InputNumber Value="(sbyte?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="sbyte?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber> else if (_columnDataType == typeof(long))
} {
else if (_columnDataType == typeof(float)) <InputNumber Value="(long?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="long?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
{ }
<InputNumber Value="(float?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="float?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber> else if (_columnDataType == typeof(sbyte))
} {
else if (_columnDataType == typeof(ushort)) <InputNumber Value="(sbyte?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="sbyte?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
{ }
<InputNumber Value="(ushort?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="ushort?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber> else if (_columnDataType == typeof(float))
} {
else if (_columnDataType == typeof(uint)) <InputNumber Value="(float?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="float?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
{ }
<InputNumber Value="(uint?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="uint?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber> else if (_columnDataType == typeof(ushort))
} {
else if (_columnDataType == typeof(ulong)) <InputNumber Value="(ushort?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="ushort?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
{ }
<InputNumber Value="(ulong?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="ulong?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber> else if (_columnDataType == typeof(uint))
} {
else if (_columnDataType == typeof(Guid)) <InputNumber Value="(uint?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="uint?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
{ }
<Input Value="(Guid?)filter.Value" TValue="Guid?" ValueChanged="value => SetFilterValue(filter, value)" /> else if (_columnDataType == typeof(ulong))
} {
else <InputNumber Value="(ulong?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="ulong?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
{ }
<Input TValue="TData" Value="(TData)filter.Value" ValueChanged="value => SetFilterValue(filter, value)" /> else if (_columnDataType == typeof(Guid))
} {
</Template>; <Input Value="(Guid?)filter.Value" TValue="Guid?" ValueChanged="value => SetFilterValue(filter, value)" />
}
else
{
<Input TValue="TData" Value="(TData)filter.Value" ValueChanged="value => SetFilterValue(filter, value)" />
}
</Template>
;
} }

View File

@ -22,8 +22,6 @@ namespace AntDesign
[Parameter] [Parameter]
public Expression<Func<TData>> FieldExpression { get; set; } public Expression<Func<TData>> FieldExpression { get; set; }
[Parameter]
public RenderFragment<TData> CellRender { get; set; }
[Parameter] [Parameter]
public TData Field public TData Field
@ -70,7 +68,7 @@ namespace AntDesign
public SortDirection DefaultSortOrder { get; set; } public SortDirection DefaultSortOrder { get; set; }
[Parameter] [Parameter]
public Func<RowData, Dictionary<string, object>> OnCell { get; set; } public Func<CellData, Dictionary<string, object>> OnCell { get; set; }
[Parameter] [Parameter]
public Func<Dictionary<string, object>> OnHeaderCell { get; set; } public Func<Dictionary<string, object>> OnHeaderCell { get; set; }

View File

@ -70,6 +70,9 @@ namespace AntDesign
[Parameter] [Parameter]
public bool Hidden { get; set; } public bool Hidden { get; set; }
[Parameter]
public virtual RenderFragment<CellData> CellRender { get; set; }
public int ColIndex { get; set; } public int ColIndex { get; set; }
protected bool AppendExpandColumn => Table.HasExpandTemplate && ColIndex == (Table.TreeMode ? Table.TreeExpandIconColumnIndex : Table.ExpandIconColumnIndex); protected bool AppendExpandColumn => Table.HasExpandTemplate && ColIndex == (Table.TreeMode ? Table.TreeExpandIconColumnIndex : Table.ExpandIconColumnIndex);

View File

@ -1,48 +1,62 @@
@namespace AntDesign @namespace AntDesign
@using AntDesign.TableModels
@inherits ColumnBase @inherits ColumnBase
@if (IsInitialize) @if (IsInitialize)
{ {
return; return;
} }
else if (IsPlaceholder) else if (IsPlaceholder)
{ {
<td style="padding: 0px; border: 0px; height: 0px;"></td> <td style="padding: 0px; border: 0px; height: 0px;"></td>
} }
else if (IsMeasure) else if (IsMeasure)
{ {
<td style="padding: 0px; border: 0px; height: 0px;"><div style="height: 0px; overflow: hidden;">&nbsp;</div></td> <td style="padding: 0px; border: 0px; height: 0px;"><div style="height: 0px; overflow: hidden;">&nbsp;</div></td>
} }
else if (IsColGroup) else if (IsColGroup)
{ {
if (Width != null) if (Width != null)
{ {
<col class="ant-table-selection-col" style="width: @((CssSizeLength)Width); min-width: @((CssSizeLength)Width);"> <col class="ant-table-selection-col" style="width: @((CssSizeLength)Width); min-width: @((CssSizeLength)Width);">
} }
else else
{ {
<col class="ant-table-selection-col"> <col class="ant-table-selection-col">
} }
} }
else if (IsHeader && HeaderColSpan != 0) else if (IsHeader && HeaderColSpan != 0)
{ {
<th class="@ClassMapper.Class ant-table-selection-column" style="@FixedStyle @HeaderStyle" @key="@Key" colspan="@HeaderColSpan"> <th class="@ClassMapper.Class ant-table-selection-column" style="@FixedStyle @HeaderStyle" @key="@Key" colspan="@HeaderColSpan">
@if (Type == "checkbox") @if (Type == "checkbox")
{ {
<Checkbox Checked="_checked" CheckedChange="HandleCheckedChange" Indeterminate="Indeterminate" /> <Checkbox Checked="_checked" CheckedChange="HandleCheckedChange" Indeterminate="Indeterminate" />
} }
</th> </th>
} }
else if (!IsHeader && RowSpan != 0 && ColSpan != 0) else if (!IsHeader && RowSpan != 0 && ColSpan != 0)
{ {
<td class="@ClassMapper.Class ant-table-selection-column" style="@FixedStyle @Style" @key="@Key" rowspan="@RowSpan" colspan="@ColSpan"> <td class="@ClassMapper.Class ant-table-selection-column" style="@FixedStyle @Style" @key="@Key" rowspan="@RowSpan" colspan="@ColSpan">
@if (Type == "checkbox")
{ @if (CellRender != null)
<Checkbox Checked="_checked" Disabled="Disabled" CheckedChange="HandleCheckedChange" /> {
} var cellData = new CellData(RowData);
else if (Type == "radio") @CellRender(cellData)
{ }
<Radio Checked="_checked" Disabled="Disabled" CheckedChange="HandleCheckedChange" TValue="bool" /> else if (ChildContent != null)
} {
</td> @ChildContent
}
else
{
@if (Type == "checkbox")
{
<Checkbox Checked="_checked" Disabled="Disabled" CheckedChange="HandleCheckedChange" />
}
else if (Type == "radio")
{
<Radio Checked="_checked" Disabled="Disabled" CheckedChange="HandleCheckedChange" TValue="bool" />
}
}
</td>
} }

View File

@ -1,5 +1,15 @@
@namespace AntDesign @namespace AntDesign
@using AntDesign.TableModels
@inherits ColumnBase @inherits ColumnBase
<td class="@ClassMapper.Class" style="@FixedStyle @Style" colspan="@ColSpan">@ChildContent</td> <td class="@ClassMapper.Class" style="@FixedStyle @Style" colspan="@ColSpan">
@if (CellRender != null)
{
var cellData = new CellData(RowData);
@CellRender(cellData)
}
else
{
@ChildContent
}
</td>

View File

@ -27,7 +27,7 @@
<CascadingValue Name="IsInitialize" Value="true" IsFixed> <CascadingValue Name="IsInitialize" Value="true" IsFixed>
@ChildContent(_fieldModel) @ChildContent(_fieldModel)
</CascadingValue> </CascadingValue>
<div class="@ClassMapper.Class"> <div class="@ClassMapper.Class" @ref="_tableRef">
@if (TitleTemplate != null || Title != null) @if (TitleTemplate != null || Title != null)
{ {
<div class="ant-table-title"> <div class="ant-table-title">

View File

@ -136,7 +136,7 @@ namespace AntDesign
public bool Responsive { get; set; } = true; public bool Responsive { get; set; } = true;
[Inject] [Inject]
public DomEventService DomEventService { get; set; } private IDomEventListener DomEventListener { get; set; }
public ColumnContext ColumnContext { get; set; } public ColumnContext ColumnContext { get; set; }
@ -146,20 +146,20 @@ namespace AntDesign
private IList<SummaryRow> _summaryRows; private IList<SummaryRow> _summaryRows;
private bool _hasFirstLoad;
private bool _waitingReload; private bool _waitingReload;
private bool _waitingReloadAndInvokeChange; private bool _waitingReloadAndInvokeChange;
private bool _treeMode; private bool _treeMode;
private bool _hasFixLeft; private bool _hasFixLeft;
private bool _hasFixRight; private bool _hasFixRight;
private bool _pingRight;
private bool _pingLeft;
private int _treeExpandIconColumnIndex; private int _treeExpandIconColumnIndex;
private readonly ClassMapper _wrapperClassMapper = new ClassMapper(); private readonly ClassMapper _wrapperClassMapper = new ClassMapper();
private string TableLayoutStyle => TableLayout == null ? "" : $"table-layout: {TableLayout};"; private string TableLayoutStyle => TableLayout == null ? "" : $"table-layout: {TableLayout};";
private ElementReference _tableHeaderRef; private ElementReference _tableHeaderRef;
private ElementReference _tableBodyRef; private ElementReference _tableBodyRef;
private ElementReference _tableRef;
private bool ServerSide => _hasRemoteDataSourceAttribute ? RemoteDataSource : Total > _dataSourceCount; private bool ServerSide => _hasRemoteDataSourceAttribute ? RemoteDataSource : Total > _dataSourceCount;
@ -325,8 +325,8 @@ namespace AntDesign
.If($"{prefixCls}-fixed-column {prefixCls}-scroll-horizontal", () => ScrollX != null) .If($"{prefixCls}-fixed-column {prefixCls}-scroll-horizontal", () => ScrollX != null)
.If($"{prefixCls}-has-fix-left", () => _hasFixLeft) .If($"{prefixCls}-has-fix-left", () => _hasFixLeft)
.If($"{prefixCls}-has-fix-right", () => _hasFixRight) .If($"{prefixCls}-has-fix-right", () => _hasFixRight)
.If($"{prefixCls}-ping-left", () => _pingLeft) //.If($"{prefixCls}-ping-left", () => _pingLeft)
.If($"{prefixCls}-ping-right", () => _pingRight) //.If($"{prefixCls}-ping-right", () => _pingRight)
.If($"{prefixCls}-rtl", () => RTL) .If($"{prefixCls}-rtl", () => RTL)
; ;
@ -359,9 +359,21 @@ namespace AntDesign
FlushCache(); FlushCache();
} }
protected override void OnAfterRender(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
base.OnAfterRender(firstRender); await base.OnAfterRenderAsync(firstRender);
if (firstRender)
{
DomEventListener.AddShared<JsonElement>("window", "beforeunload", Reloading);
if (ScrollY != null || ScrollX != null)
{
await JsInvokeAsync(JSInteropConstants.BindTableScroll, _tableBodyRef, _tableRef, _tableHeaderRef, ScrollX != null, ScrollY != null);
}
_hasFirstLoad = true;
}
if (!firstRender) if (!firstRender)
{ {
@ -371,28 +383,6 @@ namespace AntDesign
_shouldRender = false; _shouldRender = false;
} }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (firstRender)
{
DomEventService.AddEventListener("window", "beforeunload", Reloading, false);
if (ScrollX != null)
{
await SetScrollPositionClassName();
DomEventService.AddEventListener("window", "resize", OnResize, false);
DomEventService.AddEventListener(_tableBodyRef, "scroll", OnScroll);
}
if (ScrollY != null && ScrollX != null)
{
await JsInvokeAsync(JSInteropConstants.BindTableHeaderAndBodyScroll, _tableBodyRef, _tableHeaderRef);
}
}
}
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
base.OnParametersSet(); base.OnParametersSet();
@ -402,12 +392,19 @@ namespace AntDesign
_waitingReloadAndInvokeChange = false; _waitingReloadAndInvokeChange = false;
_waitingReload = false; _waitingReload = false;
ReloadAndInvokeChange(); if (_hasFirstLoad)
{
ReloadAndInvokeChange();
}
} }
else if (_waitingReload) else if (_waitingReload)
{ {
_waitingReload = false; _waitingReload = false;
InternalReload();
if (_hasFirstLoad)
{
InternalReload();
}
} }
if (this.RenderMode == RenderMode.ParametersHashCodeChanged) if (this.RenderMode == RenderMode.ParametersHashCodeChanged)
@ -437,61 +434,9 @@ namespace AntDesign
StateHasChanged(); StateHasChanged();
} }
private async void OnResize(JsonElement _) => await SetScrollPositionClassName();
private async void OnScroll(JsonElement _) => await SetScrollPositionClassName();
private async Task SetScrollPositionClassName(bool clear = false)
{
if (_isReloading)
return;
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;
if ((scrollWidth == clientWidth && scrollWidth != 0) || clear)
{
_pingLeft = false;
_pingRight = false;
}
else if (scrollLeft == 0)
{
_pingLeft = false;
_pingRight = true;
}
// allow the gap between 1 px, it's magic ✨
else if (Math.Abs(scrollWidth - (scrollLeft + clientWidth)) < 1)
{
_pingRight = false;
_pingLeft = true;
}
else
{
_pingLeft = true;
_pingRight = true;
}
if (beforePingLeft != _pingLeft || beforePingRight != _pingRight)
{
_shouldRender = true;
}
if (!clear)
{
StateHasChanged();
}
}
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
DomEventService.RemoveEventListerner<JsonElement>("window", "resize", OnResize); DomEventListener.Dispose();
DomEventService.RemoveEventListerner<JsonElement>(_tableBodyRef, "scroll", OnScroll);
DomEventService.RemoveEventListerner<JsonElement>("window", "beforeunload", Reloading);
base.Dispose(disposing); base.Dispose(disposing);
} }
@ -499,12 +444,12 @@ namespace AntDesign
{ {
if (!_isReloading) if (!_isReloading)
{ {
if (ScrollY != null && ScrollX != null) if (ScrollY != null || ScrollX != null)
{ {
await JsInvokeAsync(JSInteropConstants.UnbindTableHeaderAndBodyScroll, _tableBodyRef); await JsInvokeAsync(JSInteropConstants.UnbindTableScroll, _tableBodyRef);
} }
} }
DomEventService.RemoveEventListerner<JsonElement>("window", "beforeunload", Reloading); DomEventListener.Dispose();
} }
bool ITable.RowExpandable(RowData rowData) bool ITable.RowExpandable(RowData rowData)

View File

@ -0,0 +1,20 @@
// 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.Text;
namespace AntDesign.TableModels
{
public class CellData
{
public RowData RowData { get; }
public CellData(RowData rowData)
{
RowData = rowData;
}
}
}

View File

@ -1,8 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements. using System;
// 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.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
@ -54,7 +50,7 @@ namespace AntDesign
internal List<TabPane> _panes = new List<TabPane>(); internal List<TabPane> _panes = new List<TabPane>();
[Inject] [Inject]
public DomEventService DomEventService { get; set; } private IDomEventListener DomEventListener { get; set; }
#region Parameters #region Parameters
@ -394,13 +390,13 @@ namespace AntDesign
if (IsHorizontal && !_wheelDisabled) if (IsHorizontal && !_wheelDisabled)
{ {
DomEventService.AddEventListener<string>(_scrollTabBar, "wheel", OnWheel, true, true); DomEventListener.AddExclusive<string>(_scrollTabBar, "wheel", OnWheel, true);
_wheelDisabled = true; _wheelDisabled = true;
} }
if (!IsHorizontal && _wheelDisabled) if (!IsHorizontal && _wheelDisabled)
{ {
DomEventService.RemoveEventListerner<string>(_scrollTabBar, "wheel", OnWheel); DomEventListener.RemoveExclusive(_scrollTabBar, "wheel");
_wheelDisabled = false; _wheelDisabled = false;
} }
@ -568,5 +564,11 @@ namespace AntDesign
} }
#endregion DRAG & DROP #endregion DRAG & DROP
protected override void Dispose(bool disposing)
{
DomEventListener.DisposeExclusive();
base.Dispose(disposing);
}
} }
} }

View File

@ -23,7 +23,7 @@ namespace AntDesign
public Tooltip() public Tooltip()
{ {
PrefixCls = "ant-tooltip"; PrefixCls = "ant-tooltip";
Placement = PlacementType.Top; Placement = Placement.Top;
} }
internal override string GetOverlayEnterClass() internal override string GetOverlayEnterClass()
@ -38,7 +38,7 @@ namespace AntDesign
internal override async Task Show(int? overlayLeft = null, int? overlayTop = null) internal override async Task Show(int? overlayLeft = null, int? overlayTop = null)
{ {
if (Trigger.Contains(TriggerType.Hover)) if (Trigger.Contains(AntDesign.Trigger.Hover))
{ {
await Task.Delay((int)(MouseEnterDelay * 1000)); await Task.Delay((int)(MouseEnterDelay * 1000));
} }
@ -48,7 +48,7 @@ namespace AntDesign
internal override async Task Hide(bool force = false) internal override async Task Hide(bool force = false)
{ {
if (Trigger.Contains(TriggerType.Hover)) if (Trigger.Contains(AntDesign.Trigger.Hover))
{ {
await Task.Delay((int)(MouseLeaveDelay * 1000)); await Task.Delay((int)(MouseLeaveDelay * 1000));
} }
@ -56,7 +56,7 @@ namespace AntDesign
await base.Hide(force); await base.Hide(force);
} }
internal async Task ChildElementMoved() =>await GetOverlayComponent().UpdatePosition(); //internal async Task ChildElementMoved() =>await GetOverlayComponent().UpdatePosition();
} }
} }

View File

@ -11,7 +11,7 @@
<OverlayTrigger @ref="@_dropDown" <OverlayTrigger @ref="@_dropDown"
Visible="Open" Visible="Open"
Disabled="Disabled" Disabled="Disabled"
Trigger="new[] { TriggerType.Click }" Trigger="new[] { Trigger.Click }"
HiddenMode HiddenMode
OnMouseEnter="@(() => { OnMouseEnter?.Invoke(); })" OnMouseEnter="@(() => { OnMouseEnter?.Invoke(); })"
OnMouseLeave="@(() => { OnMouseLeave?.Invoke(); })" OnMouseLeave="@(() => { OnMouseLeave?.Invoke(); })"

View File

@ -2,7 +2,8 @@
<div style="padding: 100px; height: 1000px; background: #eee; position: relative " id="area"> <div style="padding: 100px; height: 1000px; background: #eee; position: relative " id="area">
<AutoComplete PopupContainerSelector="#area" <AutoComplete PopupContainerSelector="#area"
@bind-Value="@value" @bind-Value="@value"
Options="@options" /> Options="@options"
BoundaryAdjustMode="@TriggerBoundaryAdjustMode.InScroll"/>
</div> </div>
</div> </div>

View File

@ -18,6 +18,7 @@ When there is a need for autocomplete functionality.
| Property | Description | Type | Default | | Property | Description | Type | Default |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| Backfill | backfill selected item the input when using keyboard | bool | false | | Backfill | backfill selected item the input when using keyboard | bool | false |
| BoundaryAdjustMode | `Dropdown` adjustment strategy (when for example browser resize is happening) | TriggerBoundaryAdjustMode | TriggerBoundaryAdjustMode.InView |
| Options | Data source for autocomplete | AutocompleteDataSource | - | | Options | Data source for autocomplete | AutocompleteDataSource | - |
| Disabled | Set disabled | bool | - | | Disabled | Set disabled | bool | - |
| Placeholder | Placeholder text | string | - | | Placeholder | Placeholder text | string | - |

View File

@ -19,6 +19,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/qtJm4yt45/AutoComplete.svg
| 参数 | 说明 | 类型 | 默认值 | | 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| Backfill | 使用键盘选择选项的时候把选中项回填到输入框中 | boolean | false | | Backfill | 使用键盘选择选项的时候把选中项回填到输入框中 | boolean | false |
| BoundaryAdjustMode | `Dropdown` adjustment strategy (when for example browser resize is happening) | TriggerBoundaryAdjustMode | TriggerBoundaryAdjustMode.InView |
| Options | 自动完成的数据源 | AutocompleteDataSource | - | | Options | 自动完成的数据源 | AutocompleteDataSource | - |
| Disabled | 是否禁用 | bool | - | | Disabled | 是否禁用 | bool | - |
| Placeholder | 占位符文本 | string | - | | Placeholder | 占位符文本 | string | - |

View File

@ -1,7 +1,7 @@
<AvatarGroup> <AvatarGroup>
<Avatar Src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" /> <Avatar Src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />
<Avatar Style="background-color: #f56a00">K</Avatar> <Avatar Style="background-color: #f56a00">K</Avatar>
<Tooltip Title=@("Ant User") Placement="PlacementType.Top"> <Tooltip Title=@("Ant User") Placement="Placement.Top">
<Unbound> <Unbound>
<Avatar Style="background-color: #87d068;" Icon="user" RefBack="@context"/> <Avatar Style="background-color: #87d068;" Icon="user" RefBack="@context"/>
</Unbound> </Unbound>
@ -12,7 +12,7 @@
<AvatarGroup MaxCount="2" MaxStyle="color: #f56a00; background-color:#fde3cf;"> <AvatarGroup MaxCount="2" MaxStyle="color: #f56a00; background-color:#fde3cf;">
<Avatar Src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" /> <Avatar Src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />
<Avatar Style="background-color: #f56a00">K</Avatar> <Avatar Style="background-color: #f56a00">K</Avatar>
<Tooltip Title=@("Ant User") Placement="PlacementType.Top" > <Tooltip Title=@("Ant User") Placement="Placement.Top" >
<Unbound> <Unbound>
<Avatar Style="background-color: #87d068;" Icon="user" RefBack="@context"/> <Avatar Style="background-color: #87d068;" Icon="user" RefBack="@context"/>
</Unbound> </Unbound>

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