ant-design-blazor/components/affix/Affix.razor.cs
Andrzej Bakun 152a574577 feat(module: overlay): OverlayTrigger not bound to a div (#937)
* feat(module:overlay): OverlayTrigger not bound to a div

* feat(module:overlay): OverlayTrigger not bound to a div

* feat(module:overlay): Logic transfer to single Overlay

* feat(module:overlay): remove obsolete duplication

* feat(module:Tooltip): Add for unbounded oncontextmenu event handler

* feat(module:tooltip): unbound js event listeners remove

* docs(module:tooltip): unbound explanation

* fix(module:button): attach Ref to top level html element @ref

* feat(module:dropdown&tooltip&popconfirm&popover): Overlay not bound to a div

* docs(module:dropdown&tooltip&popconfirm&popover): unbound explanation

* feat(module:OverlayTrigger): common logic relocation

* feat(module:overlaytrigger): Overlay not bound to a div

* feat(module:DatePicker): Overlay not bound to a div

* feat(module:select): Overlay not boud to div

* fix(module:select): onclickarrow event relocation

* fix(module:select): rename Show to OnArrowClick

* feat(module:avatar): Overlay not bound to a div

* docs(module:avatar): demo switch to unbound version

* feat(module:autocomplete): partial OverlayTrigger not bound to a div

* feat(module:slider): tooltip

* docs(module:slider): tooltip

* fix(module:overlay): add SetVisible method

* feat: set Ref where missing, performance

components register Ref when missing
IsFixed flag for CascadeValue changed
hard-code sequence numbers when using RenderTreeBuilder
Rate component use Tooltip Unbound version
Tabs test fix

* fix: revert changes (accidental)

* feat(module:upload): tooltip with unbound usage

* feat(module:table): column use of unbound tooltip

* feat(module:autocomplete):overlay unbound from div

* fix(module:upload): missing div restore

Co-authored-by: James Yeung <shunjiey@hotmail.com>
2021-01-21 17:20:10 +08:00

185 lines
5.8 KiB
C#

using System.Text.Json;
using System.Threading.Tasks;
using AntDesign.JsInterop;
using Microsoft.AspNetCore.Components;
namespace AntDesign
{
public partial class Affix : AntDomComponentBase
{
private const string PrefixCls = "ant-affix";
private const string RootScollSelector = "window";
private const string RootRectSelector = "app";
private bool _affixed;
private bool _rootListened;
private bool _targetListened;
private bool Affixed
{
get => _affixed;
set
{
if (_affixed != value)
{
_affixed = value;
StateHasChanged();
if (OnChange.HasDelegate)
{
OnChange.InvokeAsync(_affixed);
}
}
}
}
private ElementReference _childRef;
private string _hiddenStyle;
private string _affixStyle;
[Inject]
private DomEventService DomEventService { get; set; }
#region Parameters
/// <summary>
/// Offset from the bottom of the viewport (in pixels)
/// </summary>
[Parameter]
public uint? OffsetBottom { get; set; }
/// <summary>
/// Offset from the top of the viewport (in pixels)
/// </summary>
[Parameter]
public uint? OffsetTop { get; set; } = 0;
/// <summary>
/// Specifies the scrollable area DOM node
/// </summary>
[Parameter]
public ElementReference Target { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public EventCallback<bool> OnChange { get; set; }
#endregion Parameters
protected override void OnInitialized()
{
base.OnInitialized();
SetClasses();
}
public async override Task SetParametersAsync(ParameterView parameters)
{
await base.SetParametersAsync(parameters);
if (!_targetListened && !string.IsNullOrEmpty(Target.Id))
{
DomEventService.AddEventListener(Target, "scroll", OnScroll);
DomEventService.AddEventListener(Target, "resize", OnWindowResize);
await RenderAffixAsync();
_targetListened = true;
}
}
protected async override Task OnFirstAfterRenderAsync()
{
await base.OnFirstAfterRenderAsync();
DomRect domRect = await JsInvokeAsync<DomRect>(JSInteropConstants.GetBoundingClientRect, _childRef);
_hiddenStyle = $"width: {domRect.width}px; height: {domRect.height}px;";
if (_rootListened)
{
await RenderAffixAsync();
}
else if (!_rootListened && string.IsNullOrEmpty(Target.Id))
{
DomEventService.AddEventListener(RootScollSelector, "scroll", OnScroll, false);
DomEventService.AddEventListener(RootScollSelector, "resize", OnWindowResize, false);
_rootListened = true;
}
}
private async void OnScroll(JsonElement obj)
{
await RenderAffixAsync();
}
private async void OnWindowResize(JsonElement obj)
{
await RenderAffixAsync();
}
private void SetClasses()
{
ClassMapper.Clear()
.If(PrefixCls, () => _affixed);
}
private async Task RenderAffixAsync()
{
DomRect childRect = await JsInvokeAsync<DomRect>(JSInteropConstants.GetBoundingClientRect, _childRef);
_hiddenStyle = $"width: {childRect.width}px; height: {childRect.height}px;";
DomRect domRect = await JsInvokeAsync<DomRect>(JSInteropConstants.GetBoundingClientRect, Ref);
DomRect appRect = await JsInvokeAsync<DomRect>(JSInteropConstants.GetBoundingClientRect, RootRectSelector);
// reset appRect.top / bottom, so its position is fixed.
appRect.top = 0;
appRect.bottom = appRect.height;
DomRect containerRect;
if (string.IsNullOrEmpty(Target.Id))
{
containerRect = appRect;
}
else
{
containerRect = await JsInvokeAsync<DomRect>(JSInteropConstants.GetBoundingClientRect, Target);
}
// become affixed
if (OffsetBottom.HasValue)
{
// domRect.bottom / domRect.top have the identical value here.
if (domRect.top > containerRect.height + containerRect.top)
{
_affixStyle = _hiddenStyle + $"bottom: { appRect.height - containerRect.bottom + OffsetBottom}px; position: fixed;";
Affixed = true;
}
else
{
_affixStyle = string.Empty;
Affixed = false;
}
}
else if (OffsetTop.HasValue)
{
if (domRect.top < containerRect.top + OffsetTop.Value)
{
_affixStyle = _hiddenStyle + $"top: {containerRect.top + OffsetTop}px; position: fixed;";
Affixed = true;
}
else
{
_affixStyle = string.Empty;
Affixed = false;
}
}
StateHasChanged();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
DomEventService.RemoveEventListerner<JsonElement>(RootScollSelector, "scroll", OnScroll);
DomEventService.RemoveEventListerner<JsonElement>(RootScollSelector, "resize", OnWindowResize);
}
}
}