From 8c61578623cde42dd48e53c38fc716ea03cf1808 Mon Sep 17 00:00:00 2001 From: stm Date: Tue, 10 Jan 2023 13:03:51 +0100 Subject: [PATCH] Changes: Drawer, Tabs, Layout --- .../Components/Alert/Alert.razor.cs | 1 + .../Components/Drawer/Drawer.razor.cs | 135 +- .../Components/Drawer/DrawerHeader.razor.cs | 8 +- .../Components/Layout/Layout.razor.cs | 7 - .../Components/Tabs/DynamicTabs.razor.cs | 51 +- .../Components/Tabs/TabPanel.razor | 124 +- .../Components/Tabs/TabPanel.razor.cs | 111 ++ .../Components/Tabs/Tabs.razor | 4 - .../Components/Tabs/Tabs.razor.cs | 1370 ++++++++--------- .../Components/TextField/TextField.razor.cs | 3 - 10 files changed, 857 insertions(+), 957 deletions(-) create mode 100644 src/Connected.Components/Components/Tabs/TabPanel.razor.cs diff --git a/src/Connected.Components/Components/Alert/Alert.razor.cs b/src/Connected.Components/Components/Alert/Alert.razor.cs index fd1703e..6c77eee 100644 --- a/src/Connected.Components/Components/Alert/Alert.razor.cs +++ b/src/Connected.Components/Components/Alert/Alert.razor.cs @@ -37,6 +37,7 @@ public partial class Alert : UIComponent{ [Parameter] public EventCallback CloseClicked { get; set; } #endregion + #region Content placeholders /// /// Define the glyph used for the close button. diff --git a/src/Connected.Components/Components/Drawer/Drawer.razor.cs b/src/Connected.Components/Components/Drawer/Drawer.razor.cs index 031b508..6f8b54a 100644 --- a/src/Connected.Components/Components/Drawer/Drawer.razor.cs +++ b/src/Connected.Components/Components/Drawer/Drawer.razor.cs @@ -1,9 +1,7 @@ -using Connected.Annotations; -using Connected.Extensions; +using Connected.Extensions; using Connected.Services; using Connected.Utilities; using Microsoft.AspNetCore.Components; -using System.ComponentModel; namespace Connected.Components; @@ -101,7 +99,6 @@ public partial class Drawer : UIComponent, IDisposable, INavigationEventReceiver return Anchor.ToDescription(); } - private bool closeOnMouseLeave = false; public async void OnMouseEnter() @@ -122,69 +119,6 @@ public partial class Drawer : UIComponent, IDisposable, INavigationEventReceiver } } - protected override void OnInitialized() - { - if (Variant != DrawerVariant.Temporary) - { - DrawerContainer?.Add(this); - } - base.OnInitialized(); - } - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { - //await UpdateHeight(); - var result = await Breakpointistener.Subscribe(UpdateBreakpointState); - //var currentBreakpoint = result.Breakpoint; - - _breakpointListenerSubscriptionId = result.SubscriptionId; - - var refresh = false; - - if (_screenBreakpoint != result.Breakpoint) refresh = true; - _screenBreakpoint = result.Breakpoint; - if (_screenBreakpoint < Breakpoint && _open) - { - _keepInitialState = true; - await OpenChanged.InvokeAsync(false); - } - - _isRendered = true; - if (Anchor == Anchor.Bottom || Anchor == Anchor.Top || _screenBreakpoint == Breakpoint.None) - { - refresh = true; - } - if (refresh) StateHasChanged(); - } - - await base.OnAfterRenderAsync(firstRender); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - int _disposeCount; - public virtual void Dispose(bool disposing) - { - if (Interlocked.Increment(ref _disposeCount) == 1) - { - if (disposing) - { - DrawerContainer?.Remove(this); - - if (_breakpointListenerSubscriptionId != default) - { - Breakpointistener.Unsubscribe(_breakpointListenerSubscriptionId).AndForget(); - } - } - } - } - #endregion #region Content placeholders @@ -427,4 +361,71 @@ public partial class Drawer : UIComponent, IDisposable, INavigationEventReceiver #endregion + #region Lifecycle + + protected override void OnInitialized() + { + if (Variant != DrawerVariant.Temporary) + { + DrawerContainer?.Add(this); + } + base.OnInitialized(); + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + //await UpdateHeight(); + var result = await Breakpointistener.Subscribe(UpdateBreakpointState); + //var currentBreakpoint = result.Breakpoint; + + _breakpointListenerSubscriptionId = result.SubscriptionId; + + var refresh = false; + + if (_screenBreakpoint != result.Breakpoint) refresh = true; + _screenBreakpoint = result.Breakpoint; + if (_screenBreakpoint < Breakpoint && _open) + { + _keepInitialState = true; + await OpenChanged.InvokeAsync(false); + } + + _isRendered = true; + if (Anchor == Anchor.Bottom || Anchor == Anchor.Top || _screenBreakpoint == Breakpoint.None) + { + refresh = true; + } + if (refresh) StateHasChanged(); + } + + await base.OnAfterRenderAsync(firstRender); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + int _disposeCount; + public virtual void Dispose(bool disposing) + { + if (Interlocked.Increment(ref _disposeCount) == 1) + { + if (disposing) + { + DrawerContainer?.Remove(this); + + if (_breakpointListenerSubscriptionId != default) + { + Breakpointistener.Unsubscribe(_breakpointListenerSubscriptionId).AndForget(); + } + } + } + } + + #endregion + } diff --git a/src/Connected.Components/Components/Drawer/DrawerHeader.razor.cs b/src/Connected.Components/Components/Drawer/DrawerHeader.razor.cs index 58216f5..32ae6a3 100644 --- a/src/Connected.Components/Components/Drawer/DrawerHeader.razor.cs +++ b/src/Connected.Components/Components/Drawer/DrawerHeader.razor.cs @@ -43,8 +43,6 @@ public partial class DrawerHeader : UIComponent [Parameter] public string? StyleList { get; set; } - #endregion - protected virtual CssBuilder CompiledClassList { get @@ -54,5 +52,9 @@ public partial class DrawerHeader : UIComponent .AddClass(ClassList); } } - + + #endregion + + + } diff --git a/src/Connected.Components/Components/Layout/Layout.razor.cs b/src/Connected.Components/Components/Layout/Layout.razor.cs index 917832b..b2bc3e5 100644 --- a/src/Connected.Components/Components/Layout/Layout.razor.cs +++ b/src/Connected.Components/Components/Layout/Layout.razor.cs @@ -1,5 +1,4 @@ using Connected.Utilities; -using System.Net; namespace Connected.Components; @@ -20,12 +19,6 @@ public partial class Layout : DrawerContainer { return new StyleBuilder() .AddStyle(base.StyleList); - //.AddStyle("width", Width, !string.IsNullOrWhiteSpace(Width) && !Fixed) - //.AddStyle("--mud-drawer-width", Width, !string.IsNullOrWhiteSpace(Width) && (!Fixed || Variant == DrawerVariant.Temporary)) - //.AddStyle("height", Height, !string.IsNullOrWhiteSpace(Height)) - //.AddStyle("--mud-drawer-content-height",string.IsNullOrWhiteSpace(Height) ? _height.ToPx() : Height,Anchor == Anchor.Bottom || Anchor == Anchor.Top) - //.AddStyle("visibility", "hidden", string.IsNullOrWhiteSpace(Height) && _height == 0 && (Anchor == Anchor.Bottom || Anchor == Anchor.Top)); - } } diff --git a/src/Connected.Components/Components/Tabs/DynamicTabs.razor.cs b/src/Connected.Components/Components/Tabs/DynamicTabs.razor.cs index e920afc..cdad9a5 100644 --- a/src/Connected.Components/Components/Tabs/DynamicTabs.razor.cs +++ b/src/Connected.Components/Components/Tabs/DynamicTabs.razor.cs @@ -1,23 +1,11 @@ -using Connected.Annotations; -using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components; namespace Connected.Components; public partial class DynamicTabs : Tabs { - /// - /// The icon used for the add button - /// - [Parameter] - [Category(CategoryTypes.Tabs.Appearance)] - public string AddTabIcon { get; set; } = Icons.Material.Filled.Add; - /// - /// the icon used of the close button - /// - [Parameter] - [Category(CategoryTypes.Tabs.Appearance)] - public string CloseTabIcon { get; set; } = Icons.Material.Filled.Close; + #region Event callbacks /// /// The callback, when the add button has been clicked @@ -29,47 +17,68 @@ public partial class DynamicTabs : Tabs /// [Parameter] public EventCallback CloseTab { get; set; } + #endregion + + #region Content placeholders + #endregion + + #region Styling properties + + protected override string InternalClassName { get; } = "dynamic-tabs"; + + /// + /// The icon used for the add button + /// + [Parameter] + public string AddTabIcon { get; set; } = Icons.Material.Filled.Add; + + /// + /// the icon used of the close button + /// + [Parameter] + public string CloseTabIcon { get; set; } = Icons.Material.Filled.Close; + /// /// Classes that are applied to the icon button of the add button /// [Parameter] - [Category(CategoryTypes.Tabs.Appearance)] public string AddIconClass { get; set; } = string.Empty; /// /// Styles that are applied to the icon button of the add button /// [Parameter] - [Category(CategoryTypes.Tabs.Appearance)] public string AddIconStyle { get; set; } = string.Empty; /// /// Classes that are applied to the icon button of the close button /// [Parameter] - [Category(CategoryTypes.Tabs.Appearance)] public string CloseIconClass { get; set; } = string.Empty; /// /// Styles that are applied to the icon button of the close button /// [Parameter] - [Category(CategoryTypes.Tabs.Appearance)] public string CloseIconStyle { get; set; } = string.Empty; /// /// Tooltip that shown when a user hovers of the add button. Empty or null, if no tooltip should be visible /// [Parameter] - [Category(CategoryTypes.Tabs.Behavior)] public string AddIconToolTip { get; set; } = string.Empty; /// /// Tooltip that shown when a user hovers of the close button. Empty or null, if no tooltip should be visible /// [Parameter] - [Category(CategoryTypes.Tabs.Behavior)] public string CloseIconToolTip { get; set; } = string.Empty; - protected override string InternalClassName { get; } = "dynamic-tabs"; + + #endregion + + #region Lifecycle + #endregion + + } diff --git a/src/Connected.Components/Components/Tabs/TabPanel.razor b/src/Connected.Components/Components/Tabs/TabPanel.razor index 6451225..a4a8392 100644 --- a/src/Connected.Components/Components/Tabs/TabPanel.razor +++ b/src/Connected.Components/Components/Tabs/TabPanel.razor @@ -1,6 +1,6 @@ @namespace Connected.Components -@using Connected.Annotations; @inherits UIComponent + @implements IAsyncDisposable @if (Parent?.KeepPanelsAlive == true) @@ -16,125 +16,3 @@ else @ChildContent } } - -@code { - - private Boolean _disposed; - - [CascadingParameter] private Tabs Parent { get; set; } - - public ElementReference PanelRef; - - - /// - /// Text will be displayed in the TabPanel as TabTitle. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Behavior)] - public string Text { get; set; } - - /// - /// Glyph placed before the text if set. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Behavior)] - public string Icon { get; set; } - - /// - /// If true, the tabpanel will be disabled. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Behavior)] - public bool Disabled { get; set; } - - /// - /// Optional information to be showed into a badge - /// - [Parameter] - [Category(CategoryTypes.Tabs.Behavior)] - public object BadgeData { get; set; } - - /// - /// Optional information to show the badge as a dot. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Behavior)] - public bool BadgeDot { get; set; } - - /// - /// The color of the badge. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Appearance)] - public ThemeColor BadgeColor { get; set; } = ThemeColor.Primary; - - /// - /// Unique TabPanel ID. Useful for activation when Panels are dynamically generated. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Behavior)] - public object ID { get; set; } - - /// - /// Raised when tab is clicked - /// - [Parameter] public EventCallback OnClick { get; set; } - - /// - /// Child content of component. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Behavior)] - public RenderFragment ChildContent { get; set; } - - /// - /// Tab content of component. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Behavior)] - public RenderFragment TabContent { get; set; } - - /// - /// Tab content wrapper of component. It is used to wrap the content of a tab heading in a user supplied div or component. - /// Use @context in the TabWrapperContent to render the tab header within your custom wrapper. - /// This is most useful with tooltips, which must wrap the entire content they refer to. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Behavior)] - public RenderFragment TabWrapperContent { get; set; } - - /// - /// TabPanel Tooltip. It will be ignored if TabContent is provided. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Behavior)] - public string ToolTip { get; set; } - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - await base.OnAfterRenderAsync(firstRender); - if(firstRender == true && Parent != null) - { - await Parent.SetPanelRef(PanelRef); - } - } - - protected override void OnInitialized() - { - // NOTE: we must not throw here because we need the component to be able to live for the API docs to be able to infer default values - //if (Parent == null) - // throw new ArgumentNullException(nameof(Parent), "TabPanel must exist within a Tabs component"); - base.OnInitialized(); - - Parent?.AddPanel(this); - } - - public async ValueTask DisposeAsync() - { - if(_disposed == true) { return; } - - _disposed = true; - await Parent?.RemovePanel(this); - - } -} diff --git a/src/Connected.Components/Components/Tabs/TabPanel.razor.cs b/src/Connected.Components/Components/Tabs/TabPanel.razor.cs new file mode 100644 index 0000000..8b1f854 --- /dev/null +++ b/src/Connected.Components/Components/Tabs/TabPanel.razor.cs @@ -0,0 +1,111 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; + +namespace Connected.Components; +public partial class TabPanel : UIComponent +{ + #region Event callbacks + + /// + /// Raised when tab is clicked + /// + [Parameter] public EventCallback OnClick { get; set; } + + #endregion + + #region Content placeholders + + /// + /// Child content of component. + /// + [Parameter] + public RenderFragment ChildContent { get; set; } + + /// + /// Tab content of component. + /// + [Parameter] + public RenderFragment TabContent { get; set; } + + /// + /// Tab content wrapper of component. It is used to wrap the content of a tab heading in a user supplied div or component. + /// Use @context in the TabWrapperContent to render the tab header within your custom wrapper. + /// This is most useful with tooltips, which must wrap the entire content they refer to. + /// + [Parameter] + public RenderFragment TabWrapperContent { get; set; } + + /// + /// TabPanel Tooltip. It will be ignored if TabContent is provided. + /// + [Parameter] + public string ToolTip { get; set; } + + private bool _disposed; + + [CascadingParameter] private Tabs Parent { get; set; } + + public ElementReference PanelRef; + + #endregion + + #region Styling properties + + /// + /// Text will be displayed in the TabPanel as TabTitle. + /// + [Parameter] + public string Text { get; set; } + + /// + /// Glyph placed before the text if set. + /// + [Parameter] + public string Icon { get; set; } + + /// + /// If true, the tabpanel will be disabled. + /// + [Parameter] + public bool Disabled { get; set; } + + /// + /// Unique TabPanel ID. Useful for activation when Panels are dynamically generated. + /// + [Parameter] + public object ID { get; set; } + #endregion + + #region Lifecycle + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender); + if (firstRender == true && Parent != null) + { + await Parent.SetPanelRef(PanelRef); + } + } + + protected override void OnInitialized() + { + // NOTE: we must not throw here because we need the component to be able to live for the API docs to be able to infer default values + //if (Parent == null) + // throw new ArgumentNullException(nameof(Parent), "TabPanel must exist within a Tabs component"); + base.OnInitialized(); + + Parent?.AddPanel(this); + } + + public async ValueTask DisposeAsync() + { + if (_disposed == true) { return; } + + _disposed = true; + await Parent?.RemovePanel(this); + + + #endregion + + } +} diff --git a/src/Connected.Components/Components/Tabs/Tabs.razor b/src/Connected.Components/Components/Tabs/Tabs.razor index cbeeb36..34764b3 100644 --- a/src/Connected.Components/Components/Tabs/Tabs.razor +++ b/src/Connected.Components/Components/Tabs/Tabs.razor @@ -98,10 +98,6 @@ @((MarkupString)panel.Text) } - @if (panel.BadgeData != null || panel.BadgeDot) - { - - } @if (TabPanelHeaderPosition == TabHeaderPosition.After && TabPanelHeader != null) {
diff --git a/src/Connected.Components/Components/Tabs/Tabs.razor.cs b/src/Connected.Components/Components/Tabs/Tabs.razor.cs index c4c7c82..3bdc6e6 100644 --- a/src/Connected.Components/Components/Tabs/Tabs.razor.cs +++ b/src/Connected.Components/Components/Tabs/Tabs.razor.cs @@ -1,5 +1,4 @@ using System.Globalization; -using Connected.Annotations; using Connected.Extensions; using Connected.Interop; using Connected.Services; @@ -11,733 +10,646 @@ namespace Connected.Components; public partial class Tabs : UIComponent, IAsyncDisposable { - private bool _isDisposed; - - private int _activePanelIndex = 0; - private int _scrollIndex = 0; - - private bool _isRendered = false; - private bool _prevButtonDisabled; - private bool _nextButtonDisabled; - private bool _showScrollButtons; - private bool _disableSliderAnimation; - private ElementReference _tabsContentSize; - private double _size; - private double _position; - private double _toolbarContentSize; - private double _allTabsSize; - private double _scrollPosition; - - private IResizeObserver _resizeObserver; - - [CascadingParameter(Name = "RightToLeft")] public bool RightToLeft { get; set; } - - [Inject] private IResizeObserverFactory _resizeObserverFactory { get; set; } - - /// - /// If true, render all tabs and hide (display:none) every non-active. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Behavior)] - public bool KeepPanelsAlive { get; set; } = false; - - /// - /// If true, sets the border-radius to theme default. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Appearance)] - public bool Rounded { get; set; } - - /// - /// If true, sets a border between the content and the toolbar depending on the position. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Appearance)] - public bool Border { get; set; } - - /// - /// If true, toolbar will be outlined. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Appearance)] - public bool Outlined { get; set; } - - /// - /// If true, centers the tabitems. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Appearance)] - public bool Centered { get; set; } - - /// - /// Hides the active tab slider. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Appearance)] - public bool HideSlider { get; set; } - - /// - /// Glyph to use for left pagination. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Appearance)] - public string PrevIcon { get; set; } = Icons.Filled.ChevronLeft; - - /// - /// Glyph to use for right pagination. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Appearance)] - public string NextIcon { get; set; } = Icons.Filled.ChevronRight; - - /// - /// If true, always display the scroll buttons even if the tabs are smaller than the required with, buttons will be disabled if there is nothing to scroll. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Appearance)] - public bool AlwaysShowScrollButtons { get; set; } - - /// - /// Sets the maxheight the component can have. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Appearance)] - public int? MaxHeight { get; set; } = null; - - /// - /// Sets the position of the tabs itself. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Behavior)] - public Position Position { get; set; } = Position.Top; - - /// - /// The color of the component. It supports the theme colors. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Appearance)] - public ThemeColor Color { get; set; } = ThemeColor.Default; - - /// - /// The color of the tab slider. It supports the theme colors. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Appearance)] - public ThemeColor SliderColor { get; set; } = ThemeColor.Inherit; - - /// - /// The color of the icon. It supports the theme colors. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Appearance)] - public ThemeColor IconColor { get; set; } = ThemeColor.Inherit; - - /// - /// The color of the next/prev icons. It supports the theme colors. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Appearance)] - public ThemeColor ScrollIconColor { get; set; } = ThemeColor.Inherit; - - /// - /// The higher the number, the heavier the drop-shadow, applies around the whole component. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Appearance)] - public int Elevation { set; get; } = 0; - - /// - /// If true, will apply elevation, rounded, outlined effects to the whole tab component instead of just toolbar. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Appearance)] - public bool ApplyEffectsToContainer { get; set; } - - /// - /// If true, disables ripple effect. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Appearance)] - public bool DisableRipple { get; set; } - - /// - /// If true, disables slider animation - /// - [Parameter] - [Category(CategoryTypes.Tabs.Appearance)] - public bool DisableSliderAnimation { get => _disableSliderAnimation; set => _disableSliderAnimation = value; } - - /// - /// Child content of component. - /// - [Parameter] - [Category(CategoryTypes.Tabs.Behavior)] - public RenderFragment ChildContent { get; set; } - - /// - /// This fragment is placed between toolbar and panels. - /// It can be used to display additional content like an address line in a browser. - /// The active tab will be the content of this RenderFragement - /// - [Parameter] - [Category(CategoryTypes.Tabs.Behavior)] - public RenderFragment PrePanelContent { get; set; } - - /// - /// Custom class/classes for TabPanel - /// - [Parameter] - [Category(CategoryTypes.Tabs.Appearance)] - public string TabPanelClass { get; set; } - - /// - /// Custom class/classes for Selected Content Panel - /// - [Parameter] - [Category(CategoryTypes.Tabs.Appearance)] - public string PanelClass { get; set; } - - public TabPanel ActivePanel { get; private set; } - - /// - /// The current active panel index. Also with Bidirectional Binding - /// - [Parameter] - [Category(CategoryTypes.Tabs.Behavior)] - public int ActivePanelIndex - { - get => _activePanelIndex; - set - { - if (_activePanelIndex != value) - { - _activePanelIndex = value; - if (_isRendered) - ActivePanel = _panels[_activePanelIndex]; - ActivePanelIndexChanged.InvokeAsync(value); - } - } - } - - /// - /// Fired when ActivePanelIndex changes. - /// - [Parameter] - public EventCallback ActivePanelIndexChanged { get; set; } - - /// - /// A readonly list of the current panels. Panels should be added or removed through the RenderTree use this collection to get informations about the current panels - /// - public IReadOnlyList Panels { get; private set; } - - private List _panels; - - /// - /// A render fragment that is added before or after (based on the value of HeaderPosition) the tabs inside the header panel of the tab control - /// - [Parameter] - [Category(CategoryTypes.Tabs.Behavior)] - public RenderFragment Header { get; set; } - - /// - /// Additional content specified by Header is placed either before the tabs, after or not at all - /// - [Parameter] - [Category(CategoryTypes.Tabs.Behavior)] - public TabHeaderPosition HeaderPosition { get; set; } = TabHeaderPosition.After; - - /// - /// A render fragment that is added before or after (based on the value of HeaderPosition) inside each tab panel - /// - [Parameter] - [Category(CategoryTypes.Tabs.Behavior)] - public RenderFragment TabPanelHeader { get; set; } - - /// - /// Additional content specified by Header is placed either before the tabs, after or not at all - /// - [Parameter] - [Category(CategoryTypes.Tabs.Behavior)] - public TabHeaderPosition TabPanelHeaderPosition { get; set; } = TabHeaderPosition.After; - - /// - /// Can be used in derived class to add a class to the main container. If not overwritten return an empty string - /// - protected virtual string InternalClassName { get; } = string.Empty; - - private string _prevIcon; - - private string _nextIcon; - - #region Life cycle management - - public Tabs() - { - _panels = new List(); - Panels = _panels.AsReadOnly(); - } - - protected override void OnInitialized() - { - _resizeObserver = _resizeObserverFactory.Create(); - base.OnInitialized(); - } - - protected override void OnParametersSet() - { - if (_resizeObserver == null) - { - _resizeObserver = _resizeObserverFactory.Create(); - } - - Rerender(); - } - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { - var items = _panels.Select(x => x.PanelRef).ToList(); - items.Add(_tabsContentSize); - - if (_panels.Count > 0) - ActivePanel = _panels[_activePanelIndex]; - - await _resizeObserver.Observe(items); - - _resizeObserver.OnResized += OnResized; - - Rerender(); - StateHasChanged(); - - _isRendered = true; - } - } - - public async ValueTask DisposeAsync() - { - if (_isDisposed == true) - return; - _isDisposed = true; - _resizeObserver.OnResized -= OnResized; - await _resizeObserver.DisposeAsync(); - } - - #endregion - - #region Children - - internal void AddPanel(TabPanel tabPanel) - { - _panels.Add(tabPanel); - if (_panels.Count == 1) - ActivePanel = tabPanel; - - StateHasChanged(); - } - - internal async Task SetPanelRef(ElementReference reference) - { - if (_isRendered == true && _resizeObserver.IsElementObserved(reference) == false) - { - await _resizeObserver.Observe(reference); - Rerender(); - StateHasChanged(); - } - } - - internal async Task RemovePanel(TabPanel tabPanel) - { - if (_isDisposed) - return; - - var index = _panels.IndexOf(tabPanel); - var newIndex = index; - if (ActivePanelIndex == index && index == _panels.Count - 1) - { - newIndex = index > 0 ? index - 1 : 0; - if (_panels.Count == 1) - { - ActivePanel = null; - } - } - else if (_activePanelIndex > index) - { - _activePanelIndex--; - await ActivePanelIndexChanged.InvokeAsync(_activePanelIndex); - } - - if (index != newIndex) - { - ActivePanelIndex = newIndex; - } - - _panels.Remove(tabPanel); - await _resizeObserver.Unobserve(tabPanel.PanelRef); - Rerender(); - StateHasChanged(); - } - - public void ActivatePanel(TabPanel panel, bool ignoreDisabledState = false) - { - ActivatePanel(panel, null, ignoreDisabledState); - } - - public void ActivatePanel(int index, bool ignoreDisabledState = false) - { - var panel = _panels[index]; - ActivatePanel(panel, null, ignoreDisabledState); - } - - public void ActivatePanel(object id, bool ignoreDisabledState = false) - { - var panel = _panels.Where((p) => Equals(p.ID, id)).FirstOrDefault(); - if (panel != null) - ActivatePanel(panel, null, ignoreDisabledState); - } - - private void ActivatePanel(TabPanel panel, MouseEventArgs ev, bool ignoreDisabledState = false) - { - if (!panel.Disabled || ignoreDisabledState) - { - ActivePanelIndex = _panels.IndexOf(panel); - - if (ev != null) - ActivePanel.OnClick.InvokeAsync(ev); - - CenterScrollPositionAroundSelectedItem(); - SetSliderState(); - SetScrollabilityStates(); - StateHasChanged(); - } - } - - #endregion - - #region Style and classes - protected string TabsClassnames => - new CssBuilder("tabs") - .AddClass($"tabs-rounded", ApplyEffectsToContainer && Rounded) - .AddClass($"paper-outlined", ApplyEffectsToContainer && Outlined) - .AddClass($"elevation-{Elevation}", ApplyEffectsToContainer && Elevation != 0) - .AddClass($"tabs-reverse", Position == Position.Bottom) - .AddClass($"tabs-vertical", IsVerticalTabs()) - .AddClass($"tabs-vertical-reverse", Position == Position.Right && !RightToLeft || (Position == Position.Left) && RightToLeft) - .AddClass(InternalClassName) - .Build(); - - protected string ToolbarClassnames => - new CssBuilder("tabs-toolbar") - .AddClass($"tabs-rounded", !ApplyEffectsToContainer && Rounded) - .AddClass($"tabs-vertical", IsVerticalTabs()) - .AddClass($"tabs-toolbar-{Color.ToDescription()}", Color != ThemeColor.Default) - .AddClass($"tabs-border-{ConvertPosition(Position).ToDescription()}", Border) - .AddClass($"paper-outlined", !ApplyEffectsToContainer && Outlined) - .AddClass($"elevation-{Elevation}", !ApplyEffectsToContainer && Elevation != 0) - .Build(); - - protected string WrapperClassnames => - new CssBuilder("tabs-toolbar-wrapper") - .AddClass($"tabs-centered", Centered) - .AddClass($"tabs-vertical", IsVerticalTabs()) - .Build(); - - protected string WrapperScrollStyle => - new StyleBuilder() - .AddStyle("transform", $"translateX({(-1 * _scrollPosition).ToString(CultureInfo.InvariantCulture)}px)", Position is Position.Top or Position.Bottom) - .AddStyle("transform", $"translateY({(-1 * _scrollPosition).ToString(CultureInfo.InvariantCulture)}px)", IsVerticalTabs()) - .Build(); - - protected string PanelsClassnames => - new CssBuilder("tabs-panels") - .AddClass($"tabs-vertical", IsVerticalTabs()) - .AddClass(PanelClass) - .Build(); - - protected string SliderClass => - new CssBuilder("tab-slider") - .AddClass($"{SliderColor.ToDescription()}", SliderColor != ThemeColor.Inherit) - .AddClass($"tab-slider-horizontal", Position is Position.Top or Position.Bottom) - .AddClass($"tab-slider-vertical", IsVerticalTabs()) - .AddClass($"tab-slider-horizontal-reverse", Position == Position.Bottom) - .AddClass($"tab-slider-vertical-reverse", Position == Position.Right || Position == Position.Start && RightToLeft || Position == Position.End && !RightToLeft) - .Build(); - - protected string MaxHeightStyles => - new StyleBuilder() - .AddStyle("max-height", MaxHeight.ToPx(), MaxHeight != null) - .Build(); - - protected string SliderStyle => RightToLeft ? - new StyleBuilder() - .AddStyle("width", _size.ToPx(), Position is Position.Top or Position.Bottom) - .AddStyle("right", _position.ToPx(), Position is Position.Top or Position.Bottom) - .AddStyle("transition", _disableSliderAnimation ? "none" : "right .3s cubic-bezier(.64,.09,.08,1);", Position is Position.Top or Position.Bottom) - .AddStyle("transition", _disableSliderAnimation ? "none" : "top .3s cubic-bezier(.64,.09,.08,1);", IsVerticalTabs()) - .AddStyle("height", _size.ToPx(), IsVerticalTabs()) - .AddStyle("top", _position.ToPx(), IsVerticalTabs()) - .Build() : new StyleBuilder() - .AddStyle("width", _size.ToPx(), Position is Position.Top or Position.Bottom) - .AddStyle("left", _position.ToPx(), Position is Position.Top or Position.Bottom) - .AddStyle("transition", _disableSliderAnimation ? "none" : "left .3s cubic-bezier(.64,.09,.08,1);", Position is Position.Top or Position.Bottom) - .AddStyle("transition", _disableSliderAnimation ? "none" : "top .3s cubic-bezier(.64,.09,.08,1);", IsVerticalTabs()) - .AddStyle("height", _size.ToPx(), IsVerticalTabs()) - .AddStyle("top", _position.ToPx(), IsVerticalTabs()) - .Build(); - - private bool IsVerticalTabs() - { - return Position is Position.Left or Position.Right or Position.Start or Position.End; - } - - private Position ConvertPosition(Position position) - { - return position switch - { + + #region Event callbacks + + /// + /// Fired when ActivePanelIndex changes. + /// + [Parameter] + public EventCallback ActivePanelIndexChanged { get; set; } + + #endregion + + #region Content placeholders + + private bool _isDisposed; + + private int _activePanelIndex = 0; + private int _scrollIndex = 0; + + private bool _isRendered = false; + private bool _prevButtonDisabled; + private bool _nextButtonDisabled; + private bool _showScrollButtons; + private bool _disableSliderAnimation; + private ElementReference _tabsContentSize; + private double _size; + private double _position; + private double _toolbarContentSize; + private double _allTabsSize; + private double _scrollPosition; + + private IResizeObserver _resizeObserver; + + /// + /// Child content of component. + /// + [Parameter] + public RenderFragment ChildContent { get; set; } + + /// + /// A readonly list of the current panels. Panels should be added or removed through the RenderTree use this collection to get informations about the current panels + /// + public IReadOnlyList Panels { get; private set; } + + private List _panels; + + /// + /// A render fragment that is added before or after (based on the value of HeaderPosition) the tabs inside the header panel of the tab control + /// + [Parameter] + public RenderFragment Header { get; set; } + + /// + /// A render fragment that is added before or after (based on the value of HeaderPosition) inside each tab panel + /// + [Parameter] + public RenderFragment TabPanelHeader { get; set; } + + /// + /// Additional content specified by Header is placed either before the tabs, after or not at all + /// + [Parameter] + public TabHeaderPosition HeaderPosition { get; set; } = TabHeaderPosition.After; + + /// + /// Additional content specified by Header is placed either before the tabs, after or not at all + /// + [Parameter] + public TabHeaderPosition TabPanelHeaderPosition { get; set; } = TabHeaderPosition.After; + + /// + /// Can be used in derived class to add a class to the main container. If not overwritten return an empty string + /// + protected virtual string InternalClassName { get; } = string.Empty; + + private string _prevIcon; + + private string _nextIcon; + + /// + /// This fragment is placed between toolbar and panels. + /// It can be used to display additional content like an address line in a browser. + /// The active tab will be the content of this RenderFragement + /// + [Parameter] + public RenderFragment PrePanelContent { get; set; } + + + + public TabPanel ActivePanel { get; private set; } + + /// + /// The current active panel index. Also with Bidirectional Binding + /// + [Parameter] + public int ActivePanelIndex + { + get => _activePanelIndex; + set + { + if (_activePanelIndex != value) + { + _activePanelIndex = value; + if (_isRendered) + ActivePanel = _panels[_activePanelIndex]; + ActivePanelIndexChanged.InvokeAsync(value); + } + } + } + + [CascadingParameter(Name = "RightToLeft")] public bool RightToLeft { get; set; } + + [Inject] private IResizeObserverFactory _resizeObserverFactory { get; set; } + + internal void AddPanel(TabPanel tabPanel) + { + _panels.Add(tabPanel); + if (_panels.Count == 1) + ActivePanel = tabPanel; + + StateHasChanged(); + } + + internal async Task SetPanelRef(ElementReference reference) + { + if (_isRendered == true && _resizeObserver.IsElementObserved(reference) == false) + { + await _resizeObserver.Observe(reference); + Rerender(); + StateHasChanged(); + } + } + + internal async Task RemovePanel(TabPanel tabPanel) + { + if (_isDisposed) + return; + + var index = _panels.IndexOf(tabPanel); + var newIndex = index; + if (ActivePanelIndex == index && index == _panels.Count - 1) + { + newIndex = index > 0 ? index - 1 : 0; + if (_panels.Count == 1) + { + ActivePanel = null; + } + } + else if (_activePanelIndex > index) + { + _activePanelIndex--; + await ActivePanelIndexChanged.InvokeAsync(_activePanelIndex); + } + + if (index != newIndex) + { + ActivePanelIndex = newIndex; + } + + _panels.Remove(tabPanel); + await _resizeObserver.Unobserve(tabPanel.PanelRef); + Rerender(); + StateHasChanged(); + } + + public void ActivatePanel(TabPanel panel, bool ignoreDisabledState = false) + { + ActivatePanel(panel, null, ignoreDisabledState); + } + + public void ActivatePanel(int index, bool ignoreDisabledState = false) + { + var panel = _panels[index]; + ActivatePanel(panel, null, ignoreDisabledState); + } + + public void ActivatePanel(object id, bool ignoreDisabledState = false) + { + var panel = _panels.Where((p) => Equals(p.ID, id)).FirstOrDefault(); + if (panel != null) + ActivatePanel(panel, null, ignoreDisabledState); + } + + private void ActivatePanel(TabPanel panel, MouseEventArgs ev, bool ignoreDisabledState = false) + { + if (!panel.Disabled || ignoreDisabledState) + { + ActivePanelIndex = _panels.IndexOf(panel); + + if (ev != null) + ActivePanel.OnClick.InvokeAsync(ev); + + CenterScrollPositionAroundSelectedItem(); + SetSliderState(); + SetScrollabilityStates(); + StateHasChanged(); + } + } + + #endregion + + #region Styling properties + + protected string TabsClassnames => + new CssBuilder("tabs") + .AddClass(InternalClassName) + .Build(); + + protected string ToolbarClassnames => + new CssBuilder("tabs-toolbar") + .AddClass($"tabs-vertical", IsVerticalTabs()) + .Build(); + + protected string WrapperClassnames => + new CssBuilder("tabs-toolbar-wrapper") + .AddClass($"tabs-vertical", IsVerticalTabs()) + .Build(); + + protected string WrapperScrollStyle => + new StyleBuilder() + .AddStyle("transform", $"translateY({(-1 * _scrollPosition).ToString(CultureInfo.InvariantCulture)}px)", IsVerticalTabs()) + .Build(); + + protected string PanelsClassnames => + new CssBuilder("tabs-panels") + .AddClass($"tabs-vertical", IsVerticalTabs()) + .AddClass(PanelClass) + .Build(); + + protected string SliderClass => + new CssBuilder("tab-slider") + .AddClass($"tab-slider-vertical", IsVerticalTabs()) + .Build(); + + protected string MaxHeightStyles => + new StyleBuilder() + .Build(); + + protected string SliderStyle => RightToLeft ? + new StyleBuilder() + .AddStyle("transition", _disableSliderAnimation ? "none" : "top .3s cubic-bezier(.64,.09,.08,1);", IsVerticalTabs()) + .AddStyle("height", _size.ToPx(), IsVerticalTabs()) + .AddStyle("top", _position.ToPx(), IsVerticalTabs()) + .Build() : new StyleBuilder() + .AddStyle("transition", _disableSliderAnimation ? "none" : "top .3s cubic-bezier(.64,.09,.08,1);", IsVerticalTabs()) + .AddStyle("height", _size.ToPx(), IsVerticalTabs()) + .AddStyle("top", _position.ToPx(), IsVerticalTabs()) + .Build(); + + private bool IsVerticalTabs() + { + return Position is Position.Left or Position.Right or Position.Start or Position.End; + } + + private Position ConvertPosition(Position position) + { + return position switch + { Position.Start => RightToLeft ? Position.Right : Position.Left, Position.End => RightToLeft ? Position.Left : Position.Right, - _ => position - }; - } - - string GetTabClass(TabPanel panel) - { - var tabClass = new CssBuilder("tab") - .AddClass($"tab-active", when: () => panel == ActivePanel) - .AddClass($"disabled", panel.Disabled) - .AddClass($"ripple", !DisableRipple) - .AddClass(TabPanelClass) - .Build(); - - return tabClass; - } - - private Placement GetTooltipPlacement() - { - if (Position == Position.Right) - return Placement.Left; - else if (Position == Position.Left) - return Placement.Right; - else if (Position == Position.Bottom) - return Placement.Top; - else - return Placement.Bottom; - } - - string GetTabStyle(TabPanel panel) - { - var tabStyle = new StyleBuilder() - .Build(); - - return tabStyle; - } - - #endregion - - #region Rendering and placement - - private void Rerender() - { - _nextIcon = RightToLeft ? PrevIcon : NextIcon; - _prevIcon = RightToLeft ? NextIcon : PrevIcon; - - GetToolbarContentSize(); - GetAllTabsSize(); - SetScrollButtonVisibility(); - SetSliderState(); - SetScrollabilityStates(); - } - - private async void OnResized(IDictionary changes) - { - Rerender(); - await InvokeAsync(StateHasChanged); - } - - private void SetSliderState() - { - if (ActivePanel == null) { return; } - - _position = GetLengthOfPanelItems(ActivePanel); - _size = GetRelevantSize(ActivePanel.PanelRef); - } - - private void GetToolbarContentSize() => _toolbarContentSize = GetRelevantSize(_tabsContentSize); - - private void GetAllTabsSize() - { - double totalTabsSize = 0; - - foreach (var panel in _panels) - { - totalTabsSize += GetRelevantSize(panel.PanelRef); - } - - _allTabsSize = totalTabsSize; - } - - private double GetRelevantSize(ElementReference reference) => Position switch - { - Position.Top or Position.Bottom => _resizeObserver.GetWidth(reference), - _ => _resizeObserver.GetHeight(reference) - }; - - private double GetLengthOfPanelItems(TabPanel panel, bool inclusive = false) - { - var value = 0.0; - foreach (var item in _panels) - { - if (item == panel) - { - if (inclusive) - { - value += GetRelevantSize(item.PanelRef); - } - - break; - } - - value += GetRelevantSize(item.PanelRef); - } - - return value; - } - - private double GetPanelLength(TabPanel panel) => panel == null ? 0.0 : GetRelevantSize(panel.PanelRef); - - #endregion - - #region scrolling - - private void SetScrollButtonVisibility() - { - _showScrollButtons = AlwaysShowScrollButtons || _allTabsSize > _toolbarContentSize; - } - - private void ScrollPrev() - { - _scrollIndex = Math.Max(_scrollIndex - GetVisiblePanels(), 0); - ScrollToItem(_panels[_scrollIndex]); - SetScrollabilityStates(); - } - - private void ScrollNext() - { - _scrollIndex = Math.Min(_scrollIndex + GetVisiblePanels(), _panels.Count - 1); - ScrollToItem(_panels[_scrollIndex]); - SetScrollabilityStates(); - } - - /// - /// Calculates the amount of panels that are completely visible inside the toolbar content area. Panels that are just partially visible are not considered here! - /// - /// The amount of panels visible inside the toolbar area. CAUTION: Might return 0! - private int GetVisiblePanels() - { - var x = 0D; - var count = 0; - - var toolbarContentSize = GetRelevantSize(_tabsContentSize); - - foreach (var panel in _panels) - { - x += GetRelevantSize(panel.PanelRef); - - if (x < toolbarContentSize) - { - count++; - } - else - { - break; - } - } - - return count; - } - - private void ScrollToItem(TabPanel panel) - { - var position = GetLengthOfPanelItems(panel); - _scrollPosition = RightToLeft ? -position : position; - } - - private bool IsAfterLastPanelIndex(int index) => index >= _panels.Count; - private bool IsBeforeFirstPanelIndex(int index) => index < 0; - - private void CenterScrollPositionAroundSelectedItem() - { - TabPanel panelToStart = ActivePanel; - var length = GetPanelLength(panelToStart); - if (length >= _toolbarContentSize) - { - ScrollToItem(panelToStart); - return; - } - - var indexCorrection = 1; - while (true) - { - var panelAfterIndex = _activePanelIndex + indexCorrection; - if (!IsAfterLastPanelIndex(panelAfterIndex)) - { - length += GetPanelLength(_panels[panelAfterIndex]); - } - - if (length >= _toolbarContentSize) - { - _scrollIndex = _panels.IndexOf(panelToStart); - ScrollToItem(panelToStart); - break; - } - - length = _toolbarContentSize - length; - - var panelBeforeindex = _activePanelIndex - indexCorrection; - if (!IsBeforeFirstPanelIndex(panelBeforeindex)) - { - length -= GetPanelLength(_panels[panelBeforeindex]); - } - else - { - break; - } - - if (length < 0) - { - _scrollIndex = _panels.IndexOf(panelToStart); - ScrollToItem(panelToStart); - break; - } - - length = _toolbarContentSize - length; - panelToStart = _panels[_activePanelIndex - indexCorrection]; - - indexCorrection++; - } - - _scrollIndex = _panels.IndexOf(panelToStart); - ScrollToItem(panelToStart); - - SetScrollabilityStates(); - } - - private void SetScrollabilityStates() - { - var isEnoughSpace = _allTabsSize <= _toolbarContentSize; - - if (isEnoughSpace) - { - _nextButtonDisabled = true; - _prevButtonDisabled = true; - } - else - { - // Disable next button if the last panel is completely visible - _nextButtonDisabled = Math.Abs(_scrollPosition) >= GetLengthOfPanelItems(_panels.Last(), true) - _toolbarContentSize; - _prevButtonDisabled = _scrollIndex == 0; - } - } - - #endregion -} + _ => position + }; + } + + string GetTabClass(TabPanel panel) + { + var tabClass = new CssBuilder("tab") + .AddClass($"tab-active", when: () => panel == ActivePanel) + .AddClass($"disabled", panel.Disabled) + .AddClass($"ripple", !DisableRipple) + .AddClass(TabPanelClass) + .Build(); + + return tabClass; + } + + private Placement GetTooltipPlacement() + { + if (Position == Position.Right) + return Placement.Left; + else if (Position == Position.Left) + return Placement.Right; + else if (Position == Position.Bottom) + return Placement.Top; + else + return Placement.Bottom; + } + + string GetTabStyle(TabPanel panel) + { + var tabStyle = new StyleBuilder() + .Build(); + + return tabStyle; + } + + /// + /// Hides the active tab slider. + /// + [Parameter] + public bool HideSlider { get; set; } + + /// + /// The color of the component. It supports the theme colors. + /// + [Parameter] + public ThemeColor Color { get; set; } = ThemeColor.Default; + + /// + /// The color of the tab slider. It supports the theme colors. + /// + [Parameter] + public ThemeColor SliderColor { get; set; } = ThemeColor.Inherit; + + /// + /// The color of the icon. It supports the theme colors. + /// + [Parameter] + public ThemeColor IconColor { get; set; } = ThemeColor.Inherit; + + /// + /// The color of the next/prev icons. It supports the theme colors. + /// + [Parameter] + public ThemeColor ScrollIconColor { get; set; } = ThemeColor.Inherit; + + /// + /// If true, always display the scroll buttons even if the tabs are smaller than the required with, buttons will be disabled if there is nothing to scroll. + /// + [Parameter] + public bool AlwaysShowScrollButtons { get; set; } + + /// + /// Glyph to use for left pagination. + /// + [Parameter] + public string PrevIcon { get; set; } = Icons.Filled.ChevronLeft; + + /// + /// Glyph to use for right pagination. + /// + [Parameter] + public string NextIcon { get; set; } = Icons.Filled.ChevronRight; + + /// + /// Sets the position of the tabs itself. + /// + [Parameter] + public Position Position { get; set; } = Position.Top; + + /// + /// If true, will apply elevation, rounded, outlined effects to the whole tab component instead of just toolbar. + /// + [Parameter] + public bool ApplyEffectsToContainer { get; set; } + + /// + /// If true, disables ripple effect. + /// + [Parameter] + public bool DisableRipple { get; set; } + + /// + /// If true, disables slider animation + /// + [Parameter] + public bool DisableSliderAnimation { get => _disableSliderAnimation; set => _disableSliderAnimation = value; } + + /// + /// Custom class/classes for TabPanel + /// + [Parameter] + public string TabPanelClass { get; set; } + + /// + /// Custom class/classes for Selected Content Panel + /// + [Parameter] + public string PanelClass { get; set; } + + /// + /// If true, render all tabs and hide (display:none) every non-active. + /// + [Parameter] + public bool KeepPanelsAlive { get; set; } = false; + + #endregion + + #region Lifecycle + + public Tabs() + { + _panels = new List(); + Panels = _panels.AsReadOnly(); + } + + protected override void OnInitialized() + { + _resizeObserver = _resizeObserverFactory.Create(); + base.OnInitialized(); + } + + protected override void OnParametersSet() + { + if (_resizeObserver == null) + { + _resizeObserver = _resizeObserverFactory.Create(); + } + + Rerender(); + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + var items = _panels.Select(x => x.PanelRef).ToList(); + items.Add(_tabsContentSize); + + if (_panels.Count > 0) + ActivePanel = _panels[_activePanelIndex]; + + await _resizeObserver.Observe(items); + + _resizeObserver.OnResized += OnResized; + + Rerender(); + StateHasChanged(); + + _isRendered = true; + } + } + + public async ValueTask DisposeAsync() + { + if (_isDisposed == true) + return; + _isDisposed = true; + _resizeObserver.OnResized -= OnResized; + await _resizeObserver.DisposeAsync(); + } + + private void Rerender() + { + _nextIcon = RightToLeft ? PrevIcon : NextIcon; + _prevIcon = RightToLeft ? NextIcon : PrevIcon; + + GetToolbarContentSize(); + GetAllTabsSize(); + SetScrollButtonVisibility(); + SetSliderState(); + SetScrollabilityStates(); + } + + private async void OnResized(IDictionary changes) + { + Rerender(); + await InvokeAsync(StateHasChanged); + } + + private void SetSliderState() + { + if (ActivePanel == null) { return; } + + _position = GetLengthOfPanelItems(ActivePanel); + _size = GetRelevantSize(ActivePanel.PanelRef); + } + + private void GetToolbarContentSize() => _toolbarContentSize = GetRelevantSize(_tabsContentSize); + + private void GetAllTabsSize() + { + double totalTabsSize = 0; + + foreach (var panel in _panels) + { + totalTabsSize += GetRelevantSize(panel.PanelRef); + } + + _allTabsSize = totalTabsSize; + } + + private double GetRelevantSize(ElementReference reference) => Position switch + { + Position.Top or Position.Bottom => _resizeObserver.GetWidth(reference), + _ => _resizeObserver.GetHeight(reference) + }; + + private double GetLengthOfPanelItems(TabPanel panel, bool inclusive = false) + { + var value = 0.0; + foreach (var item in _panels) + { + if (item == panel) + { + if (inclusive) + { + value += GetRelevantSize(item.PanelRef); + } + + break; + } + + value += GetRelevantSize(item.PanelRef); + } + + return value; + } + + private double GetPanelLength(TabPanel panel) => panel == null ? 0.0 : GetRelevantSize(panel.PanelRef); + + private void SetScrollButtonVisibility() + { + _showScrollButtons = AlwaysShowScrollButtons || _allTabsSize > _toolbarContentSize; + } + + private void ScrollPrev() + { + _scrollIndex = Math.Max(_scrollIndex - GetVisiblePanels(), 0); + ScrollToItem(_panels[_scrollIndex]); + SetScrollabilityStates(); + } + + private void ScrollNext() + { + _scrollIndex = Math.Min(_scrollIndex + GetVisiblePanels(), _panels.Count - 1); + ScrollToItem(_panels[_scrollIndex]); + SetScrollabilityStates(); + } + + /// + /// Calculates the amount of panels that are completely visible inside the toolbar content area. Panels that are just partially visible are not considered here! + /// + /// The amount of panels visible inside the toolbar area. CAUTION: Might return 0! + private int GetVisiblePanels() + { + var x = 0D; + var count = 0; + + var toolbarContentSize = GetRelevantSize(_tabsContentSize); + + foreach (var panel in _panels) + { + x += GetRelevantSize(panel.PanelRef); + + if (x < toolbarContentSize) + { + count++; + } + else + { + break; + } + } + + return count; + } + + private void ScrollToItem(TabPanel panel) + { + var position = GetLengthOfPanelItems(panel); + _scrollPosition = RightToLeft ? -position : position; + } + + private bool IsAfterLastPanelIndex(int index) => index >= _panels.Count; + private bool IsBeforeFirstPanelIndex(int index) => index < 0; + + private void CenterScrollPositionAroundSelectedItem() + { + TabPanel panelToStart = ActivePanel; + var length = GetPanelLength(panelToStart); + if (length >= _toolbarContentSize) + { + ScrollToItem(panelToStart); + return; + } + + var indexCorrection = 1; + while (true) + { + var panelAfterIndex = _activePanelIndex + indexCorrection; + if (!IsAfterLastPanelIndex(panelAfterIndex)) + { + length += GetPanelLength(_panels[panelAfterIndex]); + } + + if (length >= _toolbarContentSize) + { + _scrollIndex = _panels.IndexOf(panelToStart); + ScrollToItem(panelToStart); + break; + } + + length = _toolbarContentSize - length; + + var panelBeforeindex = _activePanelIndex - indexCorrection; + if (!IsBeforeFirstPanelIndex(panelBeforeindex)) + { + length -= GetPanelLength(_panels[panelBeforeindex]); + } + else + { + break; + } + + if (length < 0) + { + _scrollIndex = _panels.IndexOf(panelToStart); + ScrollToItem(panelToStart); + break; + } + + length = _toolbarContentSize - length; + panelToStart = _panels[_activePanelIndex - indexCorrection]; + + indexCorrection++; + } + + _scrollIndex = _panels.IndexOf(panelToStart); + ScrollToItem(panelToStart); + + SetScrollabilityStates(); + } + + private void SetScrollabilityStates() + { + var isEnoughSpace = _allTabsSize <= _toolbarContentSize; + + if (isEnoughSpace) + { + _nextButtonDisabled = true; + _prevButtonDisabled = true; + } + else + { + // Disable next button if the last panel is completely visible + _nextButtonDisabled = Math.Abs(_scrollPosition) >= GetLengthOfPanelItems(_panels.Last(), true) - _toolbarContentSize; + _prevButtonDisabled = _scrollIndex == 0; + } + } + + #endregion + +} \ No newline at end of file diff --git a/src/Connected.Components/Components/TextField/TextField.razor.cs b/src/Connected.Components/Components/TextField/TextField.razor.cs index a6fd3a6..97a3be9 100644 --- a/src/Connected.Components/Components/TextField/TextField.razor.cs +++ b/src/Connected.Components/Components/TextField/TextField.razor.cs @@ -15,7 +15,6 @@ public partial class TextField : DebouncedInput /// Type of the input element. It should be a valid HTML5 input type. ///
[Parameter] - [Category(CategoryTypes.FormComponent.Behavior)] public InputType InputType { get; set; } = InputType.Text; internal override InputType GetInputType() => InputType; @@ -26,7 +25,6 @@ public partial class TextField : DebouncedInput /// Show clear button. /// [Parameter] - [Category(CategoryTypes.FormComponent.Behavior)] public bool Clearable { get; set; } = false; /// @@ -117,7 +115,6 @@ public partial class TextField : DebouncedInput /// Note: when Mask is set, TextField will ignore some properties such as Lines, Pattern or HideSpinButtons, OnKeyDown and OnKeyUp, etc. /// [Parameter] - [Category(CategoryTypes.General.Data)] public IMask Mask { get => _maskReference?.MaskKind ?? _mask; // this might look strange, but it is absolutely necessary due to how Mask works.