using System.Diagnostics.CodeAnalysis;
using Connected.Annotations;
using Connected.Extensions;
using Connected.Utilities;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace Connected.Components;
public partial class Popover : UIComponent, IAsyncDisposable
{
[Inject] public IPopoverService Service { get; set; }
protected string PopoverClass =>
new CssBuilder("mud-popover")
.AddClass($"mud-popover-fixed", Fixed)
.AddClass($"mud-popover-open", Open)
.AddClass($"mud-popover-{TransformOrigin.ToDescriptionString()}")
.AddClass($"mud-popover-anchor-{AnchorOrigin.ToDescriptionString()}")
.AddClass($"mud-popover-overflow-{OverflowBehavior.ToDescriptionString()}")
.AddClass($"mud-popover-relative-width", RelativeWidth)
.AddClass($"mud-paper", Paper)
.AddClass($"mud-paper-square", Paper && Square)
.AddClass($"mud-elevation-{Elevation}", Paper)
.AddClass($"overflow-y-auto", MaxHeight != null)
.AddClass(Class)
.Build();
protected string PopoverStyles =>
new StyleBuilder()
.AddStyle("transition-duration", $"{Duration}ms")
.AddStyle("transition-delay", $"{Delay}ms")
.AddStyle("max-height", MaxHeight.ToPx(), MaxHeight != null)
.AddStyle(Style)
.Build();
internal Direction ConvertDirection(Direction direction)
{
return direction switch
{
Direction.Start => RightToLeft ? Direction.Right : Direction.Left,
Direction.End => RightToLeft ? Direction.Left : Direction.Right,
_ => direction
};
}
[CascadingParameter(Name = "RightToLeft")] public bool RightToLeft { get; set; }
///
/// Sets the maxheight the popover can have when open.
///
[Parameter]
[Category(CategoryTypes.Popover.Appearance)]
public int? MaxHeight { get; set; } = null;
///
/// If true, will apply default MudPaper classes.
///
[Parameter]
[Category(CategoryTypes.Popover.Appearance)]
public bool Paper { get; set; } = true;
///
/// The higher the number, the heavier the drop-shadow.
///
[Parameter]
[Category(CategoryTypes.Popover.Appearance)]
public int Elevation { set; get; } = 8;
///
/// If true, border-radius is set to 0.
///
[Parameter]
[Category(CategoryTypes.Popover.Appearance)]
public bool Square { get; set; }
///
/// If true, the popover is visible.
///
[Parameter]
[Category(CategoryTypes.Popover.Behavior)]
public bool Open { get; set; }
///
/// If true the popover will be fixed position instead of absolute.
///
[Parameter]
[Category(CategoryTypes.Popover.Behavior)]
public bool Fixed { get; set; }
///
/// Sets the length of time that the opening transition takes to complete.
///
[Parameter]
[Category(CategoryTypes.Popover.Appearance)]
public double Duration { get; set; } = 251;
///
/// Sets the amount of time in milliseconds to wait from opening the popover before beginning to perform the transition.
///
[Parameter]
[Category(CategoryTypes.Popover.Appearance)]
public double Delay { get; set; } = 0;
///
/// Sets the direction the popover will start from relative to its parent.
///
///
[Obsolete("Use AnchorOrigin and TransformOrigin instead.", true)]
[Parameter] public Direction Direction { get; set; } = Direction.Bottom;
///
/// Set the anchor point on the element of the popover.
/// The anchor point will determinate where the popover will be placed.
///
[Parameter]
[Category(CategoryTypes.Popover.Appearance)]
public Origin AnchorOrigin { get; set; } = Origin.TopLeft;
///
/// Sets the intersection point if the anchor element. At this point the popover will lay above the popover.
/// This property in conjunction with AnchorPlacement determinate where the popover will be placed.
///
[Parameter]
[Category(CategoryTypes.Popover.Appearance)]
public Origin TransformOrigin { get; set; } = Origin.TopLeft;
///
/// Set the overflow behavior of a popover and controls how the element should react if there is not enough space for the element to be visible
/// Defaults to none, which doens't apply any overflow logic
///
[Parameter]
[Category(CategoryTypes.Popover.Appearance)]
public OverflowBehavior OverflowBehavior { get; set; } = OverflowBehavior.FlipOnOpen;
///
/// If true, the select menu will open either above or bellow the input depending on the direction.
///
[ExcludeFromCodeCoverage]
[Obsolete("Use AnchorOrigin and TransformOrigin instead.", true)]
[Parameter] public bool OffsetX { get; set; }
///
/// If true, the select menu will open either before or after the input depending on the direction.
///
[ExcludeFromCodeCoverage]
[Obsolete("Use AnchorOrigin and TransformOrigin instead.", true)]
[Parameter] public bool OffsetY { get; set; }
///
/// If true, the popover will have the same width at its parent element, default to false
///
[Parameter]
[Category(CategoryTypes.Popover.Appearance)]
public bool RelativeWidth { get; set; } = false;
///
/// Child content of the component.
///
[Parameter]
[Category(CategoryTypes.Popover.Behavior)]
public RenderFragment ChildContent { get; set; }
private PopoverHandler _handler;
protected override void OnInitialized()
{
_handler = Service.Register(ChildContent ?? new RenderFragment((x) => { }));
_handler.SetComponentBaseParameters(this, PopoverClass, PopoverStyles, Open);
base.OnInitialized();
}
protected override void OnParametersSet()
{
base.OnParametersSet();
// henon: this change by PR #3776 caused problems on BSS (#4303)
//// Only update the fragment if the popover is currently shown or will show
//// This prevents unnecessary renders and popover handle locking
//if (!_handler.ShowContent && !Open)
// return;
_handler.UpdateFragment(ChildContent, this, PopoverClass, PopoverStyles, Open);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender == true)
{
await _handler.Initialize();
await Service.InitializeIfNeeded();
}
await base.OnAfterRenderAsync(firstRender);
}
[ExcludeFromCodeCoverage]
public async ValueTask DisposeAsync()
{
try
{
await Service.Unregister(_handler);
}
catch (JSDisconnectedException) { }
catch (TaskCanceledException) { }
}
}