using System.Globalization; using Connected.Utilities; using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; namespace Connected.Components; public partial class Collapse : UIComponent { internal enum CollapseState { Entering, Entered, Exiting, Exited } internal double _height; private bool _expanded, _isRendered, _updateHeight; private ElementReference _wrapper; internal CollapseState _state = CollapseState.Exited; protected string Stylename => new StyleBuilder() .AddStyle("max-height", MaxHeight.ToPx(), MaxHeight != null) .AddStyle("height", "auto", _state == CollapseState.Entered) .AddStyle("height", _height.ToPx(), _state is CollapseState.Entering or CollapseState.Exiting) .AddStyle("animation-duration", $"{CalculatedAnimationDuration.ToString("#.##", CultureInfo.InvariantCulture)}s", _state == CollapseState.Entering) .AddStyle(Style) .Build(); protected string Classname => new CssBuilder("mud-collapse-container") .AddClass($"mud-collapse-entering", _state == CollapseState.Entering) .AddClass($"mud-collapse-entered", _state == CollapseState.Entered) .AddClass($"mud-collapse-exiting", _state == CollapseState.Exiting) .AddClass(Class) .Build(); /// /// If true, expands the panel, otherwise collapse it. Setting this prop enables control over the panel. /// [Parameter] public bool Expanded { get => _expanded; set { if (_expanded == value) return; _expanded = value; if (_isRendered) { _state = _expanded ? CollapseState.Entering : CollapseState.Exiting; _ = UpdateHeight(); _updateHeight = true; } else if (_expanded) { _state = CollapseState.Entered; } _ = ExpandedChanged.InvokeAsync(_expanded); } } /// /// Explicitly sets the height for the Collapse element to override the css default. /// [Parameter] public int? MaxHeight { get; set; } /// /// Child content of component. /// [Parameter] public RenderFragment ChildContent { get; set; } [Parameter] public EventCallback OnAnimationEnd { get; set; } [Parameter] public EventCallback ExpandedChanged { get; set; } /// /// Modified Animation duration that scales with height parameter. /// Basic implementation for now but should be a math formula to allow it to scale between 0.1s and 1s for the effect to be consistently smooth. /// private double CalculatedAnimationDuration { get { if (MaxHeight != null) { if (MaxHeight <= 200) return 0.2; else if (MaxHeight <= 600) return 0.4; else if (MaxHeight <= 1400) return 0.6; return 1; } return Math.Min(_height / 800.0, 1); } set { } } internal async Task UpdateHeight() { try { _height = (await _wrapper.MudGetBoundingClientRectAsync())?.Height ?? 0; } catch (Exception ex) when (ex is JSDisconnectedException || ex is TaskCanceledException) { _height = 0; } if (MaxHeight != null && _height > MaxHeight) { _height = MaxHeight.Value; } } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { _isRendered = true; await UpdateHeight(); } else if (_updateHeight && _state is CollapseState.Entering or CollapseState.Exiting) { _updateHeight = false; await UpdateHeight(); StateHasChanged(); } await base.OnAfterRenderAsync(firstRender); } public void AnimationEnd() { if (_state == CollapseState.Entering) { _state = CollapseState.Entered; StateHasChanged(); } else if (_state == CollapseState.Exiting) { _state = CollapseState.Exited; StateHasChanged(); } OnAnimationEnd.InvokeAsync(Expanded); } }