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) { } } }