|
|
|
|
using System.Globalization;
|
|
|
|
|
using Connected.Annotations;
|
|
|
|
|
using Connected.Extensions;
|
|
|
|
|
using Connected.Interop;
|
|
|
|
|
using Connected.Services;
|
|
|
|
|
using Connected.Utilities;
|
|
|
|
|
using Microsoft.AspNetCore.Components;
|
|
|
|
|
using Microsoft.AspNetCore.Components.Web;
|
|
|
|
|
|
|
|
|
|
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; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// If true, render all tabs and hide (display:none) every non-active.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Behavior)]
|
|
|
|
|
public bool KeepPanelsAlive { get; set; } = false;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// If true, sets the border-radius to theme default.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Appearance)]
|
|
|
|
|
public bool Rounded { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// If true, sets a border between the content and the toolbar depending on the position.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Appearance)]
|
|
|
|
|
public bool Border { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// If true, toolbar will be outlined.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Appearance)]
|
|
|
|
|
public bool Outlined { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// If true, centers the tabitems.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Appearance)]
|
|
|
|
|
public bool Centered { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Hides the active tab slider.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Appearance)]
|
|
|
|
|
public bool HideSlider { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Icon to use for left pagination.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Appearance)]
|
|
|
|
|
public string PrevIcon { get; set; } = Icons.Filled.ChevronLeft;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Icon to use for right pagination.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Appearance)]
|
|
|
|
|
public string NextIcon { get; set; } = Icons.Filled.ChevronRight;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 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.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Appearance)]
|
|
|
|
|
public bool AlwaysShowScrollButtons { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Sets the maxheight the component can have.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Appearance)]
|
|
|
|
|
public int? MaxHeight { get; set; } = null;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Sets the position of the tabs itself.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Behavior)]
|
|
|
|
|
public Position Position { get; set; } = Position.Top;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The color of the component. It supports the theme colors.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Appearance)]
|
|
|
|
|
public ThemeColor Color { get; set; } = ThemeColor.Default;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The color of the tab slider. It supports the theme colors.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Appearance)]
|
|
|
|
|
public ThemeColor SliderColor { get; set; } = ThemeColor.Inherit;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The color of the icon. It supports the theme colors.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Appearance)]
|
|
|
|
|
public ThemeColor IconColor { get; set; } = ThemeColor.Inherit;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The color of the next/prev icons. It supports the theme colors.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Appearance)]
|
|
|
|
|
public ThemeColor ScrollIconColor { get; set; } = ThemeColor.Inherit;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The higher the number, the heavier the drop-shadow, applies around the whole component.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Appearance)]
|
|
|
|
|
public int Elevation { set; get; } = 0;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// If true, will apply elevation, rounded, outlined effects to the whole tab component instead of just toolbar.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Appearance)]
|
|
|
|
|
public bool ApplyEffectsToContainer { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// If true, disables ripple effect.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Appearance)]
|
|
|
|
|
public bool DisableRipple { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// If true, disables slider animation
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Appearance)]
|
|
|
|
|
public bool DisableSliderAnimation { get => _disableSliderAnimation; set => _disableSliderAnimation = value; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Child content of component.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Behavior)]
|
|
|
|
|
public RenderFragment ChildContent { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 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
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Behavior)]
|
|
|
|
|
public RenderFragment<TabPanel> PrePanelContent { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Custom class/classes for TabPanel
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Appearance)]
|
|
|
|
|
public string TabPanelClass { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Custom class/classes for Selected Content Panel
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Appearance)]
|
|
|
|
|
public string PanelClass { get; set; }
|
|
|
|
|
|
|
|
|
|
public TabPanel ActivePanel { get; private set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The current active panel index. Also with Bidirectional Binding
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Behavior)]
|
|
|
|
|
public int ActivePanelIndex
|
|
|
|
|
{
|
|
|
|
|
get => _activePanelIndex;
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
if (_activePanelIndex != value)
|
|
|
|
|
{
|
|
|
|
|
_activePanelIndex = value;
|
|
|
|
|
if (_isRendered)
|
|
|
|
|
ActivePanel = _panels[_activePanelIndex];
|
|
|
|
|
ActivePanelIndexChanged.InvokeAsync(value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Fired when ActivePanelIndex changes.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
public EventCallback<int> ActivePanelIndexChanged { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 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
|
|
|
|
|
/// </summary>
|
|
|
|
|
public IReadOnlyList<TabPanel> Panels { get; private set; }
|
|
|
|
|
|
|
|
|
|
private List<TabPanel> _panels;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 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
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Behavior)]
|
|
|
|
|
public RenderFragment<Tabs> Header { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Additional content specified by Header is placed either before the tabs, after or not at all
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Behavior)]
|
|
|
|
|
public TabHeaderPosition HeaderPosition { get; set; } = TabHeaderPosition.After;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// A render fragment that is added before or after (based on the value of HeaderPosition) inside each tab panel
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Behavior)]
|
|
|
|
|
public RenderFragment<TabPanel> TabPanelHeader { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Additional content specified by Header is placed either before the tabs, after or not at all
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Parameter]
|
|
|
|
|
[Category(CategoryTypes.Tabs.Behavior)]
|
|
|
|
|
public TabHeaderPosition TabPanelHeaderPosition { get; set; } = TabHeaderPosition.After;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Can be used in derived class to add a class to the main container. If not overwritten return an empty string
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected virtual string InternalClassName { get; } = string.Empty;
|
|
|
|
|
|
|
|
|
|
private string _prevIcon;
|
|
|
|
|
|
|
|
|
|
private string _nextIcon;
|
|
|
|
|
|
|
|
|
|
#region Life cycle management
|
|
|
|
|
|
|
|
|
|
public Tabs()
|
|
|
|
|
{
|
|
|
|
|
_panels = new List<TabPanel>();
|
|
|
|
|
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("mud-tabs")
|
|
|
|
|
.AddClass($"mud-tabs-rounded", ApplyEffectsToContainer && Rounded)
|
|
|
|
|
.AddClass($"mud-paper-outlined", ApplyEffectsToContainer && Outlined)
|
|
|
|
|
.AddClass($"mud-elevation-{Elevation}", ApplyEffectsToContainer && Elevation != 0)
|
|
|
|
|
.AddClass($"mud-tabs-reverse", Position == Position.Bottom)
|
|
|
|
|
.AddClass($"mud-tabs-vertical", IsVerticalTabs())
|
|
|
|
|
.AddClass($"mud-tabs-vertical-reverse", Position == Position.Right && !RightToLeft || (Position == Position.Left) && RightToLeft || Position == Position.End)
|
|
|
|
|
.AddClass(InternalClassName)
|
|
|
|
|
.AddClass(Class)
|
|
|
|
|
.Build();
|
|
|
|
|
|
|
|
|
|
protected string ToolbarClassnames =>
|
|
|
|
|
new CssBuilder("mud-tabs-toolbar")
|
|
|
|
|
.AddClass($"mud-tabs-rounded", !ApplyEffectsToContainer && Rounded)
|
|
|
|
|
.AddClass($"mud-tabs-vertical", IsVerticalTabs())
|
|
|
|
|
.AddClass($"mud-tabs-toolbar-{Color.ToDescriptionString()}", Color != ThemeColor.Default)
|
|
|
|
|
.AddClass($"mud-tabs-border-{ConvertPosition(Position).ToDescriptionString()}", Border)
|
|
|
|
|
.AddClass($"mud-paper-outlined", !ApplyEffectsToContainer && Outlined)
|
|
|
|
|
.AddClass($"mud-elevation-{Elevation}", !ApplyEffectsToContainer && Elevation != 0)
|
|
|
|
|
.Build();
|
|
|
|
|
|
|
|
|
|
protected string WrapperClassnames =>
|
|
|
|
|
new CssBuilder("mud-tabs-toolbar-wrapper")
|
|
|
|
|
.AddClass($"mud-tabs-centered", Centered)
|
|
|
|
|
.AddClass($"mud-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("mud-tabs-panels")
|
|
|
|
|
.AddClass($"mud-tabs-vertical", IsVerticalTabs())
|
|
|
|
|
.AddClass(PanelClass)
|
|
|
|
|
.Build();
|
|
|
|
|
|
|
|
|
|
protected string SliderClass =>
|
|
|
|
|
new CssBuilder("mud-tab-slider")
|
|
|
|
|
.AddClass($"mud-{SliderColor.ToDescriptionString()}", SliderColor != ThemeColor.Inherit)
|
|
|
|
|
.AddClass($"mud-tab-slider-horizontal", Position is Position.Top or Position.Bottom)
|
|
|
|
|
.AddClass($"mud-tab-slider-vertical", IsVerticalTabs())
|
|
|
|
|
.AddClass($"mud-tab-slider-horizontal-reverse", Position == Position.Bottom)
|
|
|
|
|
.AddClass($"mud-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
|
|
|
|
|
{
|
|
|
|
|
Position.Start => RightToLeft ? Position.Right : Position.Left,
|
|
|
|
|
Position.End => RightToLeft ? Position.Left : Position.Right,
|
|
|
|
|
_ => position
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string GetTabClass(TabPanel panel)
|
|
|
|
|
{
|
|
|
|
|
var tabClass = new CssBuilder("mud-tab")
|
|
|
|
|
.AddClass($"mud-tab-active", when: () => panel == ActivePanel)
|
|
|
|
|
.AddClass($"mud-disabled", panel.Disabled)
|
|
|
|
|
.AddClass($"mud-ripple", !DisableRipple)
|
|
|
|
|
.AddClass(TabPanelClass)
|
|
|
|
|
.AddClass(panel.Class)
|
|
|
|
|
.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()
|
|
|
|
|
.AddStyle(panel.Style)
|
|
|
|
|
.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<ElementReference, BoundingClientRect> 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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Calculates the amount of panels that are completely visible inside the toolbar content area. Panels that are just partially visible are not considered here!
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>The amount of panels visible inside the toolbar area. CAUTION: Might return 0!</returns>
|
|
|
|
|
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
|
|
|
|
|
}
|