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.
---
### 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
@ -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)
- 🐞 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)
[#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)
- 🆕 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
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)
- 🐞 修复 时间列内置筛选器在筛选时将忽略毫秒。[#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)
[#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)
- 🆕 新增 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
$ cd ant-design-blazor
$ npm install
$ dotnet build ./site/AntDesign.Docs.Build/AntDesign.Docs.Build.csproj
$ npm start
```

View File

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

View File

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

View File

@ -26,7 +26,7 @@ namespace AntDesign
private bool _linksChanged = false;
[Inject]
private DomEventService DomEventService { get; set; }
private IDomEventListener DomEventListener { get; set; }
#region Parameters
@ -129,7 +129,7 @@ namespace AntDesign
{
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)
{
DomEventListener.Dispose();
base.Dispose(disposing);
DomEventService.RemoveEventListerner<JsonElement>("window", "scroll", OnScroll);
}
}
}

View File

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

View File

@ -163,6 +163,13 @@ namespace AntDesign
/// </summary>
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]
public bool ShowPanel { get; set; } = false;

View File

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

View File

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

View File

@ -10,7 +10,7 @@ namespace AntDesign
private bool _visible = false;
[Inject]
public DomEventService DomEventService { get; set; }
private IDomEventListener DomEventListener { get; set; }
[Parameter]
public string Icon { get; set; } = "vertical-align-top";
@ -55,11 +55,11 @@ namespace AntDesign
{
if (string.IsNullOrWhiteSpace(TargetSelector))
{
DomEventService.AddEventListener("window", "scroll", OnScroll);
DomEventListener.AddExclusive<JsonElement>("window", "scroll", OnScroll);
}
else
{
DomEventService.AddEventListener(TargetSelector, "scroll", OnScroll);
DomEventListener.AddExclusive<JsonElement>(TargetSelector, "scroll", OnScroll);
}
await base.OnFirstAfterRenderAsync();
}
@ -88,15 +88,8 @@ namespace AntDesign
protected override void Dispose(bool disposing)
{
DomEventListener.DisposeExclusive();
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)
{
<Dropdown Placement="@PlacementType.BottomCenter">
<Dropdown Placement="@Placement.BottomCenter">
<Unbound>
<span @ref="context.Current" class="ant-breadcrumb-overlay-link">
<span class="ant-breadcrumb-link">

View File

@ -2,20 +2,31 @@
@inherits AntDomComponentBase
<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:stopPropagation="@OnClickStopPropagation"
@onmouseup="OnMouseUp"
ant-click-animating-without-extra-node="@(this._animating ? "true":"false")">
@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>
</CascadingValue>

View File

@ -49,7 +49,7 @@ namespace AntDesign
public bool Danger { get; set; }
/// <summary>
/// Whether the `Button` is disabled.
/// Whether the `Button` is disabled.
/// </summary>
[Parameter]
public bool Disabled { get; set; }
@ -73,21 +73,10 @@ namespace AntDesign
public string Icon { get; set; }
/// <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>
[Parameter]
public bool Loading
{
get => _loading;
set
{
if (_loading != value)
{
_loading = value;
UpdateIconDisplay(_loading);
}
}
}
public bool Loading { get; set; }
/// <summary>
/// Callback when `Button` is clicked
@ -114,19 +103,23 @@ namespace AntDesign
public string Size { get; set; } = AntSizeLDSType.Default;
/// <summary>
/// Type of the button.
/// Type of the button.
/// </summary>
[Parameter]
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>();
protected string IconStyle { get; set; }
private bool _animating = false;
private string _btnWave = "--antd-wave-shadow-color: rgb(255, 120, 117);";
private bool _loading = false;
protected void SetClassMap()
{
@ -152,12 +145,6 @@ namespace AntDesign
base.OnInitialized();
SetClassMap();
SetButtonColorStyle();
UpdateIconDisplay(_loading);
}
private void UpdateIconDisplay(bool loading)
{
IconStyle = $"display:{(loading ? "none" : "inline-block")}";
}
private async Task HandleOnClick(MouseEventArgs args)

View File

@ -74,7 +74,7 @@ namespace AntDesign
#endregion Parameters
[Inject] public DomEventService DomEventService { get; set; }
[Inject] public IDomEventListener DomEventListener { get; set; }
public void Next() => GoTo(_slicks.IndexOf(ActiveSlick) + 1);
@ -130,7 +130,7 @@ namespace AntDesign
if (firstRender)
{
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)
{
DomEventService.RemoveEventListerner<JsonElement>("window", "resize", Resize);
DomEventListener.Dispose();
_slicks.Clear();
base.Dispose(disposing);
}
}

View File

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

View File

@ -13,6 +13,11 @@ namespace AntDesign
{
[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 string DefaultValue { get; set; }

View File

@ -11,10 +11,11 @@
@ref="Ref"
@onmouseenter="OnOverlayMouseEnter"
@onmouseleave="OnOverlayMouseLeave"
@onmouseup="OnOverlayMouseUp"
@onmouseup="OnOverlayMouseUp"
@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))
{
<CascadingValue Value="OverlayChildPrefixCls" Name="PrefixCls">

View File

@ -1,7 +1,7 @@
using System;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using AntDesign.Core.JsInterop.Modules.Components;
using AntDesign.JsInterop;
using Microsoft.AspNetCore.Components;
@ -9,14 +9,21 @@ namespace AntDesign.Internal
{
public sealed partial class Overlay : AntDomComponentBase
{
[CascadingParameter(Name = "Trigger")]
public OverlayTrigger Trigger { get; set; }
[CascadingParameter(Name = "ArrowPointAtCenter")]
public bool ArrowPointAtCenter { get; set; }
/// <summary>
/// Used in nested overlays (for example menu -> submenu) when
/// trigger is another overlay.
/// </summary>
[CascadingParameter(Name = "ParentTrigger")]
public OverlayTrigger ParentTrigger { get; set; }
[Parameter]
public string OverlayChildPrefixCls { get; set; } = "";
/// <summary>
/// Component that will trigger the overlay to show.
/// </summary>
[CascadingParameter(Name = "Trigger")]
public OverlayTrigger Trigger { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
@ -31,13 +38,14 @@ namespace AntDesign.Internal
public EventCallback OnOverlayMouseUp { get; set; }
[Parameter]
public Action OnShow { get; set; }
public EventCallback OnShow { get; set; }
[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]
public int HideMillisecondsDelay { get; set; } = 100;
@ -69,7 +77,7 @@ namespace AntDesign.Internal
public bool HiddenMode { get; set; } = false;
[Inject]
private DomEventService DomEventService { get; set; }
private IDomEventListener DomEventListener { get; set; }
private bool _hasAddOverlayToBody = false;
private bool _isPreventHide = false;
@ -80,6 +88,7 @@ namespace AntDesign.Internal
private bool _isWaitForOverlayFirstRender = false;
private bool _preVisible = false;
private bool _isOverlayDuringShowing = false;
private bool _isOverlayShow = false;
private bool _isOverlayHiding = false;
private bool _lastDisabledState = false;
@ -87,14 +96,20 @@ namespace AntDesign.Internal
private int? _overlayLeft = 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 const int ARROW_SIZE = 13;
private const int HORIZONTAL_ARROW_SHIFT = 13;
private const int VERTICAL_ARROW_SHIFT = 5;
private bool _shouldRender = true;
private int _overlayClientWidth = 0;
protected override bool ShouldRender()
{
if (_shouldRender)
return base.ShouldRender();
_shouldRender = true;
return false;
}
protected override void OnInitialized()
{
@ -121,7 +136,7 @@ namespace AntDesign.Internal
{
if (firstRender)
{
DomEventService.AddEventListener("window", "beforeunload", Reloading, false);
DomEventListener.AddShared<JsonElement>("window", "beforeunload", Reloading);
}
if (_lastDisabledState != Trigger.Disabled)
@ -143,7 +158,7 @@ namespace AntDesign.Internal
if (_isWaitForOverlayFirstRender && _isOverlayFirstRender)
{
_isOverlayFirstRender = false;
await Show(_overlayLeft, _overlayTop);
//await Show(_overlayLeft, _overlayTop);
_isWaitForOverlayFirstRender = false;
_overlayLeft = null;
@ -159,10 +174,10 @@ namespace AntDesign.Internal
_ = InvokeAsync(async () =>
{
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);
}
@ -172,24 +187,14 @@ namespace AntDesign.Internal
{
return;
}
await Task.Yield();
HtmlElement trigger;
if (Trigger.ChildContent is not null)
_isOverlayDuringShowing = true;
if (_isOverlayFirstRender)
{
trigger = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetFirstChildDomInfo, Trigger.Ref);
// fix bug in submenu: Overlay show when OvelayTrigger is not rendered complete.
// I try to render Overlay when OvelayTriggers OnAfterRender is called, but is still get negative absoluteLeft
// This may be a bad solution, but for now I can only do it this way.
while (trigger.AbsoluteLeft <= 0 && trigger.ClientWidth <= 0)
{
await Task.Delay(50);
trigger = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetFirstChildDomInfo, Trigger.Ref);
}
}
else //(Trigger.Unbound != null)
{
trigger = await JsInvokeAsync<HtmlElement>(JSInteropConstants.GetDomInfo, Trigger.Ref);
Trigger.SetShouldRender(false);
await Task.Yield();
}
else
_overlayLeft = overlayLeft;
_overlayTop = overlayTop;
@ -198,66 +203,51 @@ namespace AntDesign.Internal
{
_isWaitForOverlayFirstRender = true;
StateHasChanged();
return;
await InvokeAsync(StateHasChanged);
}
_isOverlayShow = true;
_isOverlayHiding = false;
await UpdateParentOverlayState(true);
await AddOverlayToBody();
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()}";
}
await AddOverlayToBody(overlayLeft, overlayTop);
_isOverlayShow = true;
_isOverlayDuringShowing = false;
_isOverlayHiding = false;
_overlayCls = Trigger.GetOverlayEnterClass();
await Trigger.OnVisibleChange.InvokeAsync(true);
StateHasChanged();
await InvokeAsync(StateHasChanged);
OnShow?.Invoke();
if (OnShow.HasDelegate)
OnShow.InvokeAsync(null);
}
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)
{
return;
}
await Task.Delay(HideMillisecondsDelay);
if (!force && !IsContainTrigger(TriggerType.Click) && (_isPreventHide || _mouseInOverlay || _isChildOverlayShow))
{
return;
}
_isOverlayFirstRender = true;
_isWaitForOverlayFirstRender = false;
_isOverlayHiding = true;
_overlayCls = Trigger.GetOverlayLeaveClass();
await Trigger.OnOverlayHiding.InvokeAsync(true);
await UpdateParentOverlayState(false);
StateHasChanged();
@ -271,7 +261,8 @@ namespace AntDesign.Internal
StateHasChanged();
OnHide?.Invoke();
if (OnHide.HasDelegate)
OnHide.InvokeAsync(null);
}
internal void PreventHide(bool prevent)
@ -308,275 +299,84 @@ namespace AntDesign.Internal
/// Indicates that a page is being refreshed
/// </summary>
private bool _isReloading;
private OverlayPosition _position;
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)
{
await JsInvokeAsync(JSInteropConstants.AddElementTo, Ref, Trigger.PopupContainerSelector);
bool triggerIsWrappedInDiv = Trigger.Unbound is null;
_recurenceGuard++;
_hasAddOverlayToBody = true;
}
}
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)
//In ServerSide it may happen that trigger element reference has not yet been retrieved.
if (!(await WaitFor(() => Trigger.Ref.Id is not null)))
{
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.");
}
}
else if (Trigger.Placement.IsIn(PlacementType.LeftBottom, PlacementType.RightBottom))
{
top = triggerTop - overlay.ClientHeight + triggerHeight;
if (ArrowPointAtCenter)
_position = await JsInvokeAsync<OverlayPosition>(JSInteropConstants.OverlayComponentHelper.AddOverlayToContainer,
Ref.Id, Ref, Trigger.Ref, Trigger.Placement, Trigger.PopupContainerSelector,
Trigger.BoundaryAdjustMode, triggerIsWrappedInDiv, Trigger.PrefixCls,
VerticalOffset, HorizontalOffset, ArrowPointAtCenter, overlayTop, overlayLeft);
if (_position is null && _recurenceGuard <= 10) //up to 10 attempts
{
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 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)
else
{
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
{
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 (placementDirection.IsIn(PlacementDirection.Top, PlacementDirection.Left))
if (!check())
{
return 0;
}
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
for (int i = 0; i < probings; i++)
{
"top" => containerElement.ScrollHeight,
_ => containerElement.ScrollWidth
};
await Task.Delay(waitTimeInMilisecondsPerProbing);
if (check())
{
return true;
}
}
return false;
}
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
};
return true;
}
private string GetTransformOrigin()
{
return $"transform-origin: {Trigger.Placement.TranformOrigin}";
return $"transform-origin: {Trigger.GetPlacementType().TranformOrigin}";
}
private bool IsContainTrigger(TriggerType triggerType)
{
foreach (TriggerType trigger in Trigger.Trigger)
foreach (TriggerType trigger in Trigger.GetTriggerType())
{
if (trigger == triggerType)
{
@ -630,44 +430,25 @@ namespace AntDesign.Internal
if (_hasAddOverlayToBody)
return "visibility: hidden;";
return "display: inline-flex; visibility: hidden;";
}
internal async Task UpdatePosition(int? overlayLeft = null, int? overlayTop = null)
{
HtmlElement trigger;
if (Trigger.ChildContent != null)
bool triggerIsWrappedInDiv = Trigger.Unbound is 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)

View File

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

View File

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

View File

@ -1,32 +1,72 @@
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 static readonly PlacementType TopLeft = new PlacementType("topLeft", "down", "33% 100%", 0);
public static readonly PlacementType TopCenter = new PlacementType("topCenter", "down", "50% 100%", 1);
public static readonly PlacementType Top = new PlacementType("top", "down", "50% 100%", 1);
public static readonly PlacementType TopRight = new PlacementType("topRight", "down", "66% 100%", 2);
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, Placement.TopCenter);
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, Placement.TopRight);
public static readonly PlacementType Left = new PlacementType("left", "up", "100% 50%%", 3);
public static readonly PlacementType LeftTop = new PlacementType("leftTop", "down", "100% 33%", 4);
public static readonly PlacementType LeftBottom = new PlacementType("leftBottom", "up", "100% 66%", 5);
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, Placement.LeftTop);
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 RightTop = new PlacementType("rightTop", "down", "0 33%", 7);
public static readonly PlacementType RightBottom = new PlacementType("rightBottom", "up", "0 66%", 8);
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, Placement.RightTop);
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 BottomCenter = new PlacementType("bottomCenter", "up", "50% 0", 10);
public static readonly PlacementType Bottom = new PlacementType("bottom", "up", "50% 0", 10);
public static readonly PlacementType BottomRight = new PlacementType("bottomRight", "up", "66% 0", 11);
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, Placement.BottomCenter);
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, 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 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;
TranformOrigin = transformOrigin;
Placement = placement;
}
internal PlacementType GetReverseType()

View File

@ -9,12 +9,18 @@
None,
/// <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>
InView,
/// <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>
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 static readonly TriggerType Click = new TriggerType(nameof(Click), 0);
public static readonly TriggerType Hover = new TriggerType(nameof(Hover), 1);
public static readonly TriggerType Focus = new TriggerType(nameof(Focus), 2);
public static readonly TriggerType ContextMenu = new TriggerType(nameof(ContextMenu), 3);
public static readonly TriggerType Click = new TriggerType(nameof(Click), 0, Trigger.Click);
public static readonly TriggerType Hover = new TriggerType(nameof(Hover), 1, Trigger.Hover);
public static readonly TriggerType Focus = new TriggerType(nameof(Focus), 2, Trigger.Focus);
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 Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.JSInterop;
namespace Microsoft.Extensions.DependencyInjection
{
@ -12,6 +13,12 @@ namespace Microsoft.Extensions.DependencyInjection
public static IServiceCollection AddAntDesign(this IServiceCollection services)
{
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>(),
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 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;
using Microsoft.JSInterop;
namespace AntDesign.JsInterop
{
public class DomEventService
{
private ConcurrentDictionary<string, List<DomEventSubscription>> _domEventListeners = new ConcurrentDictionary<string, List<DomEventSubscription>>();
private DomEventSubscriptionStore _domEventSubscriptionStore = new();
private readonly IJSRuntime _jsRuntime;
private bool? _isResizeObserverSupported = null;
public DomEventService(IJSRuntime 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)))
{
_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);
return new DomEventListener(_jsRuntime, _domEventSubscriptionStore);
}
}
}

View File

@ -1,16 +1,47 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
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 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;
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 HasFocus => DomInfoHelper.HasFocus;
public static string GetInnerText => DomInfoHelper.GetInnerText;
public static string GetMaxZIndex => DomInfoHelper.GetMaxZIndex;
#endregion
#region styleManipulation
@ -87,12 +89,12 @@ namespace AntDesign
#region overlay
public static string AddPreventEnterOnOverlayVisible => OverlayComponentHelper.AddPreventEnterOnOverlayVisible;
public static string RemovePreventEnterOnOverlayVisible => OverlayComponentHelper.RemovePreventEnterOnOverlayVisible;
public static string GetMaxZIndex => OverlayComponentHelper.GetMaxZIndex;
//public static string AddOverlayToContainer => OverlayComponentHelper.AddOverlayToContainer;
#endregion
#region table
public static string BindTableHeaderAndBodyScroll => TableComponentHelper.BindTableHeaderAndBodyScroll;
public static string UnbindTableHeaderAndBodyScroll => TableComponentHelper.UnbindTableHeaderAndBodyScroll;
public static string BindTableScroll => TableComponentHelper.BindTableScroll;
public static string UnbindTableScroll => TableComponentHelper.UnbindTableScroll;
#endregion
public static string DisposeObj => $"{FUNC_PREFIX}state.disposeObj";
@ -109,6 +111,7 @@ namespace AntDesign
public static string GetScroll => $"{FUNC_PREFIX}getScroll";
public static string HasFocus => $"{FUNC_PREFIX}hasFocus";
public static string GetInnerText => $"{FUNC_PREFIX}getInnerText";
public static string GetMaxZIndex => $"{FUNC_PREFIX}getMaxZIndex";
}
public static class EventHelper
@ -184,6 +187,7 @@ namespace AntDesign
{
private const string FUNC_PREFIX = JSInteropConstants.FUNC_PREFIX + "inputHelper.";
public static string RegisterResizeTextArea => $"{FUNC_PREFIX}registerResizeTextArea";
public static string GetTextAreaInfo => $"{FUNC_PREFIX}getTextAreaInfo";
public static string DisposeResizeTextArea => $"{FUNC_PREFIX}disposeResizeTextArea";
public static string SetSelectionStart => $"{FUNC_PREFIX}setSelectionStart";
}
@ -210,14 +214,16 @@ namespace AntDesign
private const string FUNC_PREFIX = JSInteropConstants.FUNC_PREFIX + "overlayHelper.";
public static string AddPreventEnterOnOverlayVisible => $"{FUNC_PREFIX}addPreventEnterOnOverlayVisible";
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
{
private const string FUNC_PREFIX = JSInteropConstants.FUNC_PREFIX + "tableHelper.";
public static string BindTableHeaderAndBodyScroll => $"{FUNC_PREFIX}bindTableHeaderAndBodyScroll";
public static string UnbindTableHeaderAndBodyScroll => $"{FUNC_PREFIX}unbindTableHeaderAndBodyScroll";
public static string BindTableScroll => $"{FUNC_PREFIX}bindTableScroll";
public static string UnbindTableScroll => $"{FUNC_PREFIX}unbindTableScroll";
}
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'
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'
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)
}
@ -48,17 +54,14 @@ export class resizeObserver {
}
static dispose(key: string): void {
console.log("dispose", key);
this.disconnect(key)
this.resizeObservers.delete(key)
}
private static observerCallBack(entries, observer, invoker) {
console.log("observerCallBack start", entries)
if (invoker) {
const mappedEntries = new Array<ResizeObserverEntry>()
entries.forEach(entry => {
console.log("observerCallBack entry", entry)
entries.forEach(entry => {
if (entry) {
const mEntry = new ResizeObserverEntry()
if (entry.borderBoxSize) {

View File

@ -1,13 +1,16 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json.Serialization;
namespace AntDesign.JsInterop
{
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';
export class inputHelper {
private static getTextAreaInfo(element) {
static getTextAreaInfo(element) {
var result = {};
var dom = domInfoHelper.get(element);
if (!dom) return null;
@ -32,7 +32,7 @@ export class inputHelper {
return result;
}
static registerResizeTextArea(element, minRows, maxRows, objReference) {
static registerResizeTextArea(element: HTMLTextAreaElement, minRows: number, maxRows: number, objReference) {
if (!objReference) {
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"]);
state.objReferenceDict[element.id] = null;
state.eventCallbackRegistry[element.id + "input"] = null;
}
static resizeTextArea(element, minRows, maxRows) {
var dims = this.getTextAreaInfo(element);
var rowHeight = dims["lineHeight"];
var offsetHeight = dims["paddingTop"] + dims["paddingBottom"] + dims["borderTop"] + dims["borderBottom"];
var oldHeight = parseFloat(element.style.height);
element.style.height = 'auto';
static resizeTextArea(element: HTMLTextAreaElement, minRows: number, maxRows: number) {
let dims = this.getTextAreaInfo(element);
let rowHeight = dims["lineHeight"];
let offsetHeight = dims["paddingTop"] + dims["paddingBottom"] + dims["borderTop"] + dims["borderBottom"];
let oldHeight = parseFloat(element.style.height);
//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);
rows = Math.max(minRows, rows);
element.rows = oldRows;
rows = Math.max(minRows, rows);
var newHeight = 0;
if (rows > maxRows) {
rows = maxRows;
@ -75,7 +78,7 @@ export class inputHelper {
}
if (oldHeight !== newHeight) {
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';
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) {
if (element && overlayElement) {
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 {
static bindTableHeaderAndBodyScroll(bodyRef, headerRef) {
bodyRef.bindScrollLeftToHeader = () => {
headerRef.scrollLeft = bodyRef.scrollLeft;
static bindTableScroll(bodyRef, tableRef, headerRef, scrollX, scrollY) {
bodyRef.bindScroll = () => {
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) {
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) {
//TODO: replace with event constructors https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent
//Not used
var evt = document.createEvent(eventType);
const evt = document.createEvent(eventType);
evt.initEvent(eventName);
return element.dispatchEvent(evt);
}
static addDomEventListener(element, eventName: string, preventDefault: boolean, invoker: any) {
let callback = args => {
const callback = args => {
const obj = {};
for (let k in args) {
if (k !== 'originalTarget') { //firefox occasionally raises Permission Denied when this property is being stringified
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 Window) return 'Window';
return v;
@ -29,14 +29,14 @@ export class eventHelper {
}
};
if (element == 'window') {
if (eventName == 'resize') {
if (element === 'window') {
if (eventName === 'resize') {
window.addEventListener(eventName, this.debounce(() => callback({ innerWidth: window.innerWidth, innerHeight: window.innerHeight }), 200, false));
} else {
window.addEventListener(eventName, callback);
}
} else {
let dom = domInfoHelper.get(element);
const dom = domInfoHelper.get(element);
if (dom) {
(dom as HTMLElement).addEventListener(eventName, callback);
}
@ -44,7 +44,7 @@ export class eventHelper {
}
static addDomEventListenerToFirstChild(element, eventName, preventDefault, invoker) {
var dom = domInfoHelper.get(element);
const dom = domInfoHelper.get(element);
if (dom && dom.firstElementChild) {
this.addDomEventListener(dom.firstElementChild, eventName, preventDefault, invoker);
@ -53,7 +53,7 @@ export class eventHelper {
static addPreventKeys(inputElement, keys: string[]) {
if (inputElement) {
let dom = domInfoHelper.get(inputElement);
const dom = domInfoHelper.get(inputElement);
keys = keys.map(function (x) { return x.toUpperCase(); })
state.eventCallbackRegistry[inputElement.id + "keydown"] = (e) => this.preventKeys(e, keys);
(dom as HTMLElement).addEventListener("keydown", state.eventCallbackRegistry[inputElement.id + "keydown"], false);
@ -69,7 +69,7 @@ export class eventHelper {
static removePreventKeys(inputElement) {
if (inputElement) {
let dom = domInfoHelper.get(inputElement);
const dom = domInfoHelper.get(inputElement);
if (dom) {
(dom as HTMLElement).removeEventListener("keydown", state.eventCallbackRegistry[inputElement.id + "keydown"]);
state.eventCallbackRegistry[inputElement.id + "keydown"] = null;

View File

@ -47,7 +47,7 @@ export class infoHelper {
}
static getElementAbsolutePos(element: any): domTypes.position {
let res: domTypes.position = {
const res: domTypes.position = {
x: 0,
y: 0
};
@ -68,7 +68,7 @@ export class infoHelper {
static getBoundingClientRect(element: any): domTypes.domRect {
const domElement = this.get(element);
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.
return {
width: rect.width,
@ -107,13 +107,43 @@ export class infoHelper {
}
static hasFocus(selector) {
let dom = this.get(selector);
const dom = this.get(selector);
return (document.activeElement === dom);
}
static getInnerText(element) {
let dom = this.get(element);
const dom = this.get(element);
if (dom) return dom.innerText;
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);
}
static addElementTo(addElement, elementSelector) {
static addElementTo(addElement, elementSelector): boolean {
let parent = domInfoHelper.get(elementSelector);
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) {
@ -95,6 +101,7 @@ export class manipulationHelper {
}
}
static blur(selector) {
let dom = domInfoHelper.get(selector);
if (dom) {
@ -102,20 +109,21 @@ export class manipulationHelper {
}
}
static scrollTo(selector: Element | string) {
let element = domInfoHelper.get(selector);
if (element && element instanceof HTMLElement) {
element.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' });
}
}
static scrollTo(selector: Element | string, parentElement?: HTMLElement) {
const element = domInfoHelper.get(selector);
if (parentElement && element && element instanceof HTMLElement) {
parentElement.scrollTop = element.offsetTop;
} else if (element && element instanceof HTMLElement) {
element.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' });
}
}
static slideTo(targetPageY) {
var timer = setInterval(function () {
var currentY = document.documentElement.scrollTop || document.body.scrollTop;
var distance = targetPageY > currentY ? targetPageY - currentY : currentY - targetPageY;
var speed = Math.ceil(distance / 10);
if (currentY == targetPageY) {
const timer = setInterval(function () {
const currentY = document.documentElement.scrollTop || document.body.scrollTop;
const distance = targetPageY > currentY ? targetPageY - currentY : currentY - targetPageY;
const speed = Math.ceil(distance / 10);
if (currentY === targetPageY) {
clearInterval(timer);
} else {
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"
Visible="Open"
IsButton="@true"
BoundaryAdjustMode="@BoundaryAdjustMode"
Disabled="IsDisabled(0)"
PopupContainerSelector="@PopupContainerSelector"
OnVisibleChange="OverlayVisibleChange"
OverlayEnterCls="ant-slide-up-enter ant-slide-up-enter-active ant-slide-up"
OverlayLeaveCls="ant-slide-up-leave ant-slide-up-leave-active ant-slide-up"
Trigger="new TriggerType[] { TriggerType.Click }">
Trigger="new[] { Trigger.Click }">
<Overlay>
<div class="@(PrefixCls)-panel-container" @onclick="@PickerClicked">
<div class="@_panelClassMapper.Class">

View File

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

View File

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

View File

@ -1,55 +1,56 @@
@namespace AntDesign
@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">
@if (Mask)
{
<div class="ant-drawer-mask" @onclick="_=>MaskClick()" style="@MaskStyle"></div>
}
<div class="ant-drawer-content-wrapper @WrapClassName " style="@WrapperStyle">
<div class="ant-drawer-content">
<div class="ant-drawer-wrapper-body" style="@(IsLeftOrRight?"height:100%":"")">
@if (_title.Value != null || Closable)
{
<div class="@TitleClassMapper.Class">
@if (_title.Value != null)
<div class="@TitleClassMapper.Class">
@if (_title.Value != null)
{
<div class="ant-drawer-title">
@if (TitleTemplate != null)
{
@TitleTemplate
}
@if (!string.IsNullOrEmpty(TitleString))
{
@((MarkupString)TitleString)
}
</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">
@if (TitleTemplate != null)
{
@TitleTemplate
}
@if (!string.IsNullOrEmpty(TitleString))
{
@((MarkupString)TitleString)
}
</div>
@ContentTemplate
}
@if (Closable)
@if (string.IsNullOrEmpty(ContentString))
{
<button @onclick="_=>CloseClick()" aria-label="Close" class="ant-drawer-close">
<Icon Type="close" />
</button>
@((MarkupString)ContentString)
}
@ChildContent
</div>
}
<div class="ant-drawer-body" style="@BodyStyle">
@if (ContentTemplate != null)
{
@ContentTemplate
}
@if (string.IsNullOrEmpty(ContentString))
{
@((MarkupString)ContentString)
}
@ChildContent
</div>
</div>
@if (Handler != null)
{
@Handler
}
</div>
@if (Handler != null)
{
@Handler
}
</div>
</div>
</CascadingValue>

View File

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

View File

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

View File

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

View File

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

View File

@ -12,7 +12,7 @@
</span>
</div>
<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"
aria-valuenow="@CurrentValue" class="ant-input-number-input" @bind="@CurrentValueAsString" @oninput="OnInput" @onkeydown="OnKeyDown" @onfocus="@OnFocus" @onblur="@OnBlurAsync" disabled="@Disabled" />
<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="@OnFocusAsync" @onblur="@OnBlurAsync" disabled="@Disabled" />
</div>
</div>

View File

@ -74,6 +74,9 @@ namespace AntDesign
[Parameter]
public EventCallback<TValue> OnChange { get; set; }
[Parameter]
public EventCallback<FocusEventArgs> OnFocus { get; set; }
private readonly bool _isNullable;
private bool _hasDefaultValue;
@ -115,40 +118,40 @@ namespace AntDesign
private static readonly Dictionary<Type, object> _defaultMaximum = new Dictionary<Type, object>()
{
{ typeof(sbyte),sbyte.MaxValue },
{ typeof(sbyte), sbyte.MaxValue },
{ typeof(byte), byte.MaxValue },
{ typeof(short),short.MaxValue },
{ typeof(ushort),ushort.MaxValue },
{ typeof(short), short.MaxValue },
{ typeof(ushort), ushort.MaxValue },
{ typeof(int),int.MaxValue },
{ typeof(uint),uint.MaxValue },
{ typeof(int), int.MaxValue },
{ typeof(uint), uint.MaxValue },
{ typeof(long),long.MaxValue },
{ typeof(ulong),ulong.MaxValue },
{ typeof(long), long.MaxValue },
{ typeof(ulong), ulong.MaxValue },
{ typeof(float),float.PositiveInfinity },
{ typeof(double),double.PositiveInfinity },
{ typeof(decimal),decimal.MaxValue },
{ typeof(float), float.PositiveInfinity },
{ typeof(double), double.PositiveInfinity },
{ typeof(decimal), decimal.MaxValue },
};
private static readonly Dictionary<Type, object> _defaultMinimum = new Dictionary<Type, object>()
{
{ typeof(sbyte),sbyte.MinValue },
{ typeof(sbyte), sbyte.MinValue },
{ typeof(byte), byte.MinValue },
{ typeof(short),short.MinValue },
{ typeof(ushort),ushort.MinValue },
{ typeof(short), short.MinValue },
{ typeof(ushort), ushort.MinValue },
{ typeof(int),int.MinValue },
{ typeof(uint),uint.MinValue },
{ typeof(int), int.MinValue },
{ typeof(uint), uint.MinValue },
{ typeof(long),long.MinValue },
{ typeof(ulong),ulong.MinValue },
{ typeof(long), long.MinValue },
{ typeof(ulong), ulong.MinValue },
{ typeof(float),float.NegativeInfinity},
{ typeof(double),double.NegativeInfinity },
{ typeof(decimal),decimal.MinValue },
{ typeof(float), float.NegativeInfinity},
{ typeof(double), double.NegativeInfinity },
{ typeof(decimal), decimal.MinValue },
};
private static Type[] _floatTypes = new Type[] { typeof(float), typeof(double), typeof(decimal) };
@ -160,6 +163,8 @@ namespace AntDesign
private CancellationTokenSource _decreaseTokenSource;
private TValue _defaultValue;
private string _inputNumberMode = "numeric";
public InputNumber()
{
_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);
var lambdaRound = Expression.Lambda<Func<TValue, int, TValue>>(expRound, num, decimalPlaces);
_roundFunc = lambdaRound.Compile();
_inputNumberMode = "decimal";
}
if (_defaultMaximum.ContainsKey(underlyingType)) Max = (TValue)_defaultMaximum[underlyingType];
@ -404,10 +410,15 @@ namespace AntDesign
_inputString = args.Value?.ToString();
}
private void OnFocus()
private async Task OnFocusAsync(FocusEventArgs args)
{
_focused = true;
CurrentValue = Value;
if (OnFocus.HasDelegate)
{
await OnFocus.InvokeAsync(args);
}
}
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.Threading;
using System.Threading.Tasks;
@ -32,7 +31,7 @@ namespace AntDesign
protected virtual bool EnableOnPressEnter => OnPressEnter.HasDelegate;
[Inject]
public DomEventService DomEventService { get; set; }
protected IDomEventListener DomEventListener { get; set; }
/// <summary>
/// The label text displayed before (on the left side of) the input field.
@ -52,6 +51,13 @@ namespace AntDesign
[Parameter]
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]
public bool AutoFocus
{
@ -168,8 +174,11 @@ namespace AntDesign
[Parameter]
public bool ReadOnly { get; set; }
/// <summary>
/// Controls onclick & blur event propagation.
/// </summary>
[Parameter]
public bool AutoComplete { get; set; } = true;
public bool StopPropagation { get; set; }
/// <summary>
/// The suffix icon for the Input.
@ -451,9 +460,10 @@ namespace AntDesign
return;
}
_debounceTimer?.Dispose();
if (_debounceTimer != null)
{
await _debounceTimer.DisposeAsync();
_debounceTimer = null;
}
}
@ -477,23 +487,21 @@ namespace AntDesign
if (firstRender)
{
DomEventService.AddEventListener(Ref, "compositionstart", OnCompositionStart);
DomEventService.AddEventListener(Ref, "compositionend", OnCompositionEnd);
DomEventListener.AddExclusive<JsonElement>(Ref, "compositionstart", OnCompositionStart);
DomEventListener.AddExclusive<JsonElement>(Ref, "compositionend", OnCompositionEnd);
if (this.AutoFocus)
{
IsFocused = true;
await this.FocusAsync(Ref);
}
DomEventService.AddEventListener(Ref, "focus", OnFocusInternal, true);
DomEventListener.AddExclusive<JsonElement>(Ref, "focus", OnFocusInternal);
}
}
protected override void Dispose(bool disposing)
{
DomEventService.RemoveEventListerner<JsonElement>(Ref, "compositionstart", OnCompositionStart);
DomEventService.RemoveEventListerner<JsonElement>(Ref, "compositionend", OnCompositionEnd);
DomEventService.RemoveEventListerner<JsonElement>(Ref, "focus", OnFocusInternal);
DomEventListener.DisposeExclusive();
_debounceTimer?.Dispose();
base.Dispose(disposing);
@ -641,6 +649,13 @@ namespace AntDesign
//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(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.CloseElement();

View File

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

View File

@ -1,6 +1,5 @@
@namespace AntDesign
@inherits Input<string>
<!--TODO: minheight, maxheight, onResize-->
@{
Dictionary<string, object> attributes =
@ -19,7 +18,7 @@
{ "style", Style },
{ "class", ClassMapper.Class },
{ "disabled", Disabled },
{ "readonly", ReadOnly },
{ "readonly", ReadOnly },
};
if (Attributes != null)
@ -34,13 +33,13 @@
@if (Suffix != null)
{
<span class="@_warpperClassMapper.Class">
<textarea @ref="Ref" @attributes="attributes"/>
<textarea @ref="Ref" @attributes="attributes" @onchange:stopPropagation="@StopPropagation" @onblur:stopPropagation="@StopPropagation"/>
@Suffix
</span>
}
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>
</div>
@ -50,12 +49,12 @@ else
@if (Suffix != null)
{
<span class="@_warpperClassMapper.Class">
<textarea @ref="Ref" @attributes="attributes"/>
<textarea @ref="Ref" @attributes="attributes" @onchange:stopPropagation="@StopPropagation" @onblur:stopPropagation="@StopPropagation"/>
@Suffix
</span>
}
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.Globalization;
using System.Diagnostics;
using System.Text.Json;
using System.Threading.Tasks;
using AntDesign.JsInterop;
@ -28,10 +26,39 @@ namespace AntDesign
private uint _minRows = DEFAULT_MIN_ROWS;
private uint _maxRows = uint.MaxValue;
private bool _hasMinOrMaxSet;
private bool _hasMinSet;
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]
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>
/// When `false`, value will be set to `null` when content is empty
@ -58,12 +85,13 @@ namespace AntDesign
if (value >= MinRows)
{
_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;
}
else
{
_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
{
_hasMinOrMaxSet = true;
_hasMinSet = true;
if (value >= DEFAULT_MIN_ROWS && value <= MaxRows)
{
_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;
}
else
{
_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>
/// Callback when the size changes
/// </summary>
@ -132,41 +169,25 @@ namespace AntDesign
if (AutoSize)
{
DomEventService.AddEventListener("window", "beforeunload", Reloading, false);
await CalculateRowHeightAsync();
DomEventListener.AddShared<JsonElement>("window", "beforeunload", Reloading);
}
await CalculateRowHeightAsync();
}
protected override bool TryParseValueFromString(string value, out string result, out string validationErrorMessage)
{
validationErrorMessage = null;
if (string.IsNullOrWhiteSpace(value))
{
if (DefaultToEmptyString)
result = string.Empty;
else
result = default;
validationErrorMessage = null;
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)
@ -174,7 +195,7 @@ namespace AntDesign
if (AutoSize && !_isReloading)
{
_reference?.Dispose();
DomEventService.RemoveEventListerner<JsonElement>("window", "beforeunload", Reloading);
DomEventListener.Dispose();
_ = InvokeAsync(async () =>
{
@ -189,6 +210,8 @@ namespace AntDesign
/// Indicates that a page is being refreshed
/// </summary>
private bool _isReloading;
private bool _autoSize;
private string _resizeStyle = "";
private void Reloading(JsonElement jsonElement) => _isReloading = true;
@ -205,33 +228,39 @@ namespace AntDesign
{
_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;
_offsetHeight = textAreaInfo.PaddingTop + textAreaInfo.PaddingBottom
+ textAreaInfo.BorderTop + textAreaInfo.BorderBottom;
uint rows = (uint)(textAreaInfo.ScrollHeight / _rowHeight);
if (_hasMinOrMaxSet)
rows = Math.Max((uint)MinRows, rows);
double height = 0;
if (rows > MaxRows)
{
rows = MaxRows;
height = rows * _rowHeight + _offsetHeight;
Style = $"height: {height}px;";
Style = $"height: {MaxRows * _rowHeight + _offsetHeight}px;{_resizeStyle};overflow-x: hidden";
}
else
{
height = rows * _rowHeight + _offsetHeight;
Style = $"height: {height}px;overflow-y: hidden;";
string overflow = _autoSize ? "hidden" : "visible";
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 LineHeight { get; set; }

View File

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

View File

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

View File

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

View File

@ -16,7 +16,7 @@ namespace AntDesign
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";
@ -40,8 +40,6 @@ namespace AntDesign
[Parameter] public bool Split { get; set; } = true;
[Parameter] public EventCallback<TItem> OnItemClick { get; set; }
[Parameter] public ListGridType Grid { 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()
{
SetClassMap();
if (Grid?.Column > 0)
{
_columnWidth = 100d / Grid.Column;
}
base.OnInitialized();
}
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()
.Add(PrefixName)
.If($"{PrefixName}-split", () => Split)
.If($"{PrefixName}-rtl", () => RTL)
.If($"{PrefixName}-bordered", () => Bordered)
.GetIf(() => $"{PrefixName}-{sizeCls}", () => !string.IsNullOrEmpty(sizeCls))
.GetIf(() => $"{PrefixName}-{SizeCls}", () => !string.IsNullOrEmpty(SizeCls))
.If($"{PrefixName}-vertical", () => ItemLayout == ListItemLayout.Vertical)
.If($"{PrefixName}-loading", () => (Loading))
.If($"{PrefixName}-grid", () => Grid != null)
.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
{
<AntDesign.Col Flex="1" Style="@ColStyle">
<div class="@ClassMapper.Class" style="@Style" Id="@Id" @onclick="HandleClick" @onclick:stopPropagation @ref="Ref">
@itemChildren(this)
</div>
</AntDesign.Col>
<div style="width: @(AntList.ColumnWidth)%; max-width: @(AntList.ColumnWidth)%;">
<AntDesign.Col Flex="1" >
<div class="@ClassMapper.Class" style="@Style" Id="@Id" @onclick="HandleClick" @onclick:stopPropagation @ref="Ref">
@itemChildren(this)
</div>
</AntDesign.Col>
</div>
}
@code{
RenderFragment<ListItem> itemChildren = content =>
@<Template>
@if (content.ItemLayout == ListItemLayout.Vertical && content.Extra != null)
@if (content.IsVerticalAndExtra)
{
<div class="@content.PrefixName-main">
@content.ChildContent

View File

@ -1,9 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Threading.Tasks;
using AntDesign.JsInterop;
using Microsoft.AspNetCore.Components;
@ -19,8 +14,6 @@ namespace AntDesign
[Parameter] public RenderFragment[] Actions { get; set; }
[Parameter] public ListGridType Grid { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public string ColStyle { get; set; }
@ -31,86 +24,21 @@ namespace AntDesign
[Parameter] public bool NoFlex { get; set; }
[CascadingParameter] public ListItemLayout ItemLayout { get; set; }
[CascadingParameter(Name = "ItemClick")] public Action ItemClick { get; set; }
[CascadingParameter]
private IAntList AntList { get; set; }
[Inject]
public DomEventService DomEventService { get; set; }
private IDomEventListener DomEventListener { get; set; }
public bool IsVerticalAndExtra()
{
return this.ItemLayout == ListItemLayout.Vertical && this.Extra != null;
}
public bool IsVerticalAndExtra => AntList?.ItemLayout == ListItemLayout.Vertical && this.Extra != null;
private ListGridType Grid => AntList.Grid;
protected override async Task OnInitializedAsync()
{
SetClassMap();
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()
{
ClassMapper.Clear()
@ -118,48 +46,18 @@ namespace AntDesign
.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()
{
if (OnClick.HasDelegate)
{
OnClick.InvokeAsync(this);
}
ItemClick?.Invoke();
}
protected override void Dispose(bool disposing)
{
DomEventListener.Dispose();
base.Dispose(disposing);
DomEventService.RemoveEventListerner<object>("window", "resize", OnResize);
}
}
}

View File

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

View File

@ -20,6 +20,7 @@ namespace AntDesign
[Parameter]
public MenuTheme Theme { get; set; } = MenuTheme.Light;
internal MenuMode? InitialMode { get; private set; }
[Parameter]
public MenuMode Mode
{
@ -110,7 +111,7 @@ namespace AntDesign
public EventCallback<string[]> SelectedKeysChanged { get; set; }
[Parameter]
public TriggerType TriggerSubMenuAction { get; set; } = TriggerType.Hover;
public Trigger TriggerSubMenuAction { get; set; } = Trigger.Hover;
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)}");
InternalMode = Mode;
InitialMode = Mode;
Parent?.AddMenu(this);
OpenKeys = DefaultOpenKeys?.ToArray() ?? OpenKeys;

View File

@ -2,43 +2,67 @@
@inherits AntDomComponentBase
<CascadingValue Value="this" IsFixed>
<Tooltip Title="@content(this)" Placement="@PlacementType.Right" Disabled="TooltipDisabled">
<Unbound>
<li class="@ClassMapper.Class" role="menuitem" style=" @(PaddingLeft>0? $"padding-left:{PaddingLeft}px;":"") @Style" @onclick="HandleOnClick" @key="Key" @ref="context.Current">
@* 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 *@
@if (RootMenu.Mode == MenuMode.Inline && ParentMenu is null)
{
<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)
{
@IconTemplate
}
else if (Icon != null)
{
<Icon Type="@Icon" />
}
@if (RouterLink == null)
{
@content(this)
}
else
{
<MenuLink Href="@RouterLink" Match="@RouterMatch">@content(this)</MenuLink>
}
</li>
</Unbound>
</Tooltip>
<span class="ant-menu-title-content">
@if (RouterLink == null)
{
@content(this)
}
else
{
<MenuLink Href="@RouterLink" Match="@RouterMatch">@content(this)</MenuLink>
}
</span>
</li>
}
</CascadingValue>
@code {
RenderFragment<MenuItem> content = item =>@<Template>
<span>
@if (item.Title != null)
{
@item.Title
}
else
{
@item.ChildContent
}
</span>
</Template>;
RenderFragment<MenuItem> content = item =>
@<Template>
@if (item.Title != null)
{
@item.Title
}
else
{
@item.ChildContent
}
</Template>
;
}

View File

@ -31,7 +31,7 @@ else
{
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">
@ -41,11 +41,11 @@ else
ComplexAutoCloseAndVisible="true"
BoundaryAdjustMode="@TriggerBoundaryAdjustMode.None"
Disabled="Disabled"
Placement="Placement"
Placement="Placement.Value"
OnVisibleChange="OnOverlayVisibleChange"
OnOverlayHiding="OnOverlayHiding"
Trigger="new[] { RootMenu?.TriggerSubMenuAction }"
PlacementCls="@($"{prefixCls}-placement-{Placement.Name} {prefixCls}-popup")"
Trigger="new Trigger[] { RootMenu?.TriggerSubMenuAction ?? Trigger.Hover }"
PlacementCls="@($"{prefixCls}-placement-{_placement.Name} {prefixCls}-popup")"
OverlayEnterCls="@($"{prefixCls}-{RootMenu.Theme} ")"
OverlayLeaveCls="@($"{prefixCls}-{RootMenu.Theme} ")"
OverlayHiddenCls="@($"{RootMenu.PrefixCls}-hidden")">

View File

@ -16,7 +16,21 @@ namespace AntDesign
public SubMenu Parent { get; set; }
[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]
public string Title { get; set; }
@ -57,6 +71,7 @@ namespace AntDesign
private OverlayTrigger _overlayTrigger;
internal bool _overlayVisible;
private PlacementType? _placement;
private void SetClass()
{
@ -136,7 +151,16 @@ namespace AntDesign
base.OnParametersSet();
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)

View File

@ -7,16 +7,18 @@
{
<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)"
@onmouseup="@EventUtil.AsNonRenderingEventHandler(OnMaskMouseUp)"
@onkeydown="@OnKeyDown"
style="@_wrapStyle">
<div @ref="@_modal" role="document" class=@($"{Config.PrefixCls} {GetModalClsName()}")
@onclick:stopPropagation
@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 class=@($"{Config.PrefixCls}-content")>
<div class=@($"{Config.PrefixCls}-content") id=@($"{Config.PrefixCls}-wrap_{DialogWrapperId}") >
@if (Config.Closable)
{
<button type="button" aria-label="Close" class=@($"{Config.PrefixCls}-close") @onclick="@OnCloserClick">

View File

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

View File

@ -54,13 +54,13 @@ namespace AntDesign
public Popconfirm()
{
PrefixCls = "ant-popover";
Placement = PlacementType.Top;
Trigger = new[] { TriggerType.Click };
Placement = Placement.Top;
Trigger = new[] { AntDesign.Trigger.Click };
}
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));
}
@ -70,7 +70,7 @@ namespace AntDesign
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));
}

View File

@ -32,7 +32,7 @@ namespace AntDesign
public Popover()
{
PrefixCls = "ant-popover";
Placement = PlacementType.Top;
Placement = Placement.Top;
}
internal override string GetOverlayEnterClass()
@ -47,7 +47,7 @@ namespace AntDesign
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));
}
@ -57,7 +57,7 @@ namespace AntDesign
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));
}

View File

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

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.Json;
using System.Threading.Tasks;
using AntDesign.Core.Helpers.MemberPath;
@ -21,84 +22,70 @@ namespace AntDesign
{
#region Parameters
/// <summary>
/// Overlay adjustment strategy (when for example browser resize is happening)
/// </summary>
[Parameter]
public TriggerBoundaryAdjustMode BoundaryAdjustMode { get; set; } = TriggerBoundaryAdjustMode.None;
/// <summary>
/// Toggle the border style.
/// </summary>
[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>
/// 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>
[Parameter]
public IEnumerable<TItem> DataSource
private MethodInfo _dataSourceItemShallowCopyMehtod;
private MethodInfo GetDataSourceItemCloneMethod()
{
get => _datasource;
set
if (_dataSourceItemShallowCopyMehtod is null)
{
if (value == null && _datasource == null)
_dataSourceItemShallowCopyMehtod = this.GetType().GetGenericArguments()[1]
.GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic);
if (DataSourceEqualityComparer is null)
{
return;
}
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;
}
DataSourceEqualityComparer = new DataSourceEqualityComparer<TItemValue, TItem>(this);
}
}
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>
/// Activates the first item that is not deactivated.
/// </summary>
@ -315,6 +302,7 @@ namespace AntDesign
if (_valueHasChanged)
{
_selectedValue = value;
_valueHasChanged = _isInitialized;
}
}
}
@ -339,13 +327,9 @@ namespace AntDesign
}
}
#endregion Parameters
[Inject] private DomEventService DomEventService { get; set; }
[Inject] private IDomEventListener DomEventListener { get; set; }
#region Properties
@ -359,12 +343,14 @@ namespace AntDesign
}
internal ElementReference DropDownRef => _dropDown.GetOverlayComponent().Ref;
private ElementReference _scrollableSelectDiv;
private string _dropdownStyle = string.Empty;
private TItemValue _selectedValue;
private TItemValue _defaultValue;
private bool _defaultValueIsNotNull;
private IEnumerable<TItem> _datasource;
private bool _afterFirstRender;
private bool _optionsHasInitialized;
private bool _defaultValueApplied;
private bool _defaultActiveFirstOptionApplied;
@ -376,7 +362,7 @@ namespace AntDesign
private string _labelName;
private Func<TItem, string> _getLabel;
internal Func<TItem, string> _getLabel;
private string _groupName = string.Empty;
@ -389,7 +375,7 @@ namespace AntDesign
private string _valueName;
private Func<TItem, TItemValue> _getValue;
internal Func<TItem, TItemValue> _getValue;
private bool _disableSubmitFormOnEnter;
private bool _showArrowIcon = true;
@ -441,10 +427,15 @@ namespace AntDesign
protected override void OnParametersSet()
{
EvaluateDataSourceChange();
if (SelectOptions == null)
{
CreateDeleteSelectOptions();
_optionsHasInitialized = true;
if (!_optionsHasInitialized || _dataSourceHasChanged)
{
CreateDeleteSelectOptions();
_optionsHasInitialized = true;
_dataSourceHasChanged = false;
}
}
if (_valueHasChanged && _optionsHasInitialized)
@ -456,10 +447,116 @@ namespace AntDesign
EditContext?.NotifyFieldChanged(FieldIdentifier);
}
}
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)
{
if (SelectOptions != null)
@ -471,7 +568,7 @@ namespace AntDesign
{
await SetInitialValuesAsync();
DomEventService.AddEventListener("window", "resize", OnWindowResize, false);
DomEventListener.AddShared<JsonElement>("window", "resize", OnWindowResize);
await SetDropdownStyleAsync();
_defaultValueApplied = !(_defaultValueIsNotNull || _defaultValuesHasItems);
@ -511,11 +608,12 @@ namespace AntDesign
}
await base.OnAfterRenderAsync(firstRender);
_afterFirstRender = true;
}
protected override void Dispose(bool disposing)
{
DomEventService.RemoveEventListerner<JsonElement>("window", "resize", OnWindowResize);
DomEventListener.Dispose();
base.Dispose(disposing);
}
@ -567,7 +665,7 @@ namespace AntDesign
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;
SelectOptionItem<TItemValue, TItem> selectOption;
@ -725,7 +823,7 @@ namespace AntDesign
/// <returns></returns>
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;
}
protected virtual string GetLabel(TItem item)
{
return item.ToString();
}
#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>
/// 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 (SelectOptions is not null)
{
LastValueBeforeReset = value;
}
if (!AllowClear)
_ = TrySetDefaultValueAsync();
else
@ -1399,9 +1508,6 @@ namespace AntDesign
currentSelected.IsActive = true;
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);
}
@ -1504,9 +1610,6 @@ namespace AntDesign
currentSelected.IsActive = true;
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);
}
@ -1703,9 +1806,6 @@ namespace AntDesign
currentSelected.IsActive = true;
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);
}
else if (ActiveOption == null)//position on first element in the list

View File

@ -161,6 +161,7 @@ namespace AntDesign
protected override async Task OnInitializedAsync()
{
bool isAlreadySelected = false;
if (SelectParent.SelectOptions == null)
{
// The SelectOptionItem was already created, now only the SelectOption has to be
@ -176,8 +177,9 @@ namespace AntDesign
GroupName = Model.GroupName;
Value = Model.Value;
Model.ChildComponent = this;
isAlreadySelected = IsAlreadySelected(Model);
}
else
else
{
// The SelectOption was not created by using a DataSource, a SelectOptionItem must be created.
InternalId = Guid.NewGuid();
@ -194,11 +196,27 @@ namespace AntDesign
};
SelectParent.SelectOptionItems.Add(newSelectOptionItem);
isAlreadySelected = IsAlreadySelected(newSelectOptionItem);
}
SetClassMap();
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()

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

View File

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

View File

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

View File

@ -1,72 +1,89 @@
@namespace AntDesign
@using AntDesign.TableModels
@inherits ColumnBase
@if (IsInitialize)
{
return;
return;
}
else if (IsPlaceholder)
{
<td style="padding: 0px; border: 0px; height: 0px;"></td>
<td style="padding: 0px; border: 0px; height: 0px;"></td>
}
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)
{
@if (AppendExpandColumn)
{
<col class="ant-table-expand-icon-col">
}
@if (AppendExpandColumn)
{
<col class="ant-table-expand-icon-col">
}
if (Width != null)
{
<col style="width: @((CssSizeLength)Width); min-width: @((CssSizeLength)Width);">
}
else
{
<col />
}
if (Width != null)
{
<col style="width: @((CssSizeLength)Width); min-width: @((CssSizeLength)Width);">
}
else
{
<col />
}
}
else if (IsHeader && HeaderColSpan != 0)
{
@if (AppendExpandColumn)
{
<th class="ant-table-cell ant-table-row-expand-icon-cell"></th>
}
@if (AppendExpandColumn)
{
<th class="ant-table-cell ant-table-row-expand-icon-cell"></th>
}
<th class="@ClassMapper.Class" style="@FixedStyle @HeaderStyle" colspan="@HeaderColSpan">
@if (TitleTemplate != null)@TitleTemplate else @Title
</th>
<th class="@ClassMapper.Class" style="@FixedStyle @HeaderStyle" colspan="@HeaderColSpan">
@if (TitleTemplate != null)
{
@TitleTemplate
}
else
{
@Title
}
</th>
}
else if (!IsHeader && RowSpan != 0 && ColSpan != 0)
{
@if (AppendExpandColumn)
{
<td class="ant-table-cell ant-table-row-expand-icon-cell">
@if (Table.RowExpandable(RowData) && (!Table.TreeMode || !RowData.HasChildren))
{
<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")"
aria-label="@(RowData.Expanded?Table.Locale.Collapse:Table.Locale.Expand)"></button>
}
</td>
}
@if (AppendExpandColumn)
{
<td class="ant-table-cell ant-table-row-expand-icon-cell">
@if (Table.RowExpandable(RowData) && (!Table.TreeMode || !RowData.HasChildren))
{
<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")"
aria-label="@(RowData.Expanded?Table.Locale.Collapse:Table.Locale.Expand)"></button>
}
</td>
}
<td class="@ClassMapper.Class" style="@FixedStyle @Style" rowspan="@RowSpan" colspan="@ColSpan" data-label="@Context.HeaderColumns[ColIndex].Title">
@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" 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>
}
}
@ChildContent
</td>
<td class="@ClassMapper.Class" style="@FixedStyle @Style" rowspan="@RowSpan" colspan="@ColSpan" data-label="@Context.HeaderColumns[ColIndex].Title">
@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" 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>
}
}
@if (CellRender != null)
{
var cellData = new CellData(RowData);
@CellRender(cellData)
}
else
{
@ChildContent
}
</td>
}

View File

@ -8,386 +8,404 @@
@if (IsInitialize)
{
return;
return;
}
else if (Hidden)
{
return;
return;
}
else if (IsPlaceholder)
{
<td style="padding: 0px; border: 0px; height: 0px;"></td>
<td style="padding: 0px; border: 0px; height: 0px;"></td>
}
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)
{
@if (AppendExpandColumn)
{
<col class="ant-table-expand-icon-col">
}
@if (AppendExpandColumn)
{
<col class="ant-table-expand-icon-col">
}
if (Width != null)
{
<col style="width: @((CssSizeLength)Width); min-width: @((CssSizeLength)Width);">
}
else
{
<col />
}
if (Width != null)
{
<col style="width: @((CssSizeLength)Width); min-width: @((CssSizeLength)Width);">
}
else
{
<col />
}
}
else if (IsHeader && HeaderColSpan != 0)
{
@if (AppendExpandColumn)
{
<th class="ant-table-cell ant-table-row-expand-icon-cell"></th>
}
@if (AppendExpandColumn)
{
<th class="ant-table-cell ant-table-row-expand-icon-cell"></th>
}
var headerCellAttributes = OnHeaderCell?.Invoke();
<CascadingValue Name="IsHeader" Value="false" IsFixed>
<th class="@ClassMapper.Class" style="@FixedStyle @HeaderStyle" colspan="@(HeaderColSpan > 1 ? HeaderColSpan : false)" title="@(Ellipsis ? Title : false)" @attributes="headerCellAttributes">
@if (Sortable || (_filterable && _filters?.Any() == true))
{
@FilterToolTipSorter
}
else if (TitleTemplate != null)
{
@TitleTemplate
}
else
{
@Title
}
</th>
</CascadingValue>
var headerCellAttributes = OnHeaderCell?.Invoke();
<CascadingValue Name="IsHeader" Value="false" IsFixed>
<th class="@ClassMapper.Class" style="@FixedStyle @HeaderStyle" colspan="@(HeaderColSpan > 1 ? HeaderColSpan : false)" title="@(Ellipsis ? HeaderTitle : false)" @attributes="headerCellAttributes">
@if (Sortable || (_filterable && _filters?.Any() == true))
{
@FilterToolTipSorter
}
else if (TitleTemplate != null)
{
@TitleTemplate
}
else
{
@HeaderTitle
}
</th>
</CascadingValue>
}
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)
{
<td class="ant-table-cell ant-table-row-expand-icon-cell">
@if (Table.RowExpandable(RowData) && (!Table.TreeMode || !RowData.HasChildren))
{
<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")"
aria-label="@(RowData.Expanded ? Table.Locale.Collapse : Table.Locale.Expand)"></button>
}
</td>
}
@if (AppendExpandColumn)
{
<td class="ant-table-cell ant-table-row-expand-icon-cell">
@if (Table.RowExpandable(RowData) && (!Table.TreeMode || !RowData.HasChildren))
{
<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")"
aria-label="@(RowData.Expanded ? Table.Locale.Collapse : Table.Locale.Expand)"></button>
}
</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);
<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>
}
}
@if (CellRender != null)
{
@CellRender(Field)
}
else if (ChildContent != null)
{
@ChildContent
}
else
{
if (!string.IsNullOrWhiteSpace(Format))
{
@(Formatter<TData>.Format(Field, Format))
}
else
{
@Field
}
}
</td>
</CascadingValue>
@if (CellRender != null)
{
@CellRender(cellData)
}
else if (ChildContent != null)
{
@ChildContent
}
else
{
if (!string.IsNullOrWhiteSpace(Format))
{
@(Formatter<TData>.Format(Field, Format))
}
else
{
@Field
}
}
</td>
</CascadingValue>
}
@code
{
string HeaderTitle => Title ?? DisplayName ?? FieldName;
RenderFragment SortHeader =>
@<Template>
<span>
@if (TitleTemplate != null) @TitleTemplate else @Title
</span>
@{
bool hasDescendingSorter = SortDirection.Descending.IsIn(SortDirections);
bool hasAscendingSorter = SortDirection.Ascending.IsIn(SortDirections);
}
<span class="ant-table-column-sorter@(hasDescendingSorter && hasAscendingSorter ? " ant-table-column-sorter-full" : "")">
<span class="ant-table-column-sorter-inner">
@if (hasAscendingSorter)
{
<Icon Type="caret-up" Class=@($"ant-table-column-sorter-up{(_sortDirection == SortDirection.Ascending ? " active" : "")}") />
}
@if (hasDescendingSorter)
{
<Icon Type="caret-down" Class=@($"ant-table-column-sorter-down{(_sortDirection == SortDirection.Descending ? " active" : "")}") />
}
</span>
</span>
</Template>;
RenderFragment SortHeader =>
@<Template>
<span>
@if (TitleTemplate != null)
{
@TitleTemplate
}
else
{
@HeaderTitle
}
</span>
@{
bool hasDescendingSorter = SortDirection.Descending.IsIn(SortDirections);
bool hasAscendingSorter = SortDirection.Ascending.IsIn(SortDirections);
}
<span class="ant-table-column-sorter@(hasDescendingSorter && hasAscendingSorter ? " ant-table-column-sorter-full" : "")">
<span class="ant-table-column-sorter-inner">
@if (hasAscendingSorter)
{
<Icon Type="caret-up" Class=@($"ant-table-column-sorter-up{(_sortDirection == SortDirection.Ascending ? " active" : "")}") />
}
@if (hasDescendingSorter)
{
<Icon Type="caret-down" Class=@($"ant-table-column-sorter-down{(_sortDirection == SortDirection.Descending ? " active" : "")}") />
}
</span>
</span>
</Template>
;
RenderFragment ToolTipSorter =>
@<Template>
@if (ShowSorterTooltip)
{
<Tooltip Title="@SorterTooltip">
<Unbound>
<div class="ant-table-column-sorters" @ref="context.Current" @onclick="HandleSort">
@SortHeader
</div>
</Unbound>
</Tooltip>
}
else
{
<div class="ant-table-column-sorters" @onclick="HandleSort">
@SortHeader
</div>
}
</Template>;
RenderFragment ToolTipSorter =>
@<Template>
@if (ShowSorterTooltip)
{
<Tooltip Title="@SorterTooltip">
<Unbound>
<div class="ant-table-column-sorters" @ref="context.Current" @onclick="HandleSort">
@SortHeader
</div>
</Unbound>
</Tooltip>
}
else
{
<div class="ant-table-column-sorters" @onclick="HandleSort">
@SortHeader
</div>
}
</Template>
;
RenderFragment FilterToolTipSorter =>
@<Template>
@if (_filterable && _filters?.Any() == true)
{
<div class="ant-table-filter-column">
@if (Sortable)
{
@ToolTipSorter
}
else if (TitleTemplate != null)
{
@TitleTemplate
}
else
{
@Title
}
RenderFragment FilterToolTipSorter =>
@<Template>
@if (_filterable && _filters?.Any() == true)
{
<div class="ant-table-filter-column">
@if (Sortable)
{
@ToolTipSorter
}
else if (TitleTemplate != null)
{
@TitleTemplate
}
else
{
@HeaderTitle
}
<Dropdown Trigger="new[] { TriggerType.Click }" Visible="_filterOpened" Placement="PlacementType.BottomRight" OnMaskClick="() => { if (_filterOpened) FilterConfirm(); }">
<Unbound>
<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; }">
<Icon Type="filter" Theme="fill" />
</span>
</Unbound>
<Overlay>
<div class="ant-table-filter-dropdown">
@if (_filters?.Any() == true && _columnFilterType == TableFilterType.List)
{
<Menu AutoCloseDropdown="false" SelectedKeys="_selectedFilterValues">
@foreach (var filter in _filters)
{
<MenuItem Key="@filter.Value?.ToString()" OnClick="() => FilterSelected(filter)">
@if (FilterMultiple)
{
<Checkbox Value="filter.Selected" ValueChanged="value => FilterSelected(filter)">@filter.Text</Checkbox>
}
else
{
<Radio TValue="bool" Checked="filter.Selected" CheckedChanged="value => FilterSelected(filter)">@filter.Text</Radio>
}
</MenuItem>
}
</Menu>
}
else
{
<div id="@("popup-container-for-" + Id)" style="position:relative!important"></div>
int filterCount = _filters.Count();
@for (int index = 0; index < filterCount; index++)
{
var filter = _filters.ElementAt(index);
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" : "")">
@if (index > 0)
{
<div>
<Space Style="margin-bottom:10px">
<SpaceItem>
<Select Value="filter.FilterCondition" TItemValue="TableFilterCondition" TItem="TableFilterCondition" ValueChanged="value => SetFilterCondition(filter,value)" PopupContainerSelector="@("#popup-container-for-" + Id)">
<SelectOptions>
<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>
</SelectOptions>
</Select>
</SpaceItem>
</Space>
</div>
}
<Space Style="width:100%;">
<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">
<SelectOptions>
<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>
@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.StartsWith" Label="@Table?.Locale.FilterOptions.StartsWith"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.EndsWith" Label="@Table?.Locale.FilterOptions.EndsWith"></SelectOption>
}
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.NotContains" Label="@Table?.Locale.FilterOptions.NotContains"></SelectOption>
}
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.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.LessThanOrEquals" Label="@Table?.Locale.FilterOptions.LessThanOrEquals"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.TheSameDateWith" Label="@Table?.Locale.FilterOptions.TheSameDateWith"></SelectOption>
}
else if (_columnDataType == typeof(Guid)) { }
else
{
<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.GreaterThanOrEquals" Label="@Table?.Locale.FilterOptions.GreaterThanOrEquals"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.LessThanOrEquals" Label="@Table?.Locale.FilterOptions.LessThanOrEquals"></SelectOption>
}
@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.IsNotNull" Label="@Table?.Locale.FilterOptions.IsNotNull"></SelectOption>
}
</SelectOptions>
</Select>
</SpaceItem>
@if (!noInput)
{
<SpaceItem>
@FilterInput(filter)
</SpaceItem>
}
@if (filterCount > 1)
{
<SpaceItem>
<Button Size="small" Icon="close" Type="primary" OnClick="()=> RemoveFilter(filter)">
</Button>
</SpaceItem>
}
</Space>
</div>
}
}
<div class="ant-table-filter-dropdown-btns">
<Button Size="small" Type="link" OnClick="ResetFilters">
@Table?.Locale.FilterReset
</Button>
@if (_columnFilterType == TableFilterType.FieldType)
{
<Button Size="small" Icon="plus" Type="primary" OnClick="AddFilter">
</Button>
}
<Button Size="small" Type="primary" OnClick="() => FilterConfirm()">
@Table?.Locale.FilterConfirm
</Button>
</div>
</div>
</Overlay>
</Dropdown>
</div>
}
else
{
@ToolTipSorter
}
</Template>;
<Dropdown Trigger="new[] { Trigger.Click }" Visible="_filterOpened" Placement="Placement.BottomRight" OnMaskClick="() => { if (_filterOpened) FilterConfirm(); }">
<Unbound>
<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; }">
<Icon Type="filter" Theme="fill" />
</span>
</Unbound>
<Overlay>
<div class="ant-table-filter-dropdown">
@if (_filters?.Any() == true && _columnFilterType == TableFilterType.List)
{
<Menu AutoCloseDropdown="false" SelectedKeys="_selectedFilterValues">
@foreach (var filter in _filters)
{
<MenuItem Key="@filter.Value?.ToString()" OnClick="() => FilterSelected(filter)">
@if (FilterMultiple)
{
<Checkbox Value="filter.Selected" ValueChanged="value => FilterSelected(filter)">@filter.Text</Checkbox>
}
else
{
<Radio TValue="bool" Checked="filter.Selected" CheckedChanged="value => FilterSelected(filter)">@filter.Text</Radio>
}
</MenuItem>
}
</Menu>
}
else
{
<div id="@("popup-container-for-" + Id)" style="position:relative!important"></div>
int filterCount = _filters.Count();
@for (int index = 0; index < filterCount; index++)
{
var filter = _filters.ElementAt(index);
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" : "")">
@if (index > 0)
{
<div>
<Space Style="margin-bottom:10px">
<SpaceItem>
<Select Value="filter.FilterCondition" TItemValue="TableFilterCondition" TItem="TableFilterCondition" ValueChanged="value => SetFilterCondition(filter,value)" PopupContainerSelector="@("#popup-container-for-" + Id)" BoundaryAdjustMode="@TriggerBoundaryAdjustMode.None">
<SelectOptions>
<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>
</SelectOptions>
</Select>
</SpaceItem>
</Space>
</div>
}
<Space Style="width:100%;">
<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)" BoundaryAdjustMode="@TriggerBoundaryAdjustMode.None" DropdownMatchSelectWidth="false">
<SelectOptions>
<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>
@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.StartsWith" Label="@Table?.Locale.FilterOptions.StartsWith"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.EndsWith" Label="@Table?.Locale.FilterOptions.EndsWith"></SelectOption>
}
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.NotContains" Label="@Table?.Locale.FilterOptions.NotContains"></SelectOption>
}
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.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.LessThanOrEquals" Label="@Table?.Locale.FilterOptions.LessThanOrEquals"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.TheSameDateWith" Label="@Table?.Locale.FilterOptions.TheSameDateWith"></SelectOption>
}
else if (_columnDataType == typeof(Guid)) { }
else
{
<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.GreaterThanOrEquals" Label="@Table?.Locale.FilterOptions.GreaterThanOrEquals"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.LessThanOrEquals" Label="@Table?.Locale.FilterOptions.LessThanOrEquals"></SelectOption>
}
@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.IsNotNull" Label="@Table?.Locale.FilterOptions.IsNotNull"></SelectOption>
}
</SelectOptions>
</Select>
</SpaceItem>
@if (!noInput)
{
<SpaceItem>
@FilterInput(filter)
</SpaceItem>
}
@if (filterCount > 1)
{
<SpaceItem>
<Button Size="small" Icon="close" Type="primary" OnClick="()=> RemoveFilter(filter)">
</Button>
</SpaceItem>
}
</Space>
</div>
}
}
<div class="ant-table-filter-dropdown-btns">
<Button Size="small" Type="link" OnClick="ResetFilters">
@Table?.Locale.FilterReset
</Button>
@if (_columnFilterType == TableFilterType.FieldType)
{
<Button Size="small" Icon="plus" Type="primary" OnClick="AddFilter">
</Button>
}
<Button Size="small" Type="primary" OnClick="() => FilterConfirm()">
@Table?.Locale.FilterConfirm
</Button>
</div>
</div>
</Overlay>
</Dropdown>
</div>
}
else
{
@ToolTipSorter
}
</Template>
;
RenderFragment<TableFilter> FilterInput => filter =>
@<Template>
@if (_columnDataType == typeof(DateTime))
{
if (filter.FilterCompareOperator != TableFilterCompareOperator.TheSameDateWith)
{
<DatePicker Value="(DateTime?)filter.Value" ShowTime="@true"
TValue="DateTime?" ValueChanged="value => SetFilterValue(filter, value?.AddMilliseconds(-value.Value.Millisecond))" PopupContainerSelector="@("#popup-container-for-" + Id)"></DatePicker>
}
else
{
<DatePicker Value="(DateTime?)filter.Value" TValue="DateTime?" ValueChanged="value => SetFilterValue(filter, value)" PopupContainerSelector="@("#popup-container-for-" + Id)"></DatePicker>
}
}
else if (_columnDataType.IsEnum)
{
<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 == typeof(byte))
{
<InputNumber Value="(byte?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="byte?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(decimal))
{
<InputNumber Value="(decimal?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="decimal?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(double))
{
<InputNumber Value="(double?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="double?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(short))
{
<InputNumber Value="(short?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="short?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(int))
{
<InputNumber Value="(int?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="int?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(long))
{
<InputNumber Value="(long?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="long?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(sbyte))
{
<InputNumber Value="(sbyte?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="sbyte?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(float))
{
<InputNumber Value="(float?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="float?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(ushort))
{
<InputNumber Value="(ushort?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="ushort?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(uint))
{
<InputNumber Value="(uint?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="uint?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(ulong))
{
<InputNumber Value="(ulong?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="ulong?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(Guid))
{
<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>;
RenderFragment<TableFilter> FilterInput => filter =>
@<Template>
@if (_columnDataType == typeof(DateTime))
{
if (filter.FilterCompareOperator != TableFilterCompareOperator.TheSameDateWith)
{
<DatePicker Value="(DateTime?)filter.Value" ShowTime="@true"
TValue="DateTime?" ValueChanged="value => SetFilterValue(filter, value?.AddMilliseconds(-value.Value.Millisecond))"
PopupContainerSelector="@("#popup-container-for-" + Id)"
BoundaryAdjustMode="@TriggerBoundaryAdjustMode.None"/>
}
else
{
<DatePicker Value="(DateTime?)filter.Value" TValue="DateTime?" ValueChanged="value => SetFilterValue(filter, value)"
PopupContainerSelector="@("#popup-container-for-" + Id)"
BoundaryAdjustMode="@TriggerBoundaryAdjustMode.None"/>
}
}
else if (_columnDataType.IsEnum)
{
<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))"
BoundaryAdjustMode="@TriggerBoundaryAdjustMode.None"/>
}
else if (_columnDataType == typeof(byte))
{
<InputNumber Value="(byte?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="byte?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(decimal))
{
<InputNumber Value="(decimal?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="decimal?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(double))
{
<InputNumber Value="(double?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="double?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(short))
{
<InputNumber Value="(short?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="short?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(int))
{
<InputNumber Value="(int?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="int?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(long))
{
<InputNumber Value="(long?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="long?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(sbyte))
{
<InputNumber Value="(sbyte?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="sbyte?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(float))
{
<InputNumber Value="(float?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="float?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(ushort))
{
<InputNumber Value="(ushort?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="ushort?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(uint))
{
<InputNumber Value="(uint?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="uint?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(ulong))
{
<InputNumber Value="(ulong?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="ulong?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(Guid))
{
<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]
public Expression<Func<TData>> FieldExpression { get; set; }
[Parameter]
public RenderFragment<TData> CellRender { get; set; }
[Parameter]
public TData Field
@ -70,7 +68,7 @@ namespace AntDesign
public SortDirection DefaultSortOrder { get; set; }
[Parameter]
public Func<RowData, Dictionary<string, object>> OnCell { get; set; }
public Func<CellData, Dictionary<string, object>> OnCell { get; set; }
[Parameter]
public Func<Dictionary<string, object>> OnHeaderCell { get; set; }

View File

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

View File

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

View File

@ -136,7 +136,7 @@ namespace AntDesign
public bool Responsive { get; set; } = true;
[Inject]
public DomEventService DomEventService { get; set; }
private IDomEventListener DomEventListener { get; set; }
public ColumnContext ColumnContext { get; set; }
@ -146,20 +146,20 @@ namespace AntDesign
private IList<SummaryRow> _summaryRows;
private bool _hasFirstLoad;
private bool _waitingReload;
private bool _waitingReloadAndInvokeChange;
private bool _treeMode;
private bool _hasFixLeft;
private bool _hasFixRight;
private bool _pingRight;
private bool _pingLeft;
private int _treeExpandIconColumnIndex;
private readonly ClassMapper _wrapperClassMapper = new ClassMapper();
private string TableLayoutStyle => TableLayout == null ? "" : $"table-layout: {TableLayout};";
private ElementReference _tableHeaderRef;
private ElementReference _tableBodyRef;
private ElementReference _tableRef;
private bool ServerSide => _hasRemoteDataSourceAttribute ? RemoteDataSource : Total > _dataSourceCount;
@ -325,8 +325,8 @@ namespace AntDesign
.If($"{prefixCls}-fixed-column {prefixCls}-scroll-horizontal", () => ScrollX != null)
.If($"{prefixCls}-has-fix-left", () => _hasFixLeft)
.If($"{prefixCls}-has-fix-right", () => _hasFixRight)
.If($"{prefixCls}-ping-left", () => _pingLeft)
.If($"{prefixCls}-ping-right", () => _pingRight)
//.If($"{prefixCls}-ping-left", () => _pingLeft)
//.If($"{prefixCls}-ping-right", () => _pingRight)
.If($"{prefixCls}-rtl", () => RTL)
;
@ -359,9 +359,21 @@ namespace AntDesign
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)
{
@ -371,28 +383,6 @@ namespace AntDesign
_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()
{
base.OnParametersSet();
@ -402,12 +392,19 @@ namespace AntDesign
_waitingReloadAndInvokeChange = false;
_waitingReload = false;
ReloadAndInvokeChange();
if (_hasFirstLoad)
{
ReloadAndInvokeChange();
}
}
else if (_waitingReload)
{
_waitingReload = false;
InternalReload();
if (_hasFirstLoad)
{
InternalReload();
}
}
if (this.RenderMode == RenderMode.ParametersHashCodeChanged)
@ -437,61 +434,9 @@ namespace AntDesign
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)
{
DomEventService.RemoveEventListerner<JsonElement>("window", "resize", OnResize);
DomEventService.RemoveEventListerner<JsonElement>(_tableBodyRef, "scroll", OnScroll);
DomEventService.RemoveEventListerner<JsonElement>("window", "beforeunload", Reloading);
DomEventListener.Dispose();
base.Dispose(disposing);
}
@ -499,12 +444,12 @@ namespace AntDesign
{
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)

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

View File

@ -23,7 +23,7 @@ namespace AntDesign
public Tooltip()
{
PrefixCls = "ant-tooltip";
Placement = PlacementType.Top;
Placement = Placement.Top;
}
internal override string GetOverlayEnterClass()
@ -38,7 +38,7 @@ namespace AntDesign
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));
}
@ -48,7 +48,7 @@ namespace AntDesign
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));
}
@ -56,7 +56,7 @@ namespace AntDesign
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"
Visible="Open"
Disabled="Disabled"
Trigger="new[] { TriggerType.Click }"
Trigger="new[] { Trigger.Click }"
HiddenMode
OnMouseEnter="@(() => { OnMouseEnter?.Invoke(); })"
OnMouseLeave="@(() => { OnMouseLeave?.Invoke(); })"

View File

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

View File

@ -18,6 +18,7 @@ When there is a need for autocomplete functionality.
| Property | Description | Type | Default |
| --- | --- | --- | --- |
| 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 | - |
| Disabled | Set disabled | bool | - |
| Placeholder | Placeholder text | string | - |

View File

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

View File

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

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