Progress - Categorizing variables and methods

features/refactor
stm 2 years ago
parent 46c396e33f
commit c3b267dfc4

@ -126,7 +126,7 @@ public static class CategoryTypes
public const string Common = "Common"; public const string Common = "Common";
} }
/// <summary>Used in: <see cref="ButtonBase"/>, all components inheriting from it, and <see cref="MudToggleIconButton"/>.</summary> /// <summary>Used in: <see cref="ButtonBase"/>, all components inheriting from it, and <see cref="ToggleIconButton"/>.</summary>
public static class Button public static class Button
{ {
public const string Behavior = "Behavior"; public const string Behavior = "Behavior";

@ -1,12 +1,25 @@
using Connected.Extensions; using Connected.Utilities;
using Connected.Utilities;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using System.Security;
namespace Connected.Components; namespace Connected.Components;
public partial class AppBar : UIComponent public partial class AppBar : UIComponent
{ {
#region Event callbacks
#endregion
#region Content placeholders
/// <summary>
/// Child content of the component.
/// </summary>
[Parameter]
public RenderFragment? ChildContent { get; set; }
#endregion
#region Styling properties
/// <summary> /// <summary>
/// The classlist determining the toolbar render. /// The classlist determining the toolbar render.
/// </summary> /// </summary>
@ -18,6 +31,12 @@ public partial class AppBar : UIComponent
.AddClass(ToolbarClassList); .AddClass(ToolbarClassList);
} }
} }
/// <summary>
/// Class names for the nested toolbar. In case of several, separate by spaces.
/// </summary>
[Parameter]
public string? ToolbarClassList { get; set; }
/// <summary> /// <summary>
/// The classlist determining the header render. /// The classlist determining the header render.
@ -32,19 +51,6 @@ public partial class AppBar : UIComponent
.AddClass(HeaderClassList); .AddClass(HeaderClassList);
} }
} }
/// <summary>
/// Child content of the component.
/// </summary>
[Parameter]
public RenderFragment? ChildContent { get; set; }
/// <summary>
/// If true, appbar will be fixed.
/// </summary>
[Parameter]
public bool Fixed { get; set; } = true;
/// <summary> /// <summary>
/// Class names for the AppBar header. In case of several, separate by spaces. /// Class names for the AppBar header. In case of several, separate by spaces.
/// </summary> /// </summary>
@ -52,14 +58,29 @@ public partial class AppBar : UIComponent
public string? HeaderClassList { get; set; } public string? HeaderClassList { get; set; }
/// <summary> /// <summary>
/// Class names for the nested toolbar. In case of several, separate by spaces. /// Determines the vertical alignment of the AppBar.
/// </summary> /// </summary>
[Parameter] [Parameter]
public string? ToolbarClassList { get; set; } public VerticalAlignment VerticalAlignment { get; set; }
/// <summary> /// <summary>
/// Determines the vertical alignment of the AppBar. /// If true, appbar will be fixed.
/// </summary> /// </summary>
[Parameter] [Parameter]
public VerticalAlignment VerticalAlignment { get; set; } public bool Fixed { get; set; } = true;
#endregion
#region Lifecycle
#endregion
} }

@ -6,6 +6,12 @@ namespace Connected.Components;
public partial class Autocomplete<T> : InputBase<T>, IDisposable public partial class Autocomplete<T> : InputBase<T>, IDisposable
{ {
#region Variables
[Inject]
IScrollManager ScrollManager { get; set; }
private Func<T, string>? _toStringFunc; private Func<T, string>? _toStringFunc;
private Task _currentSearchTask; private Task _currentSearchTask;
private CancellationTokenSource _cancellationTokenSrc; private CancellationTokenSource _cancellationTokenSrc;
@ -26,77 +32,22 @@ public partial class Autocomplete<T> : InputBase<T>, IDisposable
/// </summary> /// </summary>
private readonly string _componentId = Guid.NewGuid().ToString(); private readonly string _componentId = Guid.NewGuid().ToString();
public Autocomplete() #endregion
{
Adornment = Adornment.End;
IconSize = Size.Medium;
}
[Inject] #region Event callbacks
IScrollManager ScrollManager { get; set; }
/// <summary>
/// User class names for the popover, separated by space
/// </summary>
[Parameter]
public string PopoverClass { get; set; }
/// <summary>
/// Set the anchor origin point to determen where the popover will open from.
/// </summary>
[Parameter]
public Origin AnchorOrigin { get; set; } = Origin.BottomCenter;
/// <summary>
/// Sets the transform origin point for the popover.
/// </summary>
[Parameter]
public Origin TransformOrigin { get; set; } = Origin.TopCenter;
/// <summary>
/// If true, compact vertical padding will be applied to all Autocomplete items.
/// </summary>
[Parameter]
public bool Dense { get; set; }
/// <summary>
/// The Open Autocomplete Glyph
/// </summary>
[Parameter]
public string OpenIcon { get; set; } = Icons.Material.Filled.ArrowDropDown;
/// <summary>
/// The Close Autocomplete Glyph
/// </summary>
[Parameter]
public string CloseIcon { get; set; } = Icons.Material.Filled.ArrowDropUp;
/// <summary>
/// The maximum height of the Autocomplete when it is open.
/// </summary>
[Parameter]
public int MaxHeight { get; set; } = 300;
/// <summary>
/// Defines how values are displayed in the drop-down list
/// </summary>
[Parameter]
public Func<T, string>? ToStringFunc
{
get => _toStringFunc;
set
{
if (_toStringFunc == value)
return;
_toStringFunc = value;
SetConverter(new LambdaConverter<T, string>(_toStringFunc ?? (x => x?.ToString()), null));
}
}
/// <summary> /// <summary>
/// Whether to show the progress indicator. /// Function to be invoked when checking whether an item should be disabled or not
/// </summary> /// </summary>
[Parameter] [Parameter]
public bool ShowProgressIndicator { get; set; } = false; public Func<T, bool> ItemDisabledFunc { get; set; }
/// <summary> /// <summary>
/// The color of the progress indicator. /// If true, the currently selected item from the drop-down (if it is open) is selected.
/// </summary> /// </summary>
[Parameter] [Parameter]
public ThemeColor ProgressIndicatorColor { get; set; } = ThemeColor.Default; public bool SelectValueOnTab { get; set; } = false;
private bool IsLoading => _currentSearchTask is not null && !_currentSearchTask.IsCompleted;
/// <summary> /// <summary>
/// Func that returns a list of items matching the typed text. Provides a cancellation token that /// Func that returns a list of items matching the typed text. Provides a cancellation token that
/// is marked as cancelled when the user changes the search text or selects a value from the list. /// is marked as cancelled when the user changes the search text or selects a value from the list.
@ -109,145 +60,95 @@ public partial class Autocomplete<T> : InputBase<T>, IDisposable
/// </summary> /// </summary>
[Parameter] [Parameter]
public Func<string, Task<IEnumerable<T>>> SearchFunc { get; set; } public Func<string, Task<IEnumerable<T>>> SearchFunc { get; set; }
/// <summary>
/// Maximum items to display, defaults to 10.
/// A null value will display all items.
/// </summary>
[Parameter]
public int? MaxItems { get; set; } = 10;
/// <summary>
/// Minimum characters to initiate a search
/// </summary>
[Parameter]
public int MinCharacters { get; set; } = 0;
/// <summary>
/// Reset value if user deletes the text
/// </summary>
[Parameter]
public bool ResetValueOnEmptyText { get; set; } = false;
/// <summary>
/// If true, clicking the text field will select (highlight) its contents.
/// </summary>
[Parameter]
public bool SelectOnClick { get; set; } = true;
/// <summary>
/// Debounce interval in milliseconds.
/// </summary>
[Parameter]
public int DebounceInterval { get; set; } = 100;
/// <summary>
/// Optional presentation template for unselected items
/// </summary>
[Parameter]
public RenderFragment<T> ItemTemplate { get; set; }
/// <summary>
/// Optional presentation template for the selected item
/// </summary>
[Parameter]
public RenderFragment<T> ItemSelectedTemplate { get; set; }
/// <summary>
/// Optional presentation template for disabled item
/// </summary>
[Parameter]
public RenderFragment<T> ItemDisabledTemplate { get; set; }
/// <summary>
/// Optional presentation template for when more items were returned from the Search function than the MaxItems limit
/// </summary>
[Parameter]
public RenderFragment MoreItemsTemplate { get; set; }
/// <summary>
/// Optional presentation template for when no items were returned from the Search function
/// </summary>
[Parameter]
public RenderFragment NoItemsTemplate { get; set; }
/// <summary>
/// Optional template for progress indicator
/// </summary>
[Parameter]
public RenderFragment ProgressIndicatorTemplate { get; set; }
/// <summary>
/// Optional template for showing progress indicator inside the popover
/// </summary>
[Parameter]
public RenderFragment ProgressIndicatorInPopoverTemplate { get; set; }
/// <summary>
/// On drop-down close override Text with selected Value. This makes it clear to the user
/// which list value is currently selected and disallows incomplete values in Text.
/// </summary>
[Parameter]
public bool CoerceText { get; set; } = true;
/// <summary>
/// If user input is not found by the search func and CoerceValue is set to true the user input
/// will be applied to the Value which allows to validate it and display an error message.
/// </summary>
[Parameter]
public bool CoerceValue { get; set; }
/// <summary>
/// Function to be invoked when checking whether an item should be disabled or not
/// </summary>
[Parameter]
public Func<T, bool> ItemDisabledFunc { get; set; }
/// <summary>
/// Returns the open state of the drop-down.
/// </summary>
public bool IsOpen
{
get => _isOpen;
// Note: the setter is protected because it was needed by a user who derived his own autocomplete from this class.
// Note: setting IsOpen will not open or close it. Use ToggleMenu() for that.
protected set
{
if (value == _isOpen)
return;
_isOpen = value;
IsOpenChanged.InvokeAsync(_isOpen).AndForget(); // <summary>
}
}
/// <summary>
/// An event triggered when the state of IsOpen has changed /// An event triggered when the state of IsOpen has changed
/// </summary> /// </summary>
[Parameter] [Parameter]
public EventCallback<bool> IsOpenChanged { get; set; } public EventCallback<bool> IsOpenChanged { get; set; }
/// <summary>
/// If true, the currently selected item from the drop-down (if it is open) is selected.
/// </summary>
[Parameter]
public bool SelectValueOnTab { get; set; } = false;
/// <summary>
/// Show clear button.
/// </summary>
[Parameter]
public bool Clearable { get; set; } = false;
/// <summary> /// <summary>
/// Button click event for clear button. Called after text and value has been cleared. /// Button click event for clear button. Called after text and value has been cleared.
/// </summary> /// </summary>
[Parameter] [Parameter]
public EventCallback<MouseEventArgs> OnClearButtonClick { get; set; } public EventCallback<MouseEventArgs> OnClearButtonClick { get; set; }
private string CurrentIcon => !string.IsNullOrWhiteSpace(AdornmentIcon) ? AdornmentIcon : _isOpen ? CloseIcon : OpenIcon; internal virtual async Task OnInputKeyUp(KeyboardEventArgs args)
{
switch (args.Key)
{
case "Enter":
case "NumpadEnter":
if (!IsOpen)
await ToggleMenu();
else
await OnEnterKey();
protected string ClassList() break;
case "ArrowDown":
if (!IsOpen)
await ToggleMenu();
else
{ {
return new CssBuilder("select") var increment = _enabledItemIndices.ElementAtOrDefault(_enabledItemIndices.IndexOf(_selectedListItemIndex) + 1) - _selectedListItemIndex;
.Build();
await SelectNextItem(increment < 0 ? 1 : increment);
} }
protected string AutocompleteClassList() break;
case "ArrowUp":
if (args.AltKey)
await ChangeMenu(false);
else if (!IsOpen)
await ToggleMenu();
else
{ {
return new CssBuilder("select") var decrement = _selectedListItemIndex - _enabledItemIndices.ElementAtOrDefault(_enabledItemIndices.IndexOf(_selectedListItemIndex) - 1);
.AddClass("autocomplete") await SelectNextItem(-(decrement < 0 ? 1 : decrement));
.AddClass("width-full", FullWidth)
.AddClass("autocomplete--with-progress", ShowProgressIndicator && IsLoading)
.Build();
} }
protected string CircularProgressClassList() break;
case "Escape":
await ChangeMenu(false);
break;
case "Tab":
await Task.Delay(1);
if (!IsOpen)
return;
if (SelectValueOnTab)
await OnEnterKey();
else
await ToggleMenu();
break;
case "Backspace":
if (args.CtrlKey && args.ShiftKey)
Reset();
break;
}
base.InvokeKeyUp(args);
}
internal virtual async Task OnInputKeyDown(KeyboardEventArgs args)
{ {
return new CssBuilder("progress-indicator-circular") switch (args.Key)
.AddClass("progress-indicator-circular--with-adornment", Adornment == Adornment.End) {
.Build(); case "Tab":
// NOTE: We need to catch Tab in Keydown because a tab will move focus to the next element and thus
// in OnInputKeyUp we'd never get the tab key
if (!IsOpen)
return;
if (SelectValueOnTab)
await OnEnterKey();
else
IsOpen = false;
break;
}
} }
public async Task SelectOption(T value) public async Task SelectOption(T value)
@ -309,20 +210,7 @@ public partial class Autocomplete<T> : InputBase<T>, IDisposable
} }
} }
protected override void OnInitialized()
{
var text = GetItemString(Value);
if (!string.IsNullOrWhiteSpace(text))
Text = text;
}
protected override void OnAfterRender(bool firstRender)
{
_isCleared = false;
base.OnAfterRender(firstRender);
}
protected override Task UpdateTextPropertyAsync(bool updateValue) protected override Task UpdateTextPropertyAsync(bool updateValue)
{ {
@ -454,91 +342,14 @@ public partial class Autocomplete<T> : InputBase<T>, IDisposable
private string GetItemString(T item) private string GetItemString(T item)
{ {
if (item is null) if (item is null)
return string.Empty; return string.Empty;
try try
{ {
return Converter.Convert(item); return Converter.Convert(item);
}
catch (NullReferenceException) { }
return "null";
}
internal virtual async Task OnInputKeyDown(KeyboardEventArgs args)
{
switch (args.Key)
{
case "Tab":
// NOTE: We need to catch Tab in Keydown because a tab will move focus to the next element and thus
// in OnInputKeyUp we'd never get the tab key
if (!IsOpen)
return;
if (SelectValueOnTab)
await OnEnterKey();
else
IsOpen = false;
break;
}
}
internal virtual async Task OnInputKeyUp(KeyboardEventArgs args)
{
switch (args.Key)
{
case "Enter":
case "NumpadEnter":
if (!IsOpen)
await ToggleMenu();
else
await OnEnterKey();
break;
case "ArrowDown":
if (!IsOpen)
await ToggleMenu();
else
{
var increment = _enabledItemIndices.ElementAtOrDefault(_enabledItemIndices.IndexOf(_selectedListItemIndex) + 1) - _selectedListItemIndex;
await SelectNextItem(increment < 0 ? 1 : increment);
}
break;
case "ArrowUp":
if (args.AltKey)
await ChangeMenu(false);
else if (!IsOpen)
await ToggleMenu();
else
{
var decrement = _selectedListItemIndex - _enabledItemIndices.ElementAtOrDefault(_enabledItemIndices.IndexOf(_selectedListItemIndex) - 1);
await SelectNextItem(-(decrement < 0 ? 1 : decrement));
}
break;
case "Escape":
await ChangeMenu(false);
break;
case "Tab":
await Task.Delay(1);
if (!IsOpen)
return;
if (SelectValueOnTab)
await OnEnterKey();
else
await ToggleMenu();
break;
case "Backspace":
if (args.CtrlKey && args.ShiftKey)
Reset();
break;
} }
catch (NullReferenceException) { }
base.InvokeKeyUp(args); return "null";
} }
private ValueTask SelectNextItem(int increment) private ValueTask SelectNextItem(int increment)
@ -629,21 +440,7 @@ public partial class Autocomplete<T> : InputBase<T>, IDisposable
return SetValueAsync(value, updateText: false); return SetValueAsync(value, updateText: false);
} }
protected override void Dispose(bool disposing)
{
_timer?.Dispose();
if (_cancellationTokenSrc is not null)
{
try
{
_cancellationTokenSrc.Dispose();
}
catch { }
}
base.Dispose(disposing);
}
/// <summary> /// <summary>
/// Focus the input in the Autocomplete component. /// Focus the input in the Autocomplete component.
/// </summary> /// </summary>
@ -689,4 +486,247 @@ public partial class Autocomplete<T> : InputBase<T>, IDisposable
{ {
await SelectOption(item); await SelectOption(item);
} }
#endregion
#region Content placeholders
#endregion
#region Styling properties
/// <summary>
/// Show clear button.
/// </summary>
[Parameter]
public bool Clearable { get; set; } = false;
private string CurrentIcon => !string.IsNullOrWhiteSpace(AdornmentIcon) ? AdornmentIcon : _isOpen ? CloseIcon : OpenIcon;
/// <summary>
/// Returns the open state of the drop-down.
/// </summary>
public bool IsOpen
{
get => _isOpen;
// Note: the setter is protected because it was needed by a user who derived his own autocomplete from this class.
// Note: setting IsOpen will not open or close it. Use ToggleMenu() for that.
protected set
{
if (value == _isOpen)
return;
_isOpen = value;
IsOpenChanged.InvokeAsync(_isOpen).AndForget();
}
}
/// <summary>
/// User class names for the popover, separated by space
/// </summary>
[Parameter]
public string PopoverClass { get; set; }
/// <summary>
/// Set the anchor origin point to determen where the popover will open from.
/// </summary>
[Parameter]
public Origin AnchorOrigin { get; set; } = Origin.BottomCenter;
/// <summary>
/// Sets the transform origin point for the popover.
/// </summary>
[Parameter]
public Origin TransformOrigin { get; set; } = Origin.TopCenter;
/// <summary>
/// If true, compact vertical padding will be applied to all Autocomplete items.
/// </summary>
[Parameter]
public bool Dense { get; set; }
/// <summary>
/// The Open Autocomplete Glyph
/// </summary>
[Parameter]
public string OpenIcon { get; set; } = Icons.Material.Filled.ArrowDropDown;
/// <summary>
/// The Close Autocomplete Glyph
/// </summary>
[Parameter]
public string CloseIcon { get; set; } = Icons.Material.Filled.ArrowDropUp;
/// <summary>
/// The maximum height of the Autocomplete when it is open.
/// </summary>
[Parameter]
public int MaxHeight { get; set; } = 300;
/// <summary>
/// Defines how values are displayed in the drop-down list
/// </summary>
[Parameter]
public Func<T, string>? ToStringFunc
{
get => _toStringFunc;
set
{
if (_toStringFunc == value)
return;
_toStringFunc = value;
SetConverter(new LambdaConverter<T, string>(_toStringFunc ?? (x => x?.ToString()), null));
}
}
/// <summary>
/// Whether to show the progress indicator.
/// </summary>
[Parameter]
public bool ShowProgressIndicator { get; set; } = false;
/// <summary>
/// The color of the progress indicator.
/// </summary>
[Parameter]
public ThemeColor ProgressIndicatorColor { get; set; } = ThemeColor.Default;
private bool IsLoading => _currentSearchTask is not null && !_currentSearchTask.IsCompleted;
/// <summary>
/// Maximum items to display, defaults to 10.
/// A null value will display all items.
/// </summary>
[Parameter]
public int? MaxItems { get; set; } = 10;
/// <summary>
/// Minimum characters to initiate a search
/// </summary>
[Parameter]
public int MinCharacters { get; set; } = 0;
/// <summary>
/// Reset value if user deletes the text
/// </summary>
[Parameter]
public bool ResetValueOnEmptyText { get; set; } = false;
/// <summary>
/// If true, clicking the text field will select (highlight) its contents.
/// </summary>
[Parameter]
public bool SelectOnClick { get; set; } = true;
protected string ClassList()
{
return new CssBuilder("select")
.Build();
}
protected string AutocompleteClassList()
{
return new CssBuilder("select")
.AddClass("autocomplete")
.AddClass("width-full", FullWidth)
.AddClass("autocomplete--with-progress", ShowProgressIndicator && IsLoading)
.Build();
}
protected string CircularProgressClassList()
{
return new CssBuilder("progress-indicator-circular")
.AddClass("progress-indicator-circular--with-adornment", Adornment == Adornment.End)
.Build();
}
/// <summary>
/// Debounce interval in milliseconds.
/// </summary>
[Parameter]
public int DebounceInterval { get; set; } = 100;
/// <summary>
/// Optional presentation template for unselected items
/// </summary>
[Parameter]
public RenderFragment<T> ItemTemplate { get; set; }
/// <summary>
/// Optional presentation template for the selected item
/// </summary>
[Parameter]
public RenderFragment<T> ItemSelectedTemplate { get; set; }
/// <summary>
/// Optional presentation template for disabled item
/// </summary>
[Parameter]
public RenderFragment<T> ItemDisabledTemplate { get; set; }
/// <summary>
/// Optional presentation template for when more items were returned from the Search function than the MaxItems limit
/// </summary>
[Parameter]
public RenderFragment MoreItemsTemplate { get; set; }
/// <summary>
/// Optional presentation template for when no items were returned from the Search function
/// </summary>
[Parameter]
public RenderFragment NoItemsTemplate { get; set; }
/// <summary>
/// Optional template for progress indicator
/// </summary>
[Parameter]
public RenderFragment ProgressIndicatorTemplate { get; set; }
/// <summary>
/// Optional template for showing progress indicator inside the popover
/// </summary>
[Parameter]
public RenderFragment ProgressIndicatorInPopoverTemplate { get; set; }
/// <summary>
/// On drop-down close override Text with selected Value. This makes it clear to the user
/// which list value is currently selected and disallows incomplete values in Text.
/// </summary>
[Parameter]
public bool CoerceText { get; set; } = true;
/// <summary>
/// If user input is not found by the search func and CoerceValue is set to true the user input
/// will be applied to the Value which allows to validate it and display an error message.
/// </summary>
[Parameter]
public bool CoerceValue { get; set; }
#endregion Styling
#region Lifecycle
/// <summary>
/// Constructor
/// </summary>
public Autocomplete()
{
Adornment = Adornment.End;
IconSize = Size.Medium;
}
protected override void OnInitialized()
{
var text = GetItemString(Value);
if (!string.IsNullOrWhiteSpace(text))
Text = text;
}
protected override void OnAfterRender(bool firstRender)
{
_isCleared = false;
base.OnAfterRender(firstRender);
}
protected override void Dispose(bool disposing)
{
_timer?.Dispose();
if (_cancellationTokenSrc is not null)
{
try
{
_cancellationTokenSrc.Dispose();
}
catch { }
}
base.Dispose(disposing);
}
#endregion
} }

@ -7,9 +7,43 @@ namespace Connected.Components;
partial class Avatar : UIComponent, IDisposable partial class Avatar : UIComponent, IDisposable
{ {
#region Variables
#endregion
#region Events
internal void ForceRedraw() => StateHasChanged();
#endregion
#region Content
/// <summary>
/// Child content of the component.
/// </summary>
[Parameter]
public RenderFragment? ChildContent { get; set; }
/// <summary>
/// Link to image, if set a image will be displayed instead of text.
/// </summary>
[Parameter]
public string? Image { get; set; }
/// <summary>
/// If set (and Image is also set), will add an alt property to the img element
/// </summary>
[Parameter]
public string? ImageAltText { get; set; }
[CascadingParameter] [CascadingParameter]
protected AvatarGroup? AvatarGroup { get; set; } protected AvatarGroup? AvatarGroup { get; set; }
#endregion
#region Styling
protected CssBuilder CompiledClassList protected CssBuilder CompiledClassList
{ {
get get
@ -41,18 +75,6 @@ partial class Avatar : UIComponent, IDisposable
[Parameter] [Parameter]
public string? ClassList { get; set; } public string? ClassList { get; set; }
/// <summary>
/// If set (and Image is also set), will add an alt property to the img element
/// </summary>
[Parameter]
public string? ImageAltText { get; set; }
/// <summary>
/// Child content of the component.
/// </summary>
[Parameter]
public RenderFragment? ChildContent { get; set; }
/// <summary> /// <summary>
/// The color of the component. It supports the theme colors. /// The color of the component. It supports the theme colors.
/// </summary> /// </summary>
@ -65,12 +87,6 @@ partial class Avatar : UIComponent, IDisposable
[Parameter] [Parameter]
public int Elevation { set; get; } = 0; public int Elevation { set; get; } = 0;
/// <summary>
/// Link to image, if set a image will be displayed instead of text.
/// </summary>
[Parameter]
public string? Image { get; set; }
/// <summary> /// <summary>
/// Sets the avatar appearance /// Sets the avatar appearance
/// </summary> /// </summary>
@ -88,6 +104,10 @@ partial class Avatar : UIComponent, IDisposable
[Parameter] [Parameter]
public Variant Variant { get; set; } = Variant.Filled; public Variant Variant { get; set; } = Variant.Filled;
#endregion
#region Lifecycle
protected override void OnInitialized() protected override void OnInitialized()
{ {
base.OnInitialized(); base.OnInitialized();
@ -97,5 +117,6 @@ partial class Avatar : UIComponent, IDisposable
public void Dispose() => AvatarGroup?.RemoveAvatar(this); public void Dispose() => AvatarGroup?.RemoveAvatar(this);
internal void ForceRedraw() => StateHasChanged(); #endregion
} }

@ -7,10 +7,57 @@ namespace Connected.Components;
partial class AvatarGroup : UIComponent partial class AvatarGroup : UIComponent
{ {
#region Variables
private bool _childrenNeedUpdates = false; private bool _childrenNeedUpdates = false;
private int _spacing = 3; private int _spacing = 3;
private List<Avatar> _avatars = new(); private List<Avatar> _avatars = new();
#endregion
#region Event callbacks
#endregion
#region Content
/// <summary>
/// Child content of the component.
/// </summary>
[Parameter]
[Category(CategoryTypes.AvatarGroup.Behavior)]
public RenderFragment ChildContent { get; set; }
internal void AddAvatar(Avatar avatar)
{
_avatars.Add(avatar);
StateHasChanged();
}
internal void RemoveAvatar(Avatar avatar)
{
_avatars.Remove(avatar);
}
internal bool MaxGroupReached(Avatar avatar)
{
if (_avatars.IndexOf(avatar) < Max)
return true;
else
return false;
}
#endregion
#region Styling properties
internal CssBuilder GetAvatarSpacing() => new CssBuilder()
.AddClass($"ms-n{Spacing}");
internal StyleBuilder GetAvatarZindex(Avatar avatar) => new StyleBuilder()
.AddStyle("z-index", $"{_avatars.Count - _avatars.IndexOf(avatar)}");
private CssBuilder CompiledClassList private CssBuilder CompiledClassList
{ {
get get
@ -21,6 +68,11 @@ partial class AvatarGroup : UIComponent
.AddClass(ClassList); .AddClass(ClassList);
} }
} }
/// <summary>
/// A space separated list of class names, added on top of the default class list.
/// </summary>
[Parameter]
public string? ClassList { get; set; }
private CssBuilder CompiledMaxAvatarClassList private CssBuilder CompiledMaxAvatarClassList
{ {
@ -31,30 +83,12 @@ partial class AvatarGroup : UIComponent
.AddClass(MaxAvatarClass); .AddClass(MaxAvatarClass);
} }
} }
/// <summary>
/// Spacing between avatars where 0 is none and 16 max.
/// </summary>
[Parameter]
public int Spacing
{
get => _spacing;
set
{
if (value != _spacing)
{
_spacing = value;
_childrenNeedUpdates = true;
}
}
}
/// <summary> /// <summary>
/// A space separated list of class names, added on top of the default class list. /// Custom class/classes for MaxAvatar
/// </summary> /// </summary>
[Parameter] [Parameter]
public string? ClassList { get; set; } [Category(CategoryTypes.AvatarGroup.Appearance)]
public string MaxAvatarClass { get; set; }
/// <summary> /// <summary>
/// Outlines the grouped avatars to distinguish them, useful when avatars are the same color or uses images. /// Outlines the grouped avatars to distinguish them, useful when avatars are the same color or uses images.
/// </summary> /// </summary>
@ -132,44 +166,25 @@ partial class AvatarGroup : UIComponent
} }
/// <summary> /// <summary>
/// Custom class/classes for MaxAvatar /// Spacing between avatars where 0 is none and 16 max.
/// </summary>
[Parameter]
[Category(CategoryTypes.AvatarGroup.Appearance)]
public string MaxAvatarClass { get; set; }
/// <summary>
/// Child content of the component.
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.AvatarGroup.Behavior)] public int Spacing
public RenderFragment ChildContent { get; set; }
internal void AddAvatar(Avatar avatar)
{ {
_avatars.Add(avatar); get => _spacing;
StateHasChanged(); set
}
internal void RemoveAvatar(Avatar avatar)
{ {
_avatars.Remove(avatar); if (value != _spacing)
}
internal CssBuilder GetAvatarSpacing() => new CssBuilder()
.AddClass($"ms-n{Spacing}");
internal StyleBuilder GetAvatarZindex(Avatar avatar) => new StyleBuilder()
.AddStyle("z-index", $"{_avatars.Count - _avatars.IndexOf(avatar)}");
internal bool MaxGroupReached(Avatar avatar)
{ {
if (_avatars.IndexOf(avatar) < Max) _spacing = value;
return true; _childrenNeedUpdates = true;
else }
return false; }
} }
#endregion
#region Lifecycle
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
base.OnParametersSet(); base.OnParametersSet();
@ -184,4 +199,7 @@ partial class AvatarGroup : UIComponent
_childrenNeedUpdates = false; _childrenNeedUpdates = false;
} }
} }
#endregion
} }

@ -1,10 +1,4 @@
using System; namespace Connected.Components;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Connected.Components;
public enum AvatarKind public enum AvatarKind
{ {
Undefined = 0, Undefined = 0,

@ -9,6 +9,72 @@ namespace Connected.Components;
public partial class Badge : UIComponent public partial class Badge : UIComponent
{ {
#region Variables
#endregion
#region Events
/// <summary>
/// Button click event if set.
/// </summary>
[Parameter] public EventCallback<MouseEventArgs> OnClick { get; set; }
internal Task HandleBadgeClick(MouseEventArgs e)
{
if (OnClick.HasDelegate)
return OnClick.InvokeAsync(e);
return Task.CompletedTask;
}
#endregion
#region Content
private string _content;
/// <summary>
/// Sets the Glyph to use in the badge.
/// </summary>
[Parameter]
[Category(CategoryTypes.Badge.Behavior)]
public string Icon { get; set; }
/// <summary>
/// Max value to show when content is integer type.
/// </summary>
[Parameter]
[Category(CategoryTypes.Badge.Behavior)]
public int Max { get; set; } = 99;
/// <summary>
/// Content you want inside the badge. Supported types are string and integer.
/// </summary>
[Parameter]
[Category(CategoryTypes.Badge.Behavior)]
public object Content { get; set; }
/// <summary>
/// Child content of component, the content that the badge will apply to.
/// </summary>
[Parameter]
[Category(CategoryTypes.Badge.Behavior)]
public RenderFragment ChildContent { get; set; }
#endregion
#region Styling
/// <summary>
/// Badge class names, separated by space.
/// </summary>
[Parameter]
[Category(CategoryTypes.Badge.Appearance)]
public string BadgeClass { get; set; }
protected string CompiledClassList => protected string CompiledClassList =>
new CssBuilder("badge-root") new CssBuilder("badge-root")
.AddClass(AdditionalClassList) .AddClass(AdditionalClassList)
@ -59,27 +125,6 @@ public partial class Badge : UIComponent
[Category(CategoryTypes.Badge.Appearance)] [Category(CategoryTypes.Badge.Appearance)]
public ThemeColor Color { get; set; } = ThemeColor.Default; public ThemeColor Color { get; set; } = ThemeColor.Default;
/// <summary>
/// Aligns the badge to bottom.
/// </summary>
[ExcludeFromCodeCoverage]
[Obsolete("Use Origin instead.", true)]
[Parameter] public bool Bottom { get; set; }
/// <summary>
/// Aligns the badge to left.
/// </summary>
[ExcludeFromCodeCoverage]
[Obsolete("Use Origin instead.", true)]
[Parameter] public bool Left { get => Start; set { Start = value; } }
/// <summary>
/// Aligns the badge to the start (Left in LTR and right in RTL).
/// </summary>
[ExcludeFromCodeCoverage]
[Obsolete("Use Origin instead.", true)]
[Parameter] public bool Start { get; set; }
/// <summary> /// <summary>
/// Reduces the size of the badge and hide any of its content. /// Reduces the size of the badge and hide any of its content.
/// </summary> /// </summary>
@ -101,55 +146,9 @@ public partial class Badge : UIComponent
[Category(CategoryTypes.Badge.Appearance)] [Category(CategoryTypes.Badge.Appearance)]
public bool Bordered { get; set; } public bool Bordered { get; set; }
/// <summary> #endregion
/// Sets the Glyph to use in the badge.
/// </summary>
[Parameter]
[Category(CategoryTypes.Badge.Behavior)]
public string Icon { get; set; }
/// <summary>
/// Max value to show when content is integer type.
/// </summary>
[Parameter]
[Category(CategoryTypes.Badge.Behavior)]
public int Max { get; set; } = 99;
/// <summary>
/// Content you want inside the badge. Supported types are string and integer.
/// </summary>
[Parameter]
[Category(CategoryTypes.Badge.Behavior)]
public object Content { get; set; }
/// <summary>
/// Badge class names, separated by space.
/// </summary>
[Parameter]
[Category(CategoryTypes.Badge.Appearance)]
public string BadgeClass { get; set; }
/// <summary> #region Lifecycle
/// Child content of component, the content that the badge will apply to.
/// </summary>
[Parameter]
[Category(CategoryTypes.Badge.Behavior)]
public RenderFragment ChildContent { get; set; }
/// <summary>
/// Button click event if set.
/// </summary>
[Parameter] public EventCallback<MouseEventArgs> OnClick { get; set; }
private string _content;
internal Task HandleBadgeClick(MouseEventArgs e)
{
if (OnClick.HasDelegate)
return OnClick.InvokeAsync(e);
return Task.CompletedTask;
}
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
@ -173,4 +172,6 @@ public partial class Badge : UIComponent
_content = null; _content = null;
} }
} }
#endregion
} }

@ -4,13 +4,20 @@ using Microsoft.AspNetCore.Components;
namespace Connected.Components; namespace Connected.Components;
public partial class BreadcrumbLink : UIComponent public partial class BreadcrumbLink : UIComponent
{ {
#region Content
[Parameter] [Parameter]
public BreadcrumbItem Item { get; set; } public BreadcrumbItem Item { get; set; }
[CascadingParameter] [CascadingParameter]
public Breadcrumbs Parent { get; set; } public Breadcrumbs Parent { get; set; }
#endregion
#region Style
private string Classname => new CssBuilder("breadcrumb-item") private string Classname => new CssBuilder("breadcrumb-item")
.AddClass("disabled", Item?.Disabled) .AddClass("disabled", Item?.Disabled)
.Build(); .Build();
#endregion
} }

@ -3,6 +3,8 @@
namespace Connected.Components; namespace Connected.Components;
public partial class BreadcrumbSeparator : UIComponent public partial class BreadcrumbSeparator : UIComponent
{ {
#region Content
[CascadingParameter] [CascadingParameter]
public Breadcrumbs Parent { get; set; } public Breadcrumbs Parent { get; set; }
#endregion
} }

@ -6,6 +6,22 @@ namespace Connected.Components;
public partial class Breadcrumbs : UIComponent public partial class Breadcrumbs : UIComponent
{ {
#region Events
internal void Expand()
{
if (!Collapsed)
return;
Collapsed = false;
StateHasChanged();
}
#endregion
#region Content
/// <summary> /// <summary>
/// A list of breadcrumb items/links. /// A list of breadcrumb items/links.
/// </summary> /// </summary>
@ -20,6 +36,30 @@ public partial class Breadcrumbs : UIComponent
[Category(CategoryTypes.Breadcrumbs.Appearance)] [Category(CategoryTypes.Breadcrumbs.Appearance)]
public string Separator { get; set; } = "/"; public string Separator { get; set; } = "/";
/// <summary>
/// Custom expander icon.
/// </summary>
[Parameter]
[Category(CategoryTypes.Breadcrumbs.Appearance)]
public string ExpanderIcon { get; set; } = Icons.Material.Filled.SettingsEthernet;
#endregion
#region Styling
[Parameter]
public string ClassList { get; set; } = string.Empty;
private string Classname => new CssBuilder("breadcrumbs")
.AddClass("typography-body1")
.AddClass(ClassList)
.Build();
internal static string GetItemClassname(BreadcrumbItem item)
{
return new CssBuilder("breadcrumb-item")
.AddClass("disabled", item.Disabled)
.Build();
}
/// <summary> /// <summary>
/// Specifies a RenderFragment to use as the separator. /// Specifies a RenderFragment to use as the separator.
/// </summary> /// </summary>
@ -34,6 +74,8 @@ public partial class Breadcrumbs : UIComponent
[Category(CategoryTypes.Breadcrumbs.Behavior)] [Category(CategoryTypes.Breadcrumbs.Behavior)]
public RenderFragment<BreadcrumbItem> ItemTemplate { get; set; } public RenderFragment<BreadcrumbItem> ItemTemplate { get; set; }
public bool Collapsed { get; private set; } = true;
/// <summary> /// <summary>
/// Controls when (and if) the breadcrumbs will automatically collapse. /// Controls when (and if) the breadcrumbs will automatically collapse.
/// </summary> /// </summary>
@ -41,32 +83,6 @@ public partial class Breadcrumbs : UIComponent
[Category(CategoryTypes.Breadcrumbs.Behavior)] [Category(CategoryTypes.Breadcrumbs.Behavior)]
public byte? MaxItems { get; set; } public byte? MaxItems { get; set; }
/// <summary> #endregion
/// Custom expander icon.
/// </summary>
[Parameter]
[Category(CategoryTypes.Breadcrumbs.Appearance)]
public string ExpanderIcon { get; set; } = Icons.Material.Filled.SettingsEthernet;
public bool Collapsed { get; private set; } = true;
private string Classname => new CssBuilder("breadcrumbs")
.AddClass("typography-body1")
.AddClass(AdditionalClassList)
.Build();
internal static string GetItemClassname(BreadcrumbItem item)
{
return new CssBuilder("breadcrumb-item")
.AddClass("disabled", item.Disabled)
.Build();
}
internal void Expand()
{
if (!Collapsed)
return;
Collapsed = false;
StateHasChanged();
}
} }

@ -6,18 +6,37 @@ namespace Connected.Components;
public partial class BreakpointProvider : UIComponent, IAsyncDisposable public partial class BreakpointProvider : UIComponent, IAsyncDisposable
{ {
#region Variables
private Guid _breakPointListenerSubscriptionId; private Guid _breakPointListenerSubscriptionId;
public Breakpoint Breakpoint { get; private set; } = Breakpoint.Always; public Breakpoint Breakpoint { get; private set; } = Breakpoint.Always;
[Parameter] public EventCallback<Breakpoint> OnBreakpointChanged { get; set; }
[Inject] public IBreakpointService Service { get; set; } [Inject] public IBreakpointService Service { get; set; }
#endregion
#region Events
private void SetBreakpointCallback(Breakpoint breakpoint)
{
InvokeAsync(() =>
{
Breakpoint = breakpoint;
OnBreakpointChanged.InvokeAsync(breakpoint);
StateHasChanged();
}).AndForget();
}
[Parameter] public EventCallback<Breakpoint> OnBreakpointChanged { get; set; }
#endregion
#region Content
[Parameter] [Parameter]
[Category(CategoryTypes.BreakpointProvider.Behavior)] [Category(CategoryTypes.BreakpointProvider.Behavior)]
public RenderFragment ChildContent { get; set; } public RenderFragment ChildContent { get; set; }
#endregion
#region Lifecycle
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
await base.OnAfterRenderAsync(firstRender); await base.OnAfterRenderAsync(firstRender);
@ -32,15 +51,9 @@ public partial class BreakpointProvider : UIComponent, IAsyncDisposable
} }
} }
private void SetBreakpointCallback(Breakpoint breakpoint)
{
InvokeAsync(() =>
{
Breakpoint = breakpoint;
OnBreakpointChanged.InvokeAsync(breakpoint);
StateHasChanged();
}).AndForget();
}
public async ValueTask DisposeAsync() => await Service.Unsubscribe(_breakPointListenerSubscriptionId); public async ValueTask DisposeAsync() => await Service.Unsubscribe(_breakPointListenerSubscriptionId);
#endregion
} }

@ -5,26 +5,16 @@ namespace Connected.Components;
public partial class Button : ButtonBase public partial class Button : ButtonBase
{ {
/// <summary>
/// Contains the default container classlist and the user defined classes.
/// </summary>
private CssBuilder CompiledClassList
{
get
{
return new CssBuilder("button-root mud-button")
.AddClass($"button-{Variant}")
.AddClass(ClassList);
}
}
#region Styling properties #region Content
/// <summary> /// <summary>
/// Child content of component. /// Child content of component.
/// </summary> /// </summary>
[Parameter] [Parameter]
public RenderFragment? ChildContent { get; set; } public RenderFragment? ChildContent { get; set; }
#endregion
#region Styling
/// <summary> /// <summary>
/// A space separated list of class names, added on top of the default class list. /// A space separated list of class names, added on top of the default class list.
/// </summary> /// </summary>
@ -36,6 +26,18 @@ public partial class Button : ButtonBase
/// </summary> /// </summary>
[Parameter] [Parameter]
public Variant Variant { get; set; } = Variant.Text; public Variant Variant { get; set; } = Variant.Text;
/// <summary>
/// Contains the default container classlist and the user defined classes.
/// </summary>
private CssBuilder CompiledClassList
{
get
{
return new CssBuilder("button-root mud-button")
.AddClass($"button-{Variant}")
.AddClass(ClassList);
}
}
#endregion #endregion
} }

@ -36,9 +36,13 @@ public abstract class ButtonBase : UIComponent
/// </summary> /// </summary>
[CascadingParameter] [CascadingParameter]
protected IActivatable? Activateable { get; set; } protected IActivatable? Activateable { get; set; }
#endregion
#region Styling properties /// <summary>
/// The HTML element that will be rendered in the root by the component
/// By default, is a button.
/// </summary>
protected string HtmlTag => ButtonType.ToString().ToLower();
#endregion #endregion
#region Behavior properties #region Behavior properties
@ -65,13 +69,6 @@ public abstract class ButtonBase : UIComponent
/// </summary> /// </summary>
[Parameter] [Parameter]
public bool Disabled { get; set; } public bool Disabled { get; set; }
#endregion
/// <summary>
/// The HTML element that will be rendered in the root by the component
/// By default, is a button.
/// </summary>
protected string HtmlTag => ButtonType.ToString().ToLower();
/// <summary> /// <summary>
/// Indicates whether the internal click propagation should be disabled, /// Indicates whether the internal click propagation should be disabled,
@ -79,5 +76,6 @@ public abstract class ButtonBase : UIComponent
/// </summary> /// </summary>
protected bool PreventOnClickPropagation => string.Compare(HtmlTag, "button", true) == 0; protected bool PreventOnClickPropagation => string.Compare(HtmlTag, "button", true) == 0;
#endregion
} }

@ -7,71 +7,72 @@ namespace Connected.Components;
public partial class Fab : ButtonBase public partial class Fab : ButtonBase
{ {
protected string Classname =>
new CssBuilder("button-root mud-fab")
.AddClass($"fab-extended", !string.IsNullOrEmpty(Label))
.AddClass($"fab-{Color.ToDescription()}")
.AddClass($"fab-size-{Size.ToDescription()}")
.Build();
#region Content
/// <summary> /// <summary>
/// The color of the component. It supports the theme colors. /// If applied Glyph will be added at the start of the component.
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.Button.Appearance)] [Category(CategoryTypes.Button.Behavior)]
public ThemeColor Color { get; set; } = ThemeColor.Default; public string StartIcon { get; set; }
/// <summary> /// <summary>
/// The Size of the component. /// If applied Glyph will be added at the end of the component.
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.Button.Appearance)] [Category(CategoryTypes.Button.Behavior)]
public Size Size { get; set; } = Size.Large; public string EndIcon { get; set; }
/// <summary>
/// If applied Glyph will be added at the start of the component.
/// </summary>
[Obsolete("This property is obsolete. Use StartIcon instead.")][Parameter] public string Icon { get => StartIcon; set => StartIcon = value; }
/// <summary> /// <summary>
/// If applied Glyph will be added at the start of the component. /// If applied the text will be added to the component.
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.Button.Behavior)] [Category(CategoryTypes.Button.Behavior)]
public string StartIcon { get; set; } public string Label { get; set; }
/// <summary> /// <summary>
/// If applied Glyph will be added at the end of the component. /// GlyphTitle of the icon used for accessibility.
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.Button.Behavior)] [Category(CategoryTypes.Button.Behavior)]
public string EndIcon { get; set; } public string Title { get; set; }
#endregion
#region Styling
protected string Classname =>
new CssBuilder("button-root mud-fab")
.AddClass($"fab-extended", !string.IsNullOrEmpty(Label))
.AddClass($"fab-{Color.ToDescription()}")
.AddClass($"fab-size-{Size.ToDescription()}")
.Build();
/// <summary> /// <summary>
/// The color of the icon. It supports the theme colors. /// The color of the component. It supports the theme colors.
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.Button.Appearance)] [Category(CategoryTypes.Button.Appearance)]
public ThemeColor IconColor { get; set; } = ThemeColor.Inherit; public ThemeColor Color { get; set; } = ThemeColor.Default;
/// <summary> /// <summary>
/// The size of the icon. /// The Size of the component.
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.Button.Appearance)] [Category(CategoryTypes.Button.Appearance)]
public Size IconSize { get; set; } = Size.Medium; public Size Size { get; set; } = Size.Large;
/// <summary> /// <summary>
/// If applied the text will be added to the component. /// The color of the icon. It supports the theme colors.
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.Button.Behavior)] [Category(CategoryTypes.Button.Appearance)]
public string Label { get; set; } public ThemeColor IconColor { get; set; } = ThemeColor.Inherit;
/// <summary> /// <summary>
/// GlyphTitle of the icon used for accessibility. /// The size of the icon.
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.Button.Behavior)] [Category(CategoryTypes.Button.Appearance)]
public string Title { get; set; } public Size IconSize { get; set; } = Size.Medium;
#endregion
} }

@ -5,28 +5,7 @@ namespace Connected.Components;
public partial class IconButton : ButtonBase public partial class IconButton : ButtonBase
{ {
/// <summary> #region Content
/// Contains the default container classlist and the user defined classes.
/// </summary>
private CssBuilder CompiledClassList
{
get
{
return new CssBuilder("button-root glyph-button")
.AddClass(ClassList);
}
}
/// <summary>
/// Child content of component, only shows if Glyph is null or Empty.
/// </summary>
[Parameter]
public RenderFragment? ChildContent { get; set; }
#region EventCallbacks
#endregion
#region Content placeholders
/// <summary> /// <summary>
/// The Icon that will be used in the component. /// The Icon that will be used in the component.
/// </summary> /// </summary>
@ -38,9 +17,16 @@ public partial class IconButton : ButtonBase
/// </summary> /// </summary>
[Parameter] [Parameter]
public string? IconTitle { get; set; } public string? IconTitle { get; set; }
/// <summary>
/// Child content of component, only shows if Glyph is null or Empty.
/// </summary>
[Parameter]
public RenderFragment? ChildContent { get; set; }
#endregion #endregion
#region Styling properties #region Styling
/// <summary> /// <summary>
/// A space separated list of class names, added on top of the default class list. /// A space separated list of class names, added on top of the default class list.
/// </summary> /// </summary>
@ -52,5 +38,17 @@ public partial class IconButton : ButtonBase
/// </summary> /// </summary>
[Parameter] [Parameter]
public Variant Variant { get; set; } = Variant.Text; public Variant Variant { get; set; } = Variant.Text;
/// <summary>
/// Contains the default container classlist and the user defined classes.
/// </summary>
private CssBuilder CompiledClassList
{
get
{
return new CssBuilder("button-root glyph-button")
.AddClass(ClassList);
}
}
#endregion #endregion
} }

@ -6,8 +6,8 @@
ClassList="@ClassList" ClassList="@ClassList"
Clicked="Toggle" Clicked="Toggle"
Disabled="Disabled" Disabled="Disabled"
Icon="@(Toggled ? ToggledGlyph : Glyph)" Icon="@(Toggled ? ToggledIcon : Icon)"
IconTitle="@(Toggled && ToggledGlyphTitle != null ? ToggledGlyphTitle : GlyphTitle)" IconTitle="@(Toggled && ToggledIconTitle != null ? ToggledIconTitle : IconTitle)"
Variant="Variant" Variant="Variant"
@attributes="CustomAttributes" @attributes="CustomAttributes"
/> />

@ -4,41 +4,63 @@ namespace Connected.Components;
public partial class ToggleIconButton : UIComponent public partial class ToggleIconButton : UIComponent
{ {
#region EventCallbacks #region Events
/// <summary> /// <summary>
/// Fires whenever toggled is changed. /// Fires whenever toggled is changed.
/// </summary> /// </summary>
[Parameter] [Parameter]
public EventCallback<bool> ToggledChanged { get; set; } public EventCallback<bool> ToggledChanged { get; set; }
public async Task Toggle()
{
await SetToggledAsync(!Toggled);
}
protected internal async Task SetToggledAsync(bool toggled)
{
if (Disabled)
return;
if (Toggled != toggled)
{
Toggled = toggled;
if (!ToggledChanged.HasDelegate)
return;
await ToggledChanged.InvokeAsync(Toggled);
}
}
#endregion #endregion
#region Content placeholders #region Content
/// <summary> /// <summary>
/// The glyph that will be used in the untoggled state. /// The glyph that will be used in the untoggled state.
/// </summary> /// </summary>
[Parameter] [Parameter]
public string? Glyph { get; set; } public string? Icon { get; set; }
/// <summary> /// <summary>
/// GlyphTitle of the icon used for accessibility. /// GlyphTitle of the icon used for accessibility.
/// </summary> /// </summary>
[Parameter] [Parameter]
public string? GlyphTitle { get; set; } public string? IconTitle { get; set; }
/// <summary> /// <summary>
/// The glyph that will be used in the toggled state. /// The glyph that will be used in the toggled state.
/// </summary> /// </summary>
[Parameter] [Parameter]
public string? ToggledGlyph { get; set; } public string? ToggledIcon { get; set; }
/// <summary> /// <summary>
/// GlyphTitle used in toggled state, if different. /// GlyphTitle used in toggled state, if different.
/// </summary> /// </summary>
[Parameter] [Parameter]
public string? ToggledGlyphTitle { get; set; } public string? ToggledIconTitle { get; set; }
#endregion #endregion
#region Styling properties #region Styling
/// <summary> /// <summary>
/// A space separated list of class names, added on top of the default class list. /// A space separated list of class names, added on top of the default class list.
/// </summary> /// </summary>
@ -50,9 +72,7 @@ public partial class ToggleIconButton : UIComponent
/// </summary> /// </summary>
[Parameter] [Parameter]
public Variant Variant { get; set; } = Variant.Text; public Variant Variant { get; set; } = Variant.Text;
#endregion
#region Behavior properties
/// <summary> /// <summary>
/// If true, the button will be disabled. /// If true, the button will be disabled.
/// </summary> /// </summary>
@ -64,26 +84,6 @@ public partial class ToggleIconButton : UIComponent
/// </summary> /// </summary>
[Parameter] [Parameter]
public bool Toggled { get; set; } public bool Toggled { get; set; }
#endregion
public async Task Toggle()
{
await SetToggledAsync(!Toggled);
}
protected internal async Task SetToggledAsync(bool toggled)
{
if (Disabled)
return;
if (Toggled != toggled) #endregion
{
Toggled = toggled;
if (!ToggledChanged.HasDelegate)
return;
await ToggledChanged.InvokeAsync(Toggled);
}
}
} }

@ -7,37 +7,17 @@ namespace Connected.Components;
public partial class ButtonGroup : UIComponent public partial class ButtonGroup : UIComponent
{ {
protected string Classname =>
new CssBuilder("button-group-root")
.AddClass($"button-group-override-styles", OverrideStyles)
.AddClass($"button-group-{Variant.ToDescription()}")
.AddClass($"button-group-{Variant.ToDescription()}-{Color.ToDescription()}")
.AddClass($"button-group-{Variant.ToDescription()}-size-{Size.ToDescription()}")
.AddClass($"button-group-vertical", VerticalAlign)
.AddClass($"button-group-horizontal", !VerticalAlign)
.AddClass($"button-group-disable-elevation", DisableElevation)
.AddClass($"button-group-rtl", RightToLeft)
.AddClass(AdditionalClassList)
.Build();
[CascadingParameter(Name = "RightToLeft")]
public bool RightToLeft { get; set; }
/// <summary>
/// If true, the button group will override the styles of the individual buttons.
/// </summary>
[Parameter]
[Category(CategoryTypes.ButtonGroup.Appearance)]
public bool OverrideStyles { get; set; } = true;
#region Content
/// <summary> /// <summary>
/// Child content of component. /// Child content of component.
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.ButtonGroup.Behavior)] [Category(CategoryTypes.ButtonGroup.Behavior)]
public RenderFragment ChildContent { get; set; } public RenderFragment ChildContent { get; set; }
#endregion
#region Styling
/// <summary> /// <summary>
/// If true, the button group will be displayed vertically. /// If true, the button group will be displayed vertically.
/// </summary> /// </summary>
@ -72,4 +52,30 @@ public partial class ButtonGroup : UIComponent
[Parameter] [Parameter]
[Category(CategoryTypes.ButtonGroup.Appearance)] [Category(CategoryTypes.ButtonGroup.Appearance)]
public Variant Variant { get; set; } = Variant.Text; public Variant Variant { get; set; } = Variant.Text;
protected string Classname =>
new CssBuilder("button-group-root")
.AddClass($"button-group-override-styles", OverrideStyles)
.AddClass($"button-group-{Variant.ToDescription()}")
.AddClass($"button-group-{Variant.ToDescription()}-{Color.ToDescription()}")
.AddClass($"button-group-{Variant.ToDescription()}-size-{Size.ToDescription()}")
.AddClass($"button-group-vertical", VerticalAlign)
.AddClass($"button-group-horizontal", !VerticalAlign)
.AddClass($"button-group-disable-elevation", DisableElevation)
.AddClass($"button-group-rtl", RightToLeft)
.AddClass(AdditionalClassList)
.Build();
[CascadingParameter(Name = "RightToLeft")]
public bool RightToLeft { get; set; }
/// <summary>
/// If true, the button group will override the styles of the individual buttons.
/// </summary>
[Parameter]
[Category(CategoryTypes.ButtonGroup.Appearance)]
public bool OverrideStyles { get; set; } = true;
#endregion
} }

@ -6,6 +6,16 @@ namespace Connected.Components;
public partial class Card : UIComponent public partial class Card : UIComponent
{ {
#region Content
/// <summary>
/// Child content of the component.
/// </summary>
[Parameter]
[Category(CategoryTypes.Card.Behavior)]
public RenderFragment ChildContent { get; set; }
#endregion
#region Styling
protected string Classname => protected string Classname =>
new CssBuilder("card") new CssBuilder("card")
.AddClass(AdditionalClassList) .AddClass(AdditionalClassList)
@ -31,11 +41,6 @@ public partial class Card : UIComponent
[Parameter] [Parameter]
[Category(CategoryTypes.Card.Appearance)] [Category(CategoryTypes.Card.Appearance)]
public bool Outlined { get; set; } public bool Outlined { get; set; }
#endregion
/// <summary>
/// Child content of the component.
/// </summary>
[Parameter]
[Category(CategoryTypes.Card.Behavior)]
public RenderFragment ChildContent { get; set; }
} }

@ -6,15 +6,22 @@ namespace Connected.Components;
public partial class CardActions : UIComponent public partial class CardActions : UIComponent
{ {
#region Style
protected string Classname => protected string Classname =>
new CssBuilder("card-actions") new CssBuilder("card-actions")
.AddClass(AdditionalClassList) .AddClass(AdditionalClassList)
.Build(); .Build();
#endregion
#region Content
/// <summary> /// <summary>
/// Child content of the component. /// Child content of the component.
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.Card.Behavior)] [Category(CategoryTypes.Card.Behavior)]
public RenderFragment ChildContent { get; set; } public RenderFragment ChildContent { get; set; }
#endregion
} }

@ -6,15 +6,22 @@ namespace Connected.Components;
public partial class CardContent : UIComponent public partial class CardContent : UIComponent
{ {
#region Style
protected string Classname => protected string Classname =>
new CssBuilder("card-content") new CssBuilder("card-content")
.AddClass(AdditionalClassList) .AddClass(AdditionalClassList)
.Build(); .Build();
#endregion
#region Content
/// <summary> /// <summary>
/// Child content of the component. /// Child content of the component.
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.Card.Behavior)] [Category(CategoryTypes.Card.Behavior)]
public RenderFragment ChildContent { get; set; } public RenderFragment ChildContent { get; set; }
#endregion
} }

@ -6,6 +6,7 @@ namespace Connected.Components;
public partial class CardHeader : UIComponent public partial class CardHeader : UIComponent
{ {
#region Style
protected string Classname => protected string Classname =>
new CssBuilder("card-header") new CssBuilder("card-header")
.AddClass(AdditionalClassList) .AddClass(AdditionalClassList)
@ -32,10 +33,16 @@ public partial class CardHeader : UIComponent
[Category(CategoryTypes.Card.Behavior)] [Category(CategoryTypes.Card.Behavior)]
public RenderFragment CardHeaderActions { get; set; } public RenderFragment CardHeaderActions { get; set; }
#endregion
#region Content
/// <summary> /// <summary>
/// Optional child content /// Optional child content
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.Card.Behavior)] [Category(CategoryTypes.Card.Behavior)]
public RenderFragment ChildContent { get; set; } public RenderFragment ChildContent { get; set; }
#endregion
} }

@ -6,6 +6,7 @@ namespace Connected.Components;
public partial class CardMedia : UIComponent public partial class CardMedia : UIComponent
{ {
#region Style
protected string StyleString => protected string StyleString =>
StyleBuilder.Default($"background-image:url(\"{Image}\");height: {Height}px;") StyleBuilder.Default($"background-image:url(\"{Image}\");height: {Height}px;")
.Build(); .Build();
@ -16,23 +17,30 @@ public partial class CardMedia : UIComponent
.Build(); .Build();
/// <summary> /// <summary>
/// GlyphTitle of the image used for accessibility. /// Specifies the height of the image in px.
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.Card.Behavior)] [Category(CategoryTypes.Card.Behavior)]
public string Title { get; set; } public int Height { get; set; } = 300;
#endregion
#region Content
/// <summary> /// <summary>
/// Specifies the path to the image. /// GlyphTitle of the image used for accessibility.
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.Card.Behavior)] [Category(CategoryTypes.Card.Behavior)]
public string Image { get; set; } public string Title { get; set; }
/// <summary> /// <summary>
/// Specifies the height of the image in px. /// Specifies the path to the image.
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.Card.Behavior)] [Category(CategoryTypes.Card.Behavior)]
public int Height { get; set; } = 300; public string Image { get; set; }
#endregion
} }

@ -8,6 +8,102 @@ namespace Connected.Components;
public partial class Carousel<TData> : BindableItemsControlBase<CarouselItem, TData>, IAsyncDisposable public partial class Carousel<TData> : BindableItemsControlBase<CarouselItem, TData>, IAsyncDisposable
{ {
#region Variables
private Timer _timer;
private bool _autoCycle = true;
private ThemeColor _currentColor = ThemeColor.Inherit;
private TimeSpan _cycleTimeout = TimeSpan.FromSeconds(5);
private void TimerElapsed(object stateInfo) => InvokeAsync(async () => await TimerTickAsync());
#endregion
#region Events
/// <summary>
/// Called when selected Index changed on base class
/// </summary>
protected override void SelectionChanged()
{
InvokeAsync(async () => await ResetTimerAsync());
_currentColor = SelectedContainer?.Color ?? ThemeColor.Inherit;
}
//When an item is added, it automatically checks the color
public override void AddItem(CarouselItem item)
{
Items.Add(item);
if (Items.Count - 1 == SelectedIndex)
{
_currentColor = item.Color;
StateHasChanged();
}
}
/// <summary>
/// Provides Selection changes by horizontal swipe gesture
/// </summary>
private void OnSwipe(SwipeDirection direction)
{
if (!EnableSwipeGesture)
{
return;
}
switch (direction)
{
case SwipeDirection.LeftToRight:
if (RightToLeft) Next();
else Previous();
break;
case SwipeDirection.RightToLeft:
if (RightToLeft) Previous();
else Next();
break;
}
}
/// <summary>
/// Immediately starts the AutoCycle timer
/// </summary>
private ValueTask StartTimerAsync()
{
if (AutoCycle)
_timer?.Change(AutoCycleTime, TimeSpan.Zero);
return ValueTask.CompletedTask;
}
/// <summary>
/// Immediately stops the AutoCycle timer
/// </summary>
private ValueTask StopTimerAsync()
{
_timer?.Change(Timeout.Infinite, Timeout.Infinite);
return ValueTask.CompletedTask;
}
/// <summary>
/// Stops and restart the AutoCycle timer
/// </summary>
private async ValueTask ResetTimerAsync()
{
await StopTimerAsync();
await StartTimerAsync();
}
/// <summary>
/// Changes the SelectedIndex to a next one (or restart on 0)
/// </summary>
private async ValueTask TimerTickAsync()
{
await InvokeAsync(Next);
}
#endregion
#region Styling
protected string Classname => protected string Classname =>
new CssBuilder("carousel") new CssBuilder("carousel")
.AddClass($"carousel-{(BulletsColor ?? _currentColor).ToDescription()}") .AddClass($"carousel-{(BulletsColor ?? _currentColor).ToDescription()}")
@ -26,12 +122,6 @@ public partial class Carousel<TData> : BindableItemsControlBase<CarouselItem, TD
.AddClass(BulletsClass) .AddClass(BulletsClass)
.Build(); .Build();
private Timer _timer;
private bool _autoCycle = true;
private ThemeColor _currentColor = ThemeColor.Inherit;
private TimeSpan _cycleTimeout = TimeSpan.FromSeconds(5);
private void TimerElapsed(object stateInfo) => InvokeAsync(async () => await TimerTickAsync());
private static Position ConvertPosition(Position position) private static Position ConvertPosition(Position position)
{ {
return position switch return position switch
@ -61,12 +151,6 @@ public partial class Carousel<TData> : BindableItemsControlBase<CarouselItem, TD
[Category(CategoryTypes.Carousel.Appearance)] [Category(CategoryTypes.Carousel.Appearance)]
public Position ArrowsPosition { get; set; } = Position.Center; public Position ArrowsPosition { get; set; } = Position.Center;
/// <summary>
/// Gets or Sets if bar with Bullets must be visible
/// </summary>
[Category(CategoryTypes.Carousel.Behavior)]
[Parameter] public bool ShowBullets { get; set; } = true;
/// <summary> /// <summary>
/// Sets the position of the bullets. By default, the position is the Bottom position /// Sets the position of the bullets. By default, the position is the Bottom position
/// </summary> /// </summary>
@ -80,16 +164,6 @@ public partial class Carousel<TData> : BindableItemsControlBase<CarouselItem, TD
[Category(CategoryTypes.Carousel.Appearance)] [Category(CategoryTypes.Carousel.Appearance)]
[Parameter] public ThemeColor? BulletsColor { get; set; } [Parameter] public ThemeColor? BulletsColor { get; set; }
/// <summary>
/// Gets or Sets if bottom bar with Delimiters must be visible.
/// Deprecated, use ShowBullets instead.
/// </summary>
[Category(CategoryTypes.Carousel.Behavior)]
[Obsolete($"Use {nameof(ShowBullets)} instead", false)]
[ExcludeFromCodeCoverage]
[Parameter] public bool ShowDelimiters { get => ShowBullets; set => ShowBullets = value; }
/// <summary> /// <summary>
/// Gets or Sets the Delimiters color. /// Gets or Sets the Delimiters color.
/// If not set, the color is determined based on the <see cref="MudCarouselItem.Color"/> property of the active child. /// If not set, the color is determined based on the <see cref="MudCarouselItem.Color"/> property of the active child.
@ -100,48 +174,6 @@ public partial class Carousel<TData> : BindableItemsControlBase<CarouselItem, TD
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
[Parameter] public ThemeColor? DelimitersColor { get => BulletsColor; set => BulletsColor = value; } [Parameter] public ThemeColor? DelimitersColor { get => BulletsColor; set => BulletsColor = value; }
/// <summary>
/// Gets or Sets automatic cycle on item collection.
/// </summary>
[Parameter]
[Category(CategoryTypes.Carousel.Behavior)]
public bool AutoCycle
{
get => _autoCycle;
set
{
_autoCycle = value;
if (_autoCycle)
InvokeAsync(async () => await ResetTimerAsync());
else
InvokeAsync(async () => await StopTimerAsync());
}
}
/// <summary>
/// Gets or Sets the Auto Cycle time
/// </summary>
[Parameter]
[Category(CategoryTypes.Carousel.Behavior)]
public TimeSpan AutoCycleTime
{
get => _cycleTimeout;
set
{
_cycleTimeout = value;
if (_autoCycle == true)
InvokeAsync(async () => await ResetTimerAsync());
else
InvokeAsync(async () => await StopTimerAsync());
}
}
/// <summary> /// <summary>
/// Gets or Sets custom class(es) for 'Next' and 'Previous' arrows /// Gets or Sets custom class(es) for 'Next' and 'Previous' arrows
/// </summary> /// </summary>
@ -214,13 +246,6 @@ public partial class Carousel<TData> : BindableItemsControlBase<CarouselItem, TD
[Category(CategoryTypes.Carousel.Appearance)] [Category(CategoryTypes.Carousel.Appearance)]
[Parameter] public RenderFragment<bool> BulletTemplate { get; set; } [Parameter] public RenderFragment<bool> BulletTemplate { get; set; }
/// <summary>
/// Gets or Sets if swipe gestures are allowed for touch devices.
/// </summary>
[Category(CategoryTypes.Carousel.Behavior)]
[Parameter]
public bool EnableSwipeGesture { get; set; } = true;
/// <summary> /// <summary>
/// Gets or Sets the Template for Delimiters. /// Gets or Sets the Template for Delimiters.
/// Deprecated, use BulletsTemplate instead. /// Deprecated, use BulletsTemplate instead.
@ -230,91 +255,78 @@ public partial class Carousel<TData> : BindableItemsControlBase<CarouselItem, TD
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
[Parameter] public RenderFragment<bool> DelimiterTemplate { get => BulletTemplate; set => BulletTemplate = value; } [Parameter] public RenderFragment<bool> DelimiterTemplate { get => BulletTemplate; set => BulletTemplate = value; }
#endregion
#region Behavior
/// <summary> /// <summary>
/// Called when selected Index changed on base class /// Gets or Sets if bar with Bullets must be visible
/// </summary> /// </summary>
protected override void SelectionChanged() [Category(CategoryTypes.Carousel.Behavior)]
{ [Parameter] public bool ShowBullets { get; set; } = true;
InvokeAsync(async () => await ResetTimerAsync());
_currentColor = SelectedContainer?.Color ?? ThemeColor.Inherit;
}
//When an item is added, it automatically checks the color /// <summary>
public override void AddItem(CarouselItem item) /// Gets or Sets if bottom bar with Delimiters must be visible.
{ /// Deprecated, use ShowBullets instead.
Items.Add(item); /// </summary>
if (Items.Count - 1 == SelectedIndex) [Category(CategoryTypes.Carousel.Behavior)]
{ [Obsolete($"Use {nameof(ShowBullets)} instead", false)]
_currentColor = item.Color; [ExcludeFromCodeCoverage]
StateHasChanged(); [Parameter] public bool ShowDelimiters { get => ShowBullets; set => ShowBullets = value; }
}
}
/// <summary> /// <summary>
/// Provides Selection changes by horizontal swipe gesture /// Gets or Sets automatic cycle on item collection.
/// </summary> /// </summary>
private void OnSwipe(SwipeDirection direction) [Parameter]
[Category(CategoryTypes.Carousel.Behavior)]
public bool AutoCycle
{ {
if (!EnableSwipeGesture) get => _autoCycle;
set
{ {
return; _autoCycle = value;
}
switch (direction) if (_autoCycle)
{ InvokeAsync(async () => await ResetTimerAsync());
case SwipeDirection.LeftToRight:
if (RightToLeft) Next();
else Previous();
break;
case SwipeDirection.RightToLeft: else
if (RightToLeft) Previous(); InvokeAsync(async () => await StopTimerAsync());
else Next();
break;
} }
} }
/// <summary> /// <summary>
/// Immediately starts the AutoCycle timer /// Gets or Sets the Auto Cycle time
/// </summary> /// </summary>
private ValueTask StartTimerAsync() [Parameter]
[Category(CategoryTypes.Carousel.Behavior)]
public TimeSpan AutoCycleTime
{ {
if (AutoCycle) get => _cycleTimeout;
_timer?.Change(AutoCycleTime, TimeSpan.Zero); set
{
_cycleTimeout = value;
return ValueTask.CompletedTask; if (_autoCycle == true)
} InvokeAsync(async () => await ResetTimerAsync());
/// <summary> else
/// Immediately stops the AutoCycle timer InvokeAsync(async () => await StopTimerAsync());
/// </summary>
private ValueTask StopTimerAsync()
{
_timer?.Change(Timeout.Infinite, Timeout.Infinite);
return ValueTask.CompletedTask;
} }
/// <summary>
/// Stops and restart the AutoCycle timer
/// </summary>
private async ValueTask ResetTimerAsync()
{
await StopTimerAsync();
await StartTimerAsync();
} }
/// <summary> /// <summary>
/// Changes the SelectedIndex to a next one (or restart on 0) /// Gets or Sets if swipe gestures are allowed for touch devices.
/// </summary> /// </summary>
private async ValueTask TimerTickAsync() [Category(CategoryTypes.Carousel.Behavior)]
{ [Parameter]
await InvokeAsync(Next); public bool EnableSwipeGesture { get; set; } = true;
} #endregion
#region Lifecycle
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
await base.OnAfterRenderAsync(firstRender); await base.OnAfterRenderAsync(firstRender);
@ -346,4 +358,6 @@ public partial class Carousel<TData> : BindableItemsControlBase<CarouselItem, TD
} }
} }
} }
#endregion
} }

@ -7,6 +7,19 @@ namespace Connected.Components;
public partial class CarouselItem : UIComponent, IDisposable public partial class CarouselItem : UIComponent, IDisposable
{ {
#region Variables
private bool _disposed = false;
#endregion
#region Content
[Parameter]
[Category(CategoryTypes.Carousel.Behavior)]
public RenderFragment ChildContent { get; set; }
[CascadingParameter] protected internal ItemsControlBase<CarouselItem> Parent { get; set; }
#endregion
#region Styling
protected string Classname => protected string Classname =>
new CssBuilder("carousel-item") new CssBuilder("carousel-item")
.AddClass($"carousel-item-{Color.ToDescription()}") .AddClass($"carousel-item-{Color.ToDescription()}")
@ -35,14 +48,6 @@ public partial class CarouselItem : UIComponent, IDisposable
.AddClass(AdditionalClassList) .AddClass(AdditionalClassList)
.Build(); .Build();
[Parameter]
[Category(CategoryTypes.Carousel.Behavior)]
public RenderFragment ChildContent { get; set; }
[CascadingParameter] protected internal ItemsControlBase<CarouselItem> Parent { get; set; }
[CascadingParameter(Name = "RightToLeft")] public bool RightToLeft { get; set; }
/// <summary> /// <summary>
/// The color of the component. It supports the theme colors. /// The color of the component. It supports the theme colors.
/// </summary> /// </summary>
@ -71,21 +76,24 @@ public partial class CarouselItem : UIComponent, IDisposable
[Category(CategoryTypes.Carousel.Appearance)] [Category(CategoryTypes.Carousel.Appearance)]
public string CustomTransitionExit { get; set; } public string CustomTransitionExit { get; set; }
[CascadingParameter(Name = "RightToLeft")] public bool RightToLeft { get; set; }
public bool IsVisible => Parent != null && (Parent.LastContainer == this || Parent.SelectedIndex == Parent.Items.IndexOf(this)); public bool IsVisible => Parent != null && (Parent.LastContainer == this || Parent.SelectedIndex == Parent.Items.IndexOf(this));
#endregion
#region Lifecycle
protected override Task OnInitializedAsync() protected override Task OnInitializedAsync()
{ {
Parent?.AddItem(this); Parent?.AddItem(this);
return Task.CompletedTask; return Task.CompletedTask;
} }
private bool _disposed = false;
public void Dispose() public void Dispose()
{ {
_disposed = true; _disposed = true;
Parent?.Items.Remove(this); Parent?.Items.Remove(this);
} }
#endregion
} }

@ -8,22 +8,47 @@ namespace Connected.Components;
public partial class Chart : UIComponent public partial class Chart : UIComponent
{ {
[Parameter]
[Category(CategoryTypes.Chart.Behavior)]
public double[] InputData { get; set; } = Array.Empty<double>();
[Parameter] #region Variables
[Category(CategoryTypes.Chart.Behavior)] private int _selectedIndex;
public string[] InputLabels { get; set; } = Array.Empty<string>(); #endregion
[Parameter] #region Events
[Category(CategoryTypes.Chart.Behavior)] /// <summary>
public string[] XAxisLabels { get; set; } = Array.Empty<string>(); /// Selected index of a portion of the chart.
/// </summary>
[Parameter] public EventCallback<int> SelectedIndexChanged { get; set; }
[Parameter] /// <summary>
[Category(CategoryTypes.Chart.Behavior)] /// Scales the input data to the range between 0 and 1
public List<ChartSeries> ChartSeries { get; set; } = new(); /// </summary>
protected double[] GetNormalizedData()
{
if (InputData == null)
return Array.Empty<double>();
var total = InputData.Sum();
return InputData.Select(x => Math.Abs(x) / total).ToArray();
}
protected string ToS(double d, string format = null)
{
if (string.IsNullOrEmpty(format))
return d.ToString(CultureInfo.InvariantCulture);
return d.ToString(format);
}
private Position ConvertLegendPosition(Position position)
{
return position switch
{
Position.Start => RightToLeft ? Position.Right : Position.Left,
Position.End => RightToLeft ? Position.Left : Position.Right,
_ => position
};
}
#endregion
#region Styling
[Parameter] [Parameter]
[Category(CategoryTypes.Chart.Appearance)] [Category(CategoryTypes.Chart.Appearance)]
public ChartOptions ChartOptions { get; set; } = new(); public ChartOptions ChartOptions { get; set; } = new();
@ -43,13 +68,6 @@ public partial class Chart : UIComponent
[CascadingParameter(Name = "RightToLeft")] public bool RightToLeft { get; set; } [CascadingParameter(Name = "RightToLeft")] public bool RightToLeft { get; set; }
/// <summary>
/// The Type of the chart.
/// </summary>
[Parameter]
[Category(CategoryTypes.Chart.Behavior)]
public ChartType ChartType { get; set; }
/// <summary> /// <summary>
/// The Width of the chart, end with % or px. /// The Width of the chart, end with % or px.
/// </summary> /// </summary>
@ -71,17 +89,30 @@ public partial class Chart : UIComponent
[Category(CategoryTypes.Chart.Appearance)] [Category(CategoryTypes.Chart.Appearance)]
public Position LegendPosition { get; set; } = Position.Bottom; public Position LegendPosition { get; set; } = Position.Bottom;
private Position ConvertLegendPosition(Position position) #endregion
{
return position switch
{
Position.Start => RightToLeft ? Position.Right : Position.Left,
Position.End => RightToLeft ? Position.Left : Position.Right,
_ => position
};
}
private int _selectedIndex; #region Behavior
[Parameter]
[Category(CategoryTypes.Chart.Behavior)]
public double[] InputData { get; set; } = Array.Empty<double>();
[Parameter]
[Category(CategoryTypes.Chart.Behavior)]
public string[] InputLabels { get; set; } = Array.Empty<string>();
[Parameter]
[Category(CategoryTypes.Chart.Behavior)]
public string[] XAxisLabels { get; set; } = Array.Empty<string>();
[Parameter]
[Category(CategoryTypes.Chart.Behavior)]
public List<ChartSeries> ChartSeries { get; set; } = new();
/// <summary>
/// The Type of the chart.
/// </summary>
[Parameter]
[Category(CategoryTypes.Chart.Behavior)]
public ChartType ChartType { get; set; }
/// <summary> /// <summary>
/// Selected index of a portion of the chart. /// Selected index of a portion of the chart.
@ -101,29 +132,7 @@ public partial class Chart : UIComponent
} }
} }
/// <summary> #endregion
/// Selected index of a portion of the chart.
/// </summary>
[Parameter] public EventCallback<int> SelectedIndexChanged { get; set; }
/// <summary>
/// Scales the input data to the range between 0 and 1
/// </summary>
protected double[] GetNormalizedData()
{
if (InputData == null)
return Array.Empty<double>();
var total = InputData.Sum();
return InputData.Select(x => Math.Abs(x) / total).ToArray();
}
protected string ToS(double d, string format = null)
{
if (string.IsNullOrEmpty(format))
return d.ToString(CultureInfo.InvariantCulture);
return d.ToString(format);
}
} }

@ -4,6 +4,7 @@ namespace Connected.Components;
partial class Bar : Chart partial class Bar : Chart
{ {
#region Variables
[CascadingParameter] public Chart ChartParent { get; set; } [CascadingParameter] public Chart ChartParent { get; set; }
private List<SvgPath> _horizontalLines = new(); private List<SvgPath> _horizontalLines = new();
@ -16,7 +17,9 @@ partial class Bar : Chart
private List<ChartSeries> _series = new(); private List<ChartSeries> _series = new();
private List<SvgPath> _bars = new(); private List<SvgPath> _bars = new();
#endregion
#region Lifecycle
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
base.OnParametersSet(); base.OnParametersSet();
@ -147,4 +150,6 @@ partial class Bar : Chart
_legends.Add(legend); _legends.Add(legend);
} }
} }
#endregion
} }

@ -4,6 +4,7 @@ namespace Connected.Components;
partial class Donut : Chart partial class Donut : Chart
{ {
#region Variables
[CascadingParameter] public Chart ChartParent { get; set; } [CascadingParameter] public Chart ChartParent { get; set; }
private List<SvgCircle> _circles = new(); private List<SvgCircle> _circles = new();
@ -12,6 +13,10 @@ partial class Donut : Chart
protected string ParentWidth => ChartParent?.Width; protected string ParentWidth => ChartParent?.Width;
protected string ParentHeight => ChartParent?.Height; protected string ParentHeight => ChartParent?.Height;
#endregion
#region Lifecycle
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
_circles.Clear(); _circles.Clear();
@ -57,4 +62,6 @@ partial class Donut : Chart
} }
} }
#endregion
} }

@ -4,6 +4,8 @@ namespace Connected.Components;
partial class Line : Chart partial class Line : Chart
{ {
#region Variables
private const int MaxHorizontalGridLines = 100; private const int MaxHorizontalGridLines = 100;
[CascadingParameter] public Chart ChartParent { get; set; } [CascadingParameter] public Chart ChartParent { get; set; }
@ -19,6 +21,10 @@ partial class Line : Chart
private List<SvgPath> _chartLines = new(); private List<SvgPath> _chartLines = new();
#endregion
#region Lifecycle
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
base.OnParametersSet(); base.OnParametersSet();
@ -228,4 +234,6 @@ partial class Line : Chart
_legends.Add(legend); _legends.Add(legend);
} }
} }
#endregion
} }

@ -4,11 +4,16 @@ namespace Connected.Components;
partial class Pie : Chart partial class Pie : Chart
{ {
#region Variables
[CascadingParameter] public Chart ChartParent { get; set; } [CascadingParameter] public Chart ChartParent { get; set; }
private List<SvgPath> _paths = new(); private List<SvgPath> _paths = new();
private List<SvgLegend> _legends = new(); private List<SvgLegend> _legends = new();
#endregion
#region Lifecycle
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
_paths.Clear(); _paths.Clear();
@ -52,4 +57,5 @@ partial class Pie : Chart
counter += 1; counter += 1;
} }
} }
#endregion
} }

@ -9,6 +9,90 @@ namespace Connected.Components;
public partial class CheckBox<T> : BooleanInput<T> public partial class CheckBox<T> : BooleanInput<T>
{ {
#region Variables
private IKeyInterceptor _keyInterceptor;
[Inject] private IKeyInterceptorFactory _keyInterceptorFactory { get; set; }
private string _elementId = "checkbox" + Guid.NewGuid().ToString().Substring(0, 8);
#endregion
#region Events
protected override Task OnChange(ChangeEventArgs args)
{
Modified = true;
// Apply only when TriState parameter is set to true and T is bool?
if (TriState && typeof(T) == typeof(bool?))
{
// The cycle is forced with the following steps: true, false, indeterminate, true, false, indeterminate...
if (!((bool?)(object)_value).HasValue)
{
return SetBoolValueAsync(true);
}
else
{
return ((bool?)(object)_value).Value ? SetBoolValueAsync(false) : SetBoolValueAsync(default);
}
}
else
{
return SetBoolValueAsync((bool?)args.Value);
}
}
protected void HandleKeyDown(KeyboardEventArgs obj)
{
if (Disabled || ReadOnly || !KeyboardEnabled)
return;
switch (obj.Key)
{
case "Delete":
SetBoolValueAsync(false);
break;
case "Enter":
case "NumpadEnter":
SetBoolValueAsync(true);
break;
case "Backspace":
if (TriState)
{
SetBoolValueAsync(null);
}
break;
case " ":
if (BoolValue == null)
{
SetBoolValueAsync(true);
}
else if (BoolValue == true)
{
SetBoolValueAsync(false);
}
else if (BoolValue == false)
{
if (TriState == true)
{
SetBoolValueAsync(null);
}
else
{
SetBoolValueAsync(true);
}
}
break;
}
}
#endregion
#region Content
/// <summary>
/// Child content of component.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Behavior)]
public RenderFragment ChildContent { get; set; }
#endregion
#region Styling
protected string Classname => protected string Classname =>
new CssBuilder("input-control-boolean-input") new CssBuilder("input-control-boolean-input")
.AddClass(AdditionalClassList) .AddClass(AdditionalClassList)
@ -45,27 +129,6 @@ public partial class CheckBox<T> : BooleanInput<T>
[Category(CategoryTypes.Radio.Appearance)] [Category(CategoryTypes.Radio.Appearance)]
public ThemeColor? UnCheckedColor { get; set; } = null; public ThemeColor? UnCheckedColor { get; set; } = null;
/// <summary>
/// The text/label will be displayed next to the checkbox if set.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Behavior)]
public string Label { get; set; }
/// <summary>
/// The position of the text/label.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Behavior)]
public LabelPosition LabelPosition { get; set; } = LabelPosition.End;
/// <summary>
/// If true, the checkbox can be controlled with the keyboard.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Behavior)]
public bool KeyboardEnabled { get; set; } = true;
/// <summary> /// <summary>
/// If true, disables ripple effect. /// If true, disables ripple effect.
/// </summary> /// </summary>
@ -87,13 +150,6 @@ public partial class CheckBox<T> : BooleanInput<T>
[Category(CategoryTypes.FormComponent.Appearance)] [Category(CategoryTypes.FormComponent.Appearance)]
public Size Size { get; set; } = Size.Medium; public Size Size { get; set; } = Size.Medium;
/// <summary>
/// Child content of component.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Behavior)]
public RenderFragment ChildContent { get; set; }
/// <summary> /// <summary>
/// Custom checked icon, leave null for default. /// Custom checked icon, leave null for default.
/// </summary> /// </summary>
@ -115,13 +171,6 @@ public partial class CheckBox<T> : BooleanInput<T>
[Category(CategoryTypes.FormComponent.Appearance)] [Category(CategoryTypes.FormComponent.Appearance)]
public string IndeterminateIcon { get; set; } = Icons.Material.Filled.IndeterminateCheckBox; public string IndeterminateIcon { get; set; } = Icons.Material.Filled.IndeterminateCheckBox;
/// <summary>
/// Define if the checkbox can cycle again through indeterminate status.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Validation)]
public bool TriState { get; set; }
private string GetIcon() private string GetIcon()
{ {
if (BoolValue == true) if (BoolValue == true)
@ -137,77 +186,39 @@ public partial class CheckBox<T> : BooleanInput<T>
return IndeterminateIcon; return IndeterminateIcon;
} }
protected override Task OnChange(ChangeEventArgs args) #endregion
{
Modified = true;
// Apply only when TriState parameter is set to true and T is bool? #region Behavior
if (TriState && typeof(T) == typeof(bool?)) /// <summary>
{ /// The text/label will be displayed next to the checkbox if set.
// The cycle is forced with the following steps: true, false, indeterminate, true, false, indeterminate... /// </summary>
if (!((bool?)(object)_value).HasValue) [Parameter]
{ [Category(CategoryTypes.FormComponent.Behavior)]
return SetBoolValueAsync(true); public string Label { get; set; }
}
else
{
return ((bool?)(object)_value).Value ? SetBoolValueAsync(false) : SetBoolValueAsync(default);
}
}
else
{
return SetBoolValueAsync((bool?)args.Value);
}
}
protected void HandleKeyDown(KeyboardEventArgs obj) /// <summary>
{ /// The position of the text/label.
if (Disabled || ReadOnly || !KeyboardEnabled) /// </summary>
return; [Parameter]
switch (obj.Key) [Category(CategoryTypes.FormComponent.Behavior)]
{ public LabelPosition LabelPosition { get; set; } = LabelPosition.End;
case "Delete":
SetBoolValueAsync(false);
break;
case "Enter":
case "NumpadEnter":
SetBoolValueAsync(true);
break;
case "Backspace":
if (TriState)
{
SetBoolValueAsync(null);
}
break;
case " ":
if (BoolValue == null)
{
SetBoolValueAsync(true);
}
else if (BoolValue == true)
{
SetBoolValueAsync(false);
}
else if (BoolValue == false)
{
if (TriState == true)
{
SetBoolValueAsync(null);
}
else
{
SetBoolValueAsync(true);
}
}
break;
}
}
private IKeyInterceptor _keyInterceptor; /// <summary>
[Inject] private IKeyInterceptorFactory _keyInterceptorFactory { get; set; } /// If true, the checkbox can be controlled with the keyboard.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Behavior)]
public bool KeyboardEnabled { get; set; } = true;
private string _elementId = "checkbox" + Guid.NewGuid().ToString().Substring(0, 8); /// <summary>
/// Define if the checkbox can cycle again through indeterminate status.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Validation)]
public bool TriState { get; set; }
#endregion
#region Lifecycle
protected override void OnInitialized() protected override void OnInitialized()
{ {
base.OnInitialized(); base.OnInitialized();
@ -251,4 +262,6 @@ public partial class CheckBox<T> : BooleanInput<T>
} }
} }
} }
#endregion
} }

@ -10,11 +10,101 @@ namespace Connected.Components;
public partial class Chip : UIComponent, IDisposable public partial class Chip : UIComponent, IDisposable
{ {
#region Variables
private bool _isSelected; private bool _isSelected;
[Inject] public NavigationManager UriHelper { get; set; } [Inject] public NavigationManager UriHelper { get; set; }
[Inject] public IJsApiService JsApiService { get; set; } [Inject] public IJsApiService JsApiService { get; set; }
#endregion
#region Events
internal void ForceRerender() => StateHasChanged();
protected internal async Task OnClickHandler(MouseEventArgs ev)
{
if (ChipSet?.ReadOnly == true)
{
return;
}
if (ChipSet != null)
{
_ = ChipSet.OnChipClicked(this);
}
if (Href != null)
{
// TODO: use MudElement to render <a> and this code can be removed. we know that it has potential problems on iOS
if (string.IsNullOrWhiteSpace(Target))
UriHelper.NavigateTo(Href, ForceLoad);
else
await JsApiService.Open(Href, Target);
}
else
{
await OnClick.InvokeAsync(ev);
if (Command?.CanExecute(CommandParameter) ?? false)
{
Command.Execute(CommandParameter);
}
}
}
protected async Task OnCloseHandler(MouseEventArgs ev)
{
if (ChipSet?.ReadOnly == true)
{
return;
}
await OnClose.InvokeAsync(this);
ChipSet?.OnChipDeleted(this);
StateHasChanged();
}
/// <summary>
/// Chip click event, if set the chip focus, hover and click effects are applied.
/// </summary>
[Parameter] public EventCallback<MouseEventArgs> OnClick { get; set; }
/// <summary>
/// Chip delete event, if set the delete icon will be visible.
/// </summary>
[Parameter] public EventCallback<Chip> OnClose { get; set; }
#endregion
#region Content
[CascadingParameter] ChipSet ChipSet { get; set; }
/// <summary>
/// If set to a URL, clicking the button will open the referenced document. Use Target to specify where
/// </summary>
[Parameter]
[Category(CategoryTypes.Chip.ClickAction)]
public string Href { get; set; }
/// <summary>
/// The target attribute specifies where to open the link, if Href is specified. Possible values: _blank | _self | _parent | _top | <i>framename</i>
/// </summary>
[Parameter]
[Category(CategoryTypes.Chip.ClickAction)]
public string Target { get; set; }
/// <summary>
/// Command executed when the user clicks on an element.
/// </summary>
[Parameter]
[Category(CategoryTypes.Chip.ClickAction)]
public ICommand Command { get; set; }
/// <summary>
/// Command parameter.
/// </summary>
[Parameter]
[Category(CategoryTypes.Chip.ClickAction)]
public object CommandParameter { get; set; }
#endregion
#region Styling
protected string Classname => protected string Classname =>
new CssBuilder("chip") new CssBuilder("chip")
.AddClass($"chip-{GetVariant().ToDescription()}") .AddClass($"chip-{GetVariant().ToDescription()}")
@ -57,8 +147,6 @@ public partial class Chip : UIComponent, IDisposable
} }
} }
[CascadingParameter] ChipSet ChipSet { get; set; }
/// <summary> /// <summary>
/// The color of the component. /// The color of the component.
/// </summary> /// </summary>
@ -87,13 +175,6 @@ public partial class Chip : UIComponent, IDisposable
[Category(CategoryTypes.Chip.Appearance)] [Category(CategoryTypes.Chip.Appearance)]
public ThemeColor SelectedColor { get; set; } = ThemeColor.Inherit; public ThemeColor SelectedColor { get; set; } = ThemeColor.Inherit;
/// <summary>
/// Avatar Glyph, Overrides the regular Glyph if set.
/// </summary>
[Parameter]
[Category(CategoryTypes.Chip.Behavior)]
public string Avatar { get; set; }
/// <summary> /// <summary>
/// Avatar CSS Class, appends to Chips default avatar classes. /// Avatar CSS Class, appends to Chips default avatar classes.
/// </summary> /// </summary>
@ -108,20 +189,6 @@ public partial class Chip : UIComponent, IDisposable
[Category(CategoryTypes.Chip.Appearance)] [Category(CategoryTypes.Chip.Appearance)]
public bool Label { get; set; } public bool Label { get; set; }
/// <summary>
/// If true, the chip will be displayed in disabled state and no events possible.
/// </summary>
[Parameter]
[Category(CategoryTypes.Chip.Behavior)]
public bool Disabled { get; set; }
/// <summary>
/// Sets the Glyph to use.
/// </summary>
[Parameter]
[Category(CategoryTypes.Chip.Behavior)]
public string Icon { get; set; }
/// <summary> /// <summary>
/// Custom checked icon. /// Custom checked icon.
/// </summary> /// </summary>
@ -150,38 +217,51 @@ public partial class Chip : UIComponent, IDisposable
[Category(CategoryTypes.Chip.Appearance)] [Category(CategoryTypes.Chip.Appearance)]
public bool DisableRipple { get; set; } public bool DisableRipple { get; set; }
#endregion
#region Behavior
/// <summary> /// <summary>
/// Child content of component. /// Set by MudChipSet
/// </summary> /// </summary>
[Parameter] public bool IsSelected
[Category(CategoryTypes.Chip.Behavior)] {
public RenderFragment ChildContent { get; set; } get => _isSelected;
set
{
if (_isSelected == value)
return;
_isSelected = value;
StateHasChanged();
}
}
/// <summary> /// <summary>
/// If set to a URL, clicking the button will open the referenced document. Use Target to specify where (Obsolete replaced by Href) /// If false, this chip has not been seen before
/// </summary> /// </summary>
[Obsolete("Use Href Instead.", false)] public bool DefaultProcessed { get; set; }
[Parameter]
[Category(CategoryTypes.Chip.ClickAction)] /// <summary>
public string Link /// Set by MudChipSet
/// </summary>
public bool IsChecked
{ {
get => Href; get => _isSelected && ChipSet?.Filter == true;
set => Href = value;
} }
/// <summary> /// <summary>
/// If set to a URL, clicking the button will open the referenced document. Use Target to specify where /// If true, force browser to redirect outside component router-space.
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.Chip.ClickAction)] [Category(CategoryTypes.Chip.ClickAction)]
public string Href { get; set; } public bool ForceLoad { get; set; }
/// <summary> /// <summary>
/// The target attribute specifies where to open the link, if Href is specified. Possible values: _blank | _self | _parent | _top | <i>framename</i> /// If true, this chip is selected by default if used in a ChipSet.
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.Chip.ClickAction)] [Category(CategoryTypes.Chip.Behavior)]
public string Target { get; set; } public bool? Default { get; set; }
/// <summary> /// <summary>
/// A string you want to associate with the chip. If the ChildContent is not set this will be shown as chip text. /// A string you want to associate with the chip. If the ChildContent is not set this will be shown as chip text.
@ -199,70 +279,51 @@ public partial class Chip : UIComponent, IDisposable
public object Value { get; set; } public object Value { get; set; }
/// <summary> /// <summary>
/// If true, force browser to redirect outside component router-space. /// Avatar Glyph, Overrides the regular Glyph if set.
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.Chip.ClickAction)] [Category(CategoryTypes.Chip.Behavior)]
public bool ForceLoad { get; set; } public string Avatar { get; set; }
/// <summary> /// <summary>
/// If true, this chip is selected by default if used in a ChipSet. /// If true, the chip will be displayed in disabled state and no events possible.
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.Chip.Behavior)] [Category(CategoryTypes.Chip.Behavior)]
public bool? Default { get; set; } public bool Disabled { get; set; }
/// <summary> /// <summary>
/// Command executed when the user clicks on an element. /// Sets the Glyph to use.
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.Chip.ClickAction)] [Category(CategoryTypes.Chip.Behavior)]
public ICommand Command { get; set; } public string Icon { get; set; }
/// <summary>
/// Command parameter.
/// </summary>
[Parameter]
[Category(CategoryTypes.Chip.ClickAction)]
public object CommandParameter { get; set; }
/// <summary>
/// Chip click event, if set the chip focus, hover and click effects are applied.
/// </summary>
[Parameter] public EventCallback<MouseEventArgs> OnClick { get; set; }
/// <summary> /// <summary>
/// Chip delete event, if set the delete icon will be visible. /// Child content of component.
/// </summary> /// </summary>
[Parameter] public EventCallback<Chip> OnClose { get; set; } [Parameter]
[Category(CategoryTypes.Chip.Behavior)]
public RenderFragment ChildContent { get; set; }
/// <summary> /// <summary>
/// Set by MudChipSet /// If set to a URL, clicking the button will open the referenced document. Use Target to specify where (Obsolete replaced by Href)
/// </summary> /// </summary>
public bool IsChecked [Obsolete("Use Href Instead.", false)]
[Parameter]
[Category(CategoryTypes.Chip.ClickAction)]
public string Link
{ {
get => _isSelected && ChipSet?.Filter == true; get => Href;
set => Href = value;
} }
#endregion
/// <summary> #region Lifecycle
/// If false, this chip has not been seen before
/// </summary>
public bool DefaultProcessed { get; set; }
/// <summary>
/// Set by MudChipSet
/// </summary>
public bool IsSelected
{
get => _isSelected;
set
{
if (_isSelected == value)
return;
_isSelected = value;
StateHasChanged();
}
}
protected override void OnInitialized() protected override void OnInitialized()
{ {
@ -271,53 +332,12 @@ public partial class Chip : UIComponent, IDisposable
Value = this; Value = this;
} }
protected internal async Task OnClickHandler(MouseEventArgs ev)
{
if (ChipSet?.ReadOnly == true)
{
return;
}
if (ChipSet != null)
{
_ = ChipSet.OnChipClicked(this);
}
if (Href != null)
{
// TODO: use MudElement to render <a> and this code can be removed. we know that it has potential problems on iOS
if (string.IsNullOrWhiteSpace(Target))
UriHelper.NavigateTo(Href, ForceLoad);
else
await JsApiService.Open(Href, Target);
}
else
{
await OnClick.InvokeAsync(ev);
if (Command?.CanExecute(CommandParameter) ?? false)
{
Command.Execute(CommandParameter);
}
}
}
protected async Task OnCloseHandler(MouseEventArgs ev)
{
if (ChipSet?.ReadOnly == true)
{
return;
}
await OnClose.InvokeAsync(this);
ChipSet?.OnChipDeleted(this);
StateHasChanged();
}
protected override Task OnInitializedAsync() protected override Task OnInitializedAsync()
{ {
ChipSet?.Add(this); ChipSet?.Add(this);
return base.OnInitializedAsync(); return base.OnInitializedAsync();
} }
internal void ForceRerender() => StateHasChanged();
//Exclude because we don't test to catching exception yet //Exclude because we don't test to catching exception yet
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
public void Dispose() public void Dispose()
@ -331,5 +351,5 @@ public partial class Chip : UIComponent, IDisposable
/* ignore! */ /* ignore! */
} }
} }
#endregion
} }

@ -6,158 +6,21 @@ namespace Connected.Components;
public partial class ChipSet : UIComponent, IDisposable public partial class ChipSet : UIComponent, IDisposable
{ {
#region Variables
protected string Classname =>
new CssBuilder("chipset")
.AddClass(AdditionalClassList)
.Build();
/// <summary>
/// Child content of component.
/// </summary>
[Parameter]
[Category(CategoryTypes.ChipSet.Behavior)]
public RenderFragment ChildContent { get; set; }
/// <summary>
/// Allows to select more than one chip.
/// </summary>
[Parameter]
[Category(CategoryTypes.ChipSet.Behavior)]
public bool MultiSelection { get; set; } = false;
/// <summary>
/// Will not allow to deselect the selected chip in single selection mode.
/// </summary>
[Parameter]
[Category(CategoryTypes.ChipSet.Behavior)]
public bool Mandatory { get; set; } = false;
/// <summary>
/// Will make all chips closable.
/// </summary>
[Parameter]
[Category(CategoryTypes.ChipSet.Behavior)]
public bool AllClosable { get; set; } = false;
/// <summary>
/// Will show a check-mark for the selected components.
/// </summary>
[Parameter]
[Category(CategoryTypes.ChipSet.Appearance)]
public bool Filter
{
get => _filter;
set
{
if (_filter == value)
return;
_filter = value;
StateHasChanged();
foreach (var chip in _chips)
chip.ForceRerender();
}
}
/// <summary>
/// Will make all chips read only.
/// </summary>
[Parameter]
[Category(CategoryTypes.ChipSet.Behavior)]
public bool ReadOnly { get; set; } = false;
/// <summary>
/// The currently selected chip in Choice mode
/// </summary>
[Parameter]
[Category(CategoryTypes.ChipSet.Behavior)]
public Chip SelectedChip
{
get { return _chips.OfType<Chip>().FirstOrDefault(x => x.IsSelected); }
set
{
if (value == null)
{
foreach (var chip in _chips)
{
chip.IsSelected = false;
}
}
else
{
foreach (var chip in _chips)
{
chip.IsSelected = (chip == value);
}
}
this.InvokeAsync(StateHasChanged);
}
}
/// <summary>
/// Called when the selected chip changes, in Choice mode
/// </summary>
[Parameter]
public EventCallback<Chip> SelectedChipChanged { get; set; }
/// <summary>
/// The currently selected chips in Filter mode
/// </summary>
[Parameter]
[Category(CategoryTypes.ChipSet.Behavior)]
public Chip[] SelectedChips
{
get { return _chips.OfType<Chip>().Where(x => x.IsSelected).ToArray(); }
set
{
if (value == null || value.Length == 0)
{
foreach (var chip in _chips)
{
chip.IsSelected = false;
}
}
else
{
var selected = new HashSet<Chip>(value);
foreach (var chip in _chips)
{
chip.IsSelected = selected.Contains(chip);
}
}
StateHasChanged();
}
}
protected override void OnInitialized()
{
base.OnInitialized();
if (_selectedValues == null)
_selectedValues = new HashSet<object>(_comparer);
_initialSelectedValues = new HashSet<object>(_selectedValues, _comparer);
}
private IEqualityComparer<object> _comparer; private IEqualityComparer<object> _comparer;
private HashSet<object> _selectedValues; private HashSet<object> _selectedValues;
private HashSet<object> _initialSelectedValues; private HashSet<object> _initialSelectedValues;
private HashSet<Chip> _chips = new();
private bool _filter;
private bool _disposed;
#endregion
#region Events
/// <summary> /// <summary>
/// The Comparer to use for comparing selected values internally. /// Called when the selected chip changes, in Choice mode
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.ChipSet.Behavior)] public EventCallback<Chip> SelectedChipChanged { get; set; }
public IEqualityComparer<object> Comparer
{
get => _comparer;
set
{
_comparer = value;
// Apply comparer and refresh selected values
_selectedValues = new HashSet<object>(_selectedValues, _comparer);
SelectedValues = _selectedValues;
}
}
/// <summary> /// <summary>
/// Called when the selection changed, in Filter mode /// Called when the selection changed, in Filter mode
/// </summary> /// </summary>
@ -186,7 +49,6 @@ public partial class ChipSet : UIComponent, IDisposable
/// Called whenever the selection changed /// Called whenever the selection changed
/// </summary> /// </summary>
[Parameter] public EventCallback<ICollection<object>> SelectedValuesChanged { get; set; } [Parameter] public EventCallback<ICollection<object>> SelectedValuesChanged { get; set; }
internal Task SetSelectedValues(object[] values) internal Task SetSelectedValues(object[] values)
{ {
HashSet<object> newValues = null; HashSet<object> newValues = null;
@ -217,7 +79,6 @@ public partial class ChipSet : UIComponent, IDisposable
/// </summary> /// </summary>
[Parameter] [Parameter]
public EventCallback<Chip> OnClose { get; set; } public EventCallback<Chip> OnClose { get; set; }
internal Task Add(Chip chip) internal Task Add(Chip chip)
{ {
_chips.Add(chip); _chips.Add(chip);
@ -257,9 +118,6 @@ public partial class ChipSet : UIComponent, IDisposable
} }
} }
private HashSet<Chip> _chips = new();
private bool _filter;
internal Task OnChipClicked(Chip chip) internal Task OnChipClicked(Chip chip)
{ {
var wasSelected = chip.IsSelected; var wasSelected = chip.IsSelected;
@ -307,12 +165,7 @@ public partial class ChipSet : UIComponent, IDisposable
OnClose.InvokeAsync(chip); OnClose.InvokeAsync(chip);
} }
protected override async void OnAfterRender(bool firstRender)
{
if (firstRender)
await SelectDefaultChips();
base.OnAfterRender(firstRender);
}
private async Task SelectDefaultChips() private async Task SelectDefaultChips()
{ {
@ -332,11 +185,165 @@ public partial class ChipSet : UIComponent, IDisposable
} }
} }
} }
#endregion
private bool _disposed; #region Content
/// <summary>
/// Child content of component.
/// </summary>
[Parameter]
[Category(CategoryTypes.ChipSet.Behavior)]
public RenderFragment ChildContent { get; set; }
/// <summary>
/// The currently selected chip in Choice mode
/// </summary>
[Parameter]
[Category(CategoryTypes.ChipSet.Behavior)]
public Chip SelectedChip
{
get { return _chips.OfType<Chip>().FirstOrDefault(x => x.IsSelected); }
set
{
if (value == null)
{
foreach (var chip in _chips)
{
chip.IsSelected = false;
}
}
else
{
foreach (var chip in _chips)
{
chip.IsSelected = (chip == value);
}
}
this.InvokeAsync(StateHasChanged);
}
}
/// <summary>
/// The currently selected chips in Filter mode
/// </summary>
[Parameter]
[Category(CategoryTypes.ChipSet.Behavior)]
public Chip[] SelectedChips
{
get { return _chips.OfType<Chip>().Where(x => x.IsSelected).ToArray(); }
set
{
if (value == null || value.Length == 0)
{
foreach (var chip in _chips)
{
chip.IsSelected = false;
}
}
else
{
var selected = new HashSet<Chip>(value);
foreach (var chip in _chips)
{
chip.IsSelected = selected.Contains(chip);
}
}
StateHasChanged();
}
}
#endregion
#region Styling
protected string Classname =>
new CssBuilder("chipset")
.AddClass(AdditionalClassList)
.Build();
#endregion
#region Behavior
/// <summary>
/// The Comparer to use for comparing selected values internally.
/// </summary>
[Parameter]
[Category(CategoryTypes.ChipSet.Behavior)]
public IEqualityComparer<object> Comparer
{
get => _comparer;
set
{
_comparer = value;
// Apply comparer and refresh selected values
_selectedValues = new HashSet<object>(_selectedValues, _comparer);
SelectedValues = _selectedValues;
}
}
/// <summary>
/// Will make all chips read only.
/// </summary>
[Parameter]
[Category(CategoryTypes.ChipSet.Behavior)]
public bool ReadOnly { get; set; } = false;
/// <summary>
/// Allows to select more than one chip.
/// </summary>
[Parameter]
[Category(CategoryTypes.ChipSet.Behavior)]
public bool MultiSelection { get; set; } = false;
/// <summary>
/// Will not allow to deselect the selected chip in single selection mode.
/// </summary>
[Parameter]
[Category(CategoryTypes.ChipSet.Behavior)]
public bool Mandatory { get; set; } = false;
/// <summary>
/// Will make all chips closable.
/// </summary>
[Parameter]
[Category(CategoryTypes.ChipSet.Behavior)]
public bool AllClosable { get; set; } = false;
/// <summary>
/// Will show a check-mark for the selected components.
/// </summary>
[Parameter]
[Category(CategoryTypes.ChipSet.Appearance)]
public bool Filter
{
get => _filter;
set
{
if (_filter == value)
return;
_filter = value;
StateHasChanged();
foreach (var chip in _chips)
chip.ForceRerender();
}
}
#endregion
#region Lifecycle
public void Dispose() public void Dispose()
{ {
_disposed = true; _disposed = true;
} }
protected override void OnInitialized()
{
base.OnInitialized();
if (_selectedValues == null)
_selectedValues = new HashSet<object>(_comparer);
_initialSelectedValues = new HashSet<object>(_selectedValues, _comparer);
}
protected override async void OnAfterRender(bool firstRender)
{
if (firstRender)
await SelectDefaultChips();
base.OnAfterRender(firstRender);
}
#endregion
} }

@ -7,6 +7,7 @@ namespace Connected.Components;
public partial class Collapse : UIComponent public partial class Collapse : UIComponent
{ {
#region Variables
internal enum CollapseState internal enum CollapseState
{ {
Entering, Entered, Exiting, Exited Entering, Entered, Exiting, Exited
@ -16,7 +17,58 @@ public partial class Collapse : UIComponent
private bool _expanded, _isRendered, _updateHeight; private bool _expanded, _isRendered, _updateHeight;
private ElementReference _wrapper; private ElementReference _wrapper;
internal CollapseState _state = CollapseState.Exited; internal CollapseState _state = CollapseState.Exited;
#endregion
#region Events
[Parameter] public EventCallback OnAnimationEnd { get; set; }
[Parameter] public EventCallback<bool> ExpandedChanged { get; 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;
}
}
public void AnimationEnd()
{
if (_state == CollapseState.Entering)
{
_state = CollapseState.Entered;
StateHasChanged();
}
else if (_state == CollapseState.Exiting)
{
_state = CollapseState.Exited;
StateHasChanged();
}
OnAnimationEnd.InvokeAsync(Expanded);
}
#endregion
#region Content
/// <summary>
/// Child content of component.
/// </summary>
[Parameter] public RenderFragment ChildContent { get; set; }
#endregion
#region Styling
protected string Stylename => protected string Stylename =>
new StyleBuilder() new StyleBuilder()
.AddStyle("max-height", MaxHeight.ToPx(), MaxHeight != null) .AddStyle("max-height", MaxHeight.ToPx(), MaxHeight != null)
@ -66,15 +118,6 @@ public partial class Collapse : UIComponent
/// </summary> /// </summary>
[Parameter] public int? MaxHeight { get; set; } [Parameter] public int? MaxHeight { get; set; }
/// <summary>
/// Child content of component.
/// </summary>
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public EventCallback OnAnimationEnd { get; set; }
[Parameter] public EventCallback<bool> ExpandedChanged { get; set; }
/// <summary> /// <summary>
/// Modified Animation duration that scales with height parameter. /// 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. /// 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.
@ -95,23 +138,9 @@ public partial class Collapse : UIComponent
set { } set { }
} }
internal async Task UpdateHeight() #endregion
{
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;
}
}
#region Lifecycle
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
if (firstRender) if (firstRender)
@ -127,19 +156,6 @@ public partial class Collapse : UIComponent
} }
await base.OnAfterRenderAsync(firstRender); await base.OnAfterRenderAsync(firstRender);
} }
#endregion
public void AnimationEnd()
{
if (_state == CollapseState.Entering)
{
_state = CollapseState.Entered;
StateHasChanged();
}
else if (_state == CollapseState.Exiting)
{
_state = CollapseState.Exited;
StateHasChanged();
}
OnAnimationEnd.InvokeAsync(Expanded);
}
} }

@ -13,16 +13,7 @@ namespace Connected.Components;
public partial class ColorPicker : Picker<Color>, IAsyncDisposable public partial class ColorPicker : Picker<Color>, IAsyncDisposable
{ {
public ColorPicker() : base(new DefaultConverter<Color>()) #region Variables
{
AdornmentIcon = Icons.Material.Outlined.Palette;
DisableToolbar = true;
Value = "#594ae2"; //MudBlazor Blue
Text = GetColorTextValue();
AdornmentAriaLabel = "Open Color Picker";
}
#region Fields
private static Dictionary<int, (Func<int, int> r, Func<int, int> g, Func<int, int> b, string dominantColorPart)> _rgbToHueMapper = new() private static Dictionary<int, (Func<int, int> r, Func<int, int> g, Func<int, int> b, string dominantColorPart)> _rgbToHueMapper = new()
{ {
@ -50,225 +41,175 @@ public partial class ColorPicker : Picker<Color>, IAsyncDisposable
private readonly Guid _id = Guid.NewGuid(); private readonly Guid _id = Guid.NewGuid();
private Guid _throttledMouseOverEventId; private Guid _throttledMouseOverEventId;
private IEventListener _throttledEventManager; private ColorPickerView _colorPickerView = ColorPickerView.Spectrum;
[Inject] IEventListenerFactory ThrottledEventManagerFactory { get; set; } private ColorPickerView _activeColorPickerView = ColorPickerView.Spectrum;
#endregion
#region Parameters
[CascadingParameter(Name = "RightToLeft")] public bool RightToLeft { get; set; }
private bool _disableAlpha = false; private bool _disableAlpha = false;
private IEventListener _throttledEventManager;
/// <summary> /// <summary>
/// If true, Alpha options will not be displayed and color output will be RGB, HSL or HEX and not RGBA, HSLA or HEXA. /// MudColor list of predefined colors. The first five colors will show up as the quick colors on preview dot click.
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)] [Category(CategoryTypes.FormComponent.PickerBehavior)]
public bool DisableAlpha public IEnumerable<Color> Palette { get; set; } = new Color[]
{ { "#424242", "#2196f3", "#00c853", "#ff9800", "#f44336",
get => _disableAlpha; "#f6f9fb", "#9df1fa", "#bdffcf", "#fff0a3", "#ffd254",
set "#e6e9eb", "#27dbf5", "#7ef7a0", "#ffe273", "#ffb31f",
{ "#c9cccf", "#13b8e8", "#14dc71", "#fdd22f", "#ff9102",
if (value != _disableAlpha) "#858791", "#0989c2", "#1bbd66", "#ebb323", "#fe6800",
{ "#585b62", "#17698e", "#17a258", "#d9980d", "#dc3f11",
_disableAlpha = value; "#353940", "#113b53", "#127942", "#bf7d11", "#aa0000"
};
if (value == true) private IEnumerable<Color> _gridList = new Color[]
{ {
Value = Value.SetAlpha(1.0); "#FFFFFF","#ebebeb","#d6d6d6","#c2c2c2","#adadad","#999999","#858586","#707070","#5c5c5c","#474747","#333333","#000000",
} "#133648","#071d53","#0f0638","#2a093b","#370c1b","#541107","#532009","#53350d","#523e0f","#65611b","#505518","#2b3d16",
"#1e4c63","#0f2e76","#180b4e","#3f1256","#4e1629","#781e0e","#722f10","#734c16","#73591a","#8c8629","#707625","#3f5623",
"#2e6c8c","#1841a3","#280c72","#591e77","#6f223d","#a62c17","#a0451a","#a06b23","#9f7d28","#c3bc3c","#9da436","#587934",
"#3c8ab0","#2155ce","#331c8e","#702898","#8d2e4f","#d03a20","#ca5a24","#c8862e","#c99f35","#f3ec4e","#c6d047","#729b44",
"#479fd3","#2660f5","#4725ab","#8c33b5","#aa395d","#eb512e","#ed732e","#f3ae3d","#f5c944","#fefb67","#ddeb5c","#86b953",
"#59c4f7","#4e85f6","#5733e2","#af43eb","#d44a7a","#ed6c59","#ef8c56","#f3b757","#f6cd5b","#fef881","#e6ee7a","#a3d16e",
"#78d3f8","#7fa6f8","#7e52f5","#c45ff6","#de789d","#f09286","#f2a984","#f6c983","#f9da85","#fef9a1","#ebf29b","#badc94",
"#a5e1fa","#adc5fa","#ab8df7","#d696f8","#e8a7bf","#f4b8b1","#f6c7af","#f9daae","#fae5af","#fefbc0","#f3f7be","#d2e7ba",
"#d2effd","#d6e1fc","#d6c9fa","#e9cbfb","#f3d4df","#f9dcd9","#fae3d8","#fcecd7","#fdf2d8","#fefce0","#f7fade","#e3edd6"
};
Text = GetColorTextValue(); [Inject] IEventListenerFactory ThrottledEventManagerFactory { get; set; }
}
} #endregion
}
#region Events
private EventCallback<MouseEventArgs> GetEventCallback() => EventCallback.Factory.Create<MouseEventArgs>(this, () => Close());
private EventCallback<MouseEventArgs> GetSelectPaletteColorCallback(Color color) => new EventCallbackFactory().Create(this, (MouseEventArgs e) => SelectPaletteColor(color));
/// <summary> /// <summary>
/// If true, the color field will not be displayed. /// Set the R (red) component of the color picker
/// </summary> /// </summary>
[Parameter] /// <param name="value">A value between 0 (no red) or 255 (max red)</param>
[Category(CategoryTypes.FormComponent.PickerBehavior)] public void SetR(int value) => Value = Value.SetR(value);
public bool DisableColorField { get; set; } = false;
/// <summary> /// <summary>
/// If true, the switch to change color mode will not be displayed. /// Set the G (green) component of the color picker
/// </summary> /// </summary>
[Parameter] /// <param name="value">A value between 0 (no green) or 255 (max green)</param>
[Category(CategoryTypes.FormComponent.PickerBehavior)] public void SetG(int value) => Value = Value.SetG(value);
public bool DisableModeSwitch { get; set; } = false;
/// <summary> /// <summary>
/// If true, textfield inputs and color mode switch will not be displayed. /// Set the B (blue) component of the color picker
/// </summary> /// </summary>
[Parameter] /// <param name="value">A value between 0 (no blue) or 255 (max blue)</param>
[Category(CategoryTypes.FormComponent.PickerBehavior)] public void SetB(int value) => Value = Value.SetB(value);
public bool DisableInputs { get; set; } = false;
/// <summary> /// <summary>
/// If true, hue and alpha sliders will not be displayed. /// Set the H (hue) component of the color picker
/// </summary> /// </summary>
[Parameter] /// <param name="value">A value between 0 and 360 (degrees)</param>
[Category(CategoryTypes.FormComponent.PickerBehavior)] public void SetH(double value) => Value = Value.SetH(value);
public bool DisableSliders { get; set; } = false;
/// <summary> /// <summary>
/// If true, the preview color box will not be displayed, note that the preview color functions as a button as well for collection colors. /// Set the S (saturation) component of the color picker
/// </summary> /// </summary>
[Parameter] /// <param name="value">A value between 0.0 (no saturation) and 1.0 (max saturation)</param>
[Category(CategoryTypes.FormComponent.PickerBehavior)] public void SetS(double value) => Value = Value.SetS(value);
public bool DisablePreview { get; set; } = false;
/// <summary> /// <summary>
/// The initial mode (RGB, HSL or HEX) the picker should open. Defaults to RGB /// Set the L (Lightness) component of the color picker
/// </summary> /// </summary>
[Parameter] /// <param name="value">A value between 0.0 (no light, black) and 1.0 (max light, white)</param>
[Category(CategoryTypes.FormComponent.PickerBehavior)] public void SetL(double value) => Value = Value.SetL(value);
public ColorPickerMode ColorPickerMode { get; set; } = ColorPickerMode.RGB;
private ColorPickerView _colorPickerView = ColorPickerView.Spectrum;
private ColorPickerView _activeColorPickerView = ColorPickerView.Spectrum;
/// <summary> /// <summary>
/// The initial view of the picker. Views can be changed if toolbar is enabled. /// Set the Alpha (transparency) component of the color picker
/// </summary> /// </summary>
[Parameter] /// <param name="value">A value between 0.0 (full transparent) and 1.0 (solid) </param>
[Category(CategoryTypes.FormComponent.PickerBehavior)] public void SetAlpha(double value) => Value = Value.SetAlpha(value);
public ColorPickerView ColorPickerView
{
get => _colorPickerView;
set
{
if (value != _colorPickerView)
{
_colorPickerView = value;
ChangeView(value).AndForget();
}
}
}
/// <summary> /// <summary>
/// If true, binding changes occurred also when HSL values changed without a corresponding RGB change /// Set the Alpha (transparency) component of the color picker
/// </summary> /// </summary>
[Parameter] /// <param name="value">A value between 0 (full transparent) and 1 (solid) </param>
[Category(CategoryTypes.FormComponent.Behavior)] public void SetAlpha(int value) => Value = Value.SetAlpha(value);
public bool UpdateBindingIfOnlyHSLChanged { get; set; } = false;
/// <summary> /// <summary>
/// A two-way bindable property representing the selected value. MudColor is a utility class that can be used to get the value as RGB, HSL, hex or other value /// Set the color of the picker based on the string input
/// </summary> /// </summary>
[Parameter] /// <param name="input">Accepting different formats for a color representation such as rbg, rgba, #</param>
[Category(CategoryTypes.FormComponent.Data)] public void SetInputString(string input)
public Color Value
{
get => _color;
set
{ {
if (value == null) { return; } Color color;
try
var rgbChanged = value != _color;
var hslChanged = _color == null ? false : value.HslChanged(_color);
_color = value;
if (rgbChanged)
{ {
if (_skipFeedback == false) color = new Color(input);
}
catch (Exception)
{ {
UpdateBaseColor(); return;
UpdateColorSelectorBasedOnRgb();
} }
SetTextAsync(GetColorTextValue(), false).AndForget(); Value = color;
ValueChanged.InvokeAsync(value).AndForget();
FieldChanged(value);
} }
if (rgbChanged == false && UpdateBindingIfOnlyHSLChanged && hslChanged == true) protected override Task StringValueChanged(string value)
{ {
SetTextAsync(GetColorTextValue(), false).AndForget(); SetInputString(value);
ValueChanged.InvokeAsync(value).AndForget(); return Task.CompletedTask;
FieldChanged(value);
}
}
} }
[Parameter] public EventCallback<Color> ValueChanged { get; set; } private bool _attachedMouseEvent = false;
/// <summary>
/// MudColor list of predefined colors. The first five colors will show up as the quick colors on preview dot click.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)]
public IEnumerable<Color> Palette { get; set; } = new Color[]
{ "#424242", "#2196f3", "#00c853", "#ff9800", "#f44336",
"#f6f9fb", "#9df1fa", "#bdffcf", "#fff0a3", "#ffd254",
"#e6e9eb", "#27dbf5", "#7ef7a0", "#ffe273", "#ffb31f",
"#c9cccf", "#13b8e8", "#14dc71", "#fdd22f", "#ff9102",
"#858791", "#0989c2", "#1bbd66", "#ebb323", "#fe6800",
"#585b62", "#17698e", "#17a258", "#d9980d", "#dc3f11",
"#353940", "#113b53", "#127942", "#bf7d11", "#aa0000"
};
private IEnumerable<Color> _gridList = new Color[] protected override void OnPickerOpened()
{ {
"#FFFFFF","#ebebeb","#d6d6d6","#c2c2c2","#adadad","#999999","#858586","#707070","#5c5c5c","#474747","#333333","#000000", base.OnPickerOpened();
"#133648","#071d53","#0f0638","#2a093b","#370c1b","#541107","#532009","#53350d","#523e0f","#65611b","#505518","#2b3d16", _attachedMouseEvent = true;
"#1e4c63","#0f2e76","#180b4e","#3f1256","#4e1629","#781e0e","#722f10","#734c16","#73591a","#8c8629","#707625","#3f5623", StateHasChanged();
"#2e6c8c","#1841a3","#280c72","#591e77","#6f223d","#a62c17","#a0451a","#a06b23","#9f7d28","#c3bc3c","#9da436","#587934", }
"#3c8ab0","#2155ce","#331c8e","#702898","#8d2e4f","#d03a20","#ca5a24","#c8862e","#c99f35","#f3ec4e","#c6d047","#729b44",
"#479fd3","#2660f5","#4725ab","#8c33b5","#aa395d","#eb512e","#ed732e","#f3ae3d","#f5c944","#fefb67","#ddeb5c","#86b953",
"#59c4f7","#4e85f6","#5733e2","#af43eb","#d44a7a","#ed6c59","#ef8c56","#f3b757","#f6cd5b","#fef881","#e6ee7a","#a3d16e",
"#78d3f8","#7fa6f8","#7e52f5","#c45ff6","#de789d","#f09286","#f2a984","#f6c983","#f9da85","#fef9a1","#ebf29b","#badc94",
"#a5e1fa","#adc5fa","#ab8df7","#d696f8","#e8a7bf","#f4b8b1","#f6c7af","#f9daae","#fae5af","#fefbc0","#f3f7be","#d2e7ba",
"#d2effd","#d6e1fc","#d6c9fa","#e9cbfb","#f3d4df","#f9dcd9","#fae3d8","#fcecd7","#fdf2d8","#fefce0","#f7fade","#e3edd6"
};
/// <summary> protected override void OnPickerClosed()
/// When set to true, no mouse move events in the spectrum mode will be captured, so the selector circle won't fellow the mouse. {
/// Under some conditions like long latency the visual representation might not reflect the user behaviour anymore. So, it can be disabled base.OnPickerClosed();
/// Enabled by default RemoveMouseOverEvent().AndForget();
/// </summary> }
[Parameter] private void HandleColorOverlayClicked()
[Category(CategoryTypes.FormComponent.PickerBehavior)] {
public bool DisableDragEffect { get; set; } = false; UpdateColorBaseOnSelection();
/// <summary> if (IsAnyControlVisible() == false)
/// Custom close icon. {
/// </summary> Close();
[Parameter] }
[Category(CategoryTypes.FormComponent.PickerAppearance)] }
public string CloseIcon { get; set; } = Icons.Material.Filled.Close;
/// <summary> private void OnSelectorClicked(MouseEventArgs e)
/// Custom spectrum icon. {
/// </summary> SetSelectorBasedOnMouseEvents(e, false);
[Parameter] HandleColorOverlayClicked();
[Category(CategoryTypes.FormComponent.PickerAppearance)] }
public string SpectrumIcon { get; set; } = Icons.Material.Filled.Tune;
/// <summary> private void OnColorOverlayClick(MouseEventArgs e)
/// Custom grid icon. {
/// </summary> SetSelectorBasedOnMouseEvents(e, true);
[Parameter] HandleColorOverlayClicked();
[Category(CategoryTypes.FormComponent.PickerAppearance)] }
public string GridIcon { get; set; } = Icons.Material.Filled.Apps;
/// <summary> private void OnMouseOver(MouseEventArgs e)
/// Custom palette icon. {
/// </summary> if (e.Buttons == 1)
[Parameter] {
[Category(CategoryTypes.FormComponent.PickerAppearance)] SetSelectorBasedOnMouseEvents(e, true);
public string PaletteIcon { get; set; } = Icons.Material.Filled.Palette; UpdateColorBaseOnSelection();
}
}
/// <summary> private void SetSelectorBasedOnMouseEvents(MouseEventArgs e, bool offsetIsAbsolute)
/// Custom import/export icont. {
/// </summary> _selectorX = (offsetIsAbsolute == true ? e.OffsetX : (e.OffsetX - _selctorSize / 2.0) + _selectorX).EnsureRange(_maxX);
[Parameter] _selectorY = (offsetIsAbsolute == true ? e.OffsetY : (e.OffsetY - _selctorSize / 2.0) + _selectorY).EnsureRange(_maxY);
[Category(CategoryTypes.FormComponent.PickerAppearance)] }
public string ImportExportIcon { get; set; } = Icons.Material.Filled.ImportExport;
#endregion [Parameter] public EventCallback<Color> ValueChanged { get; set; }
private void ToggleCollection() private void ToggleCollection()
{ {
@ -391,156 +332,210 @@ public partial class ColorPicker : Picker<Color>, IAsyncDisposable
_selectorX = relation * _maxX; _selectorX = relation * _maxX;
} }
#region mouse interactions #endregion
private void HandleColorOverlayClicked() #region Styling
{
UpdateColorBaseOnSelection();
if (IsAnyControlVisible() == false) private bool IsAnyControlVisible() => !(DisablePreview && DisableSliders && DisableInputs);
{ private ThemeColor GetButtonColor(ColorPickerView view) => _activeColorPickerView == view ? ThemeColor.Primary : ThemeColor.Inherit;
Close(); private string GetColorDotClass(Color color) => new CssBuilder("picker-color-dot").AddClass("selected", color == Value).ToString();
} private string AlphaSliderStyle => new StyleBuilder().AddStyle($"background-image: linear-gradient(to {(RightToLeft ? "left" : "right")}, transparent, {_color.ToString(ColorOutputFormats.RGB)})").Build();
} private string GetSelectorLocation() => $"translate({Math.Round(_selectorX, 2).ToString(CultureInfo.InvariantCulture)}px, {Math.Round(_selectorY, 2).ToString(CultureInfo.InvariantCulture)}px);";
private string GetColorTextValue() => (DisableAlpha == true || _activeColorPickerView is ColorPickerView.Palette or ColorPickerView.GridCompact) ? _color.ToString(ColorOutputFormats.Hex) : _color.ToString(ColorOutputFormats.HexA);
private int GetHexColorInputMaxLength() => DisableAlpha ? 7 : 9;
private void OnSelectorClicked(MouseEventArgs e) /// <summary>
{ /// When set to true, no mouse move events in the spectrum mode will be captured, so the selector circle won't fellow the mouse.
SetSelectorBasedOnMouseEvents(e, false); /// Under some conditions like long latency the visual representation might not reflect the user behaviour anymore. So, it can be disabled
HandleColorOverlayClicked(); /// Enabled by default
} /// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)]
public bool DisableDragEffect { get; set; } = false;
private void OnColorOverlayClick(MouseEventArgs e) /// <summary>
{ /// Custom close icon.
SetSelectorBasedOnMouseEvents(e, true); /// </summary>
HandleColorOverlayClicked(); [Parameter]
} [Category(CategoryTypes.FormComponent.PickerAppearance)]
public string CloseIcon { get; set; } = Icons.Material.Filled.Close;
private void OnMouseOver(MouseEventArgs e) /// <summary>
/// Custom spectrum icon.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerAppearance)]
public string SpectrumIcon { get; set; } = Icons.Material.Filled.Tune;
/// <summary>
/// Custom grid icon.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerAppearance)]
public string GridIcon { get; set; } = Icons.Material.Filled.Apps;
/// <summary>
/// Custom palette icon.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerAppearance)]
public string PaletteIcon { get; set; } = Icons.Material.Filled.Palette;
/// <summary>
/// Custom import/export icont.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerAppearance)]
public string ImportExportIcon { get; set; } = Icons.Material.Filled.ImportExport;
#endregion
#region Behavior
[CascadingParameter(Name = "RightToLeft")] public bool RightToLeft { get; set; }
/// <summary>
/// If true, Alpha options will not be displayed and color output will be RGB, HSL or HEX and not RGBA, HSLA or HEXA.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)]
public bool DisableAlpha
{ {
if (e.Buttons == 1) get => _disableAlpha;
set
{ {
SetSelectorBasedOnMouseEvents(e, true); if (value != _disableAlpha)
UpdateColorBaseOnSelection(); {
} _disableAlpha = value;
}
private void SetSelectorBasedOnMouseEvents(MouseEventArgs e, bool offsetIsAbsolute) if (value == true)
{ {
_selectorX = (offsetIsAbsolute == true ? e.OffsetX : (e.OffsetX - _selctorSize / 2.0) + _selectorX).EnsureRange(_maxX); Value = Value.SetAlpha(1.0);
_selectorY = (offsetIsAbsolute == true ? e.OffsetY : (e.OffsetY - _selctorSize / 2.0) + _selectorY).EnsureRange(_maxY);
} }
#endregion Text = GetColorTextValue();
}
#region updating inputs }
}
/// <summary> /// <summary>
/// Set the R (red) component of the color picker /// If true, the color field will not be displayed.
/// </summary> /// </summary>
/// <param name="value">A value between 0 (no red) or 255 (max red)</param> [Parameter]
public void SetR(int value) => Value = Value.SetR(value); [Category(CategoryTypes.FormComponent.PickerBehavior)]
public bool DisableColorField { get; set; } = false;
/// <summary> /// <summary>
/// Set the G (green) component of the color picker /// If true, the switch to change color mode will not be displayed.
/// </summary> /// </summary>
/// <param name="value">A value between 0 (no green) or 255 (max green)</param> [Parameter]
public void SetG(int value) => Value = Value.SetG(value); [Category(CategoryTypes.FormComponent.PickerBehavior)]
public bool DisableModeSwitch { get; set; } = false;
/// <summary> /// <summary>
/// Set the B (blue) component of the color picker /// If true, textfield inputs and color mode switch will not be displayed.
/// </summary> /// </summary>
/// <param name="value">A value between 0 (no blue) or 255 (max blue)</param> [Parameter]
public void SetB(int value) => Value = Value.SetB(value); [Category(CategoryTypes.FormComponent.PickerBehavior)]
public bool DisableInputs { get; set; } = false;
/// <summary> /// <summary>
/// Set the H (hue) component of the color picker /// If true, hue and alpha sliders will not be displayed.
/// </summary> /// </summary>
/// <param name="value">A value between 0 and 360 (degrees)</param> [Parameter]
public void SetH(double value) => Value = Value.SetH(value); [Category(CategoryTypes.FormComponent.PickerBehavior)]
public bool DisableSliders { get; set; } = false;
/// <summary> /// <summary>
/// Set the S (saturation) component of the color picker /// If true, the preview color box will not be displayed, note that the preview color functions as a button as well for collection colors.
/// </summary> /// </summary>
/// <param name="value">A value between 0.0 (no saturation) and 1.0 (max saturation)</param> [Parameter]
public void SetS(double value) => Value = Value.SetS(value); [Category(CategoryTypes.FormComponent.PickerBehavior)]
public bool DisablePreview { get; set; } = false;
/// <summary> /// <summary>
/// Set the L (Lightness) component of the color picker /// The initial mode (RGB, HSL or HEX) the picker should open. Defaults to RGB
/// </summary> /// </summary>
/// <param name="value">A value between 0.0 (no light, black) and 1.0 (max light, white)</param> [Parameter]
public void SetL(double value) => Value = Value.SetL(value); [Category(CategoryTypes.FormComponent.PickerBehavior)]
public ColorPickerMode ColorPickerMode { get; set; } = ColorPickerMode.RGB;
/// <summary> /// <summary>
/// Set the Alpha (transparency) component of the color picker /// The initial view of the picker. Views can be changed if toolbar is enabled.
/// </summary> /// </summary>
/// <param name="value">A value between 0.0 (full transparent) and 1.0 (solid) </param> [Parameter]
public void SetAlpha(double value) => Value = Value.SetAlpha(value); [Category(CategoryTypes.FormComponent.PickerBehavior)]
public ColorPickerView ColorPickerView
{
get => _colorPickerView;
set
{
if (value != _colorPickerView)
{
_colorPickerView = value;
ChangeView(value).AndForget();
}
}
}
/// <summary> /// <summary>
/// Set the Alpha (transparency) component of the color picker /// If true, binding changes occurred also when HSL values changed without a corresponding RGB change
/// </summary> /// </summary>
/// <param name="value">A value between 0 (full transparent) and 1 (solid) </param> [Parameter]
public void SetAlpha(int value) => Value = Value.SetAlpha(value); [Category(CategoryTypes.FormComponent.Behavior)]
public bool UpdateBindingIfOnlyHSLChanged { get; set; } = false;
/// <summary> /// <summary>
/// Set the color of the picker based on the string input /// A two-way bindable property representing the selected value. MudColor is a utility class that can be used to get the value as RGB, HSL, hex or other value
/// </summary> /// </summary>
/// <param name="input">Accepting different formats for a color representation such as rbg, rgba, #</param> [Parameter]
public void SetInputString(string input) [Category(CategoryTypes.FormComponent.Data)]
{ public Color Value
Color color;
try
{ {
color = new Color(input); get => _color;
} set
catch (Exception)
{ {
return; if (value == null) { return; }
}
Value = color; var rgbChanged = value != _color;
} var hslChanged = _color == null ? false : value.HslChanged(_color);
_color = value;
protected override Task StringValueChanged(string value) if (rgbChanged)
{ {
SetInputString(value); if (_skipFeedback == false)
return Task.CompletedTask; {
UpdateBaseColor();
UpdateColorSelectorBasedOnRgb();
} }
private bool _attachedMouseEvent = false; SetTextAsync(GetColorTextValue(), false).AndForget();
ValueChanged.InvokeAsync(value).AndForget();
protected override void OnPickerOpened() FieldChanged(value);
{
base.OnPickerOpened();
_attachedMouseEvent = true;
StateHasChanged();
} }
protected override void OnPickerClosed() if (rgbChanged == false && UpdateBindingIfOnlyHSLChanged && hslChanged == true)
{ {
base.OnPickerClosed(); SetTextAsync(GetColorTextValue(), false).AndForget();
RemoveMouseOverEvent().AndForget(); ValueChanged.InvokeAsync(value).AndForget();
FieldChanged(value);
}
}
} }
#endregion
#region helper
private string GetSelectorLocation() => $"translate({Math.Round(_selectorX, 2).ToString(CultureInfo.InvariantCulture)}px, {Math.Round(_selectorY, 2).ToString(CultureInfo.InvariantCulture)}px);";
private string GetColorTextValue() => (DisableAlpha == true || _activeColorPickerView is ColorPickerView.Palette or ColorPickerView.GridCompact) ? _color.ToString(ColorOutputFormats.Hex) : _color.ToString(ColorOutputFormats.HexA);
private int GetHexColorInputMaxLength() => DisableAlpha ? 7 : 9;
private EventCallback<MouseEventArgs> GetEventCallback() => EventCallback.Factory.Create<MouseEventArgs>(this, () => Close());
private bool IsAnyControlVisible() => !(DisablePreview && DisableSliders && DisableInputs);
private EventCallback<MouseEventArgs> GetSelectPaletteColorCallback(Color color) => new EventCallbackFactory().Create(this, (MouseEventArgs e) => SelectPaletteColor(color));
private ThemeColor GetButtonColor(ColorPickerView view) => _activeColorPickerView == view ? ThemeColor.Primary : ThemeColor.Inherit;
private string GetColorDotClass(Color color) => new CssBuilder("picker-color-dot").AddClass("selected", color == Value).ToString();
private string AlphaSliderStyle => new StyleBuilder().AddStyle($"background-image: linear-gradient(to {(RightToLeft ? "left" : "right")}, transparent, {_color.ToString(ColorOutputFormats.RGB)})").Build();
#endregion #endregion
#region life cycle hooks #region Lifecycle
public ColorPicker() : base(new DefaultConverter<Color>())
{
AdornmentIcon = Icons.Material.Outlined.Palette;
DisableToolbar = true;
Value = "#594ae2"; //MudBlazor Blue
Text = GetColorTextValue();
AdornmentAriaLabel = "Open Color Picker";
}
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
@ -594,4 +589,5 @@ public partial class ColorPicker : Picker<Color>, IAsyncDisposable
} }
#endregion #endregion
} }

@ -5,9 +5,6 @@ namespace Connected.Components;
public partial class Container : UIComponent public partial class Container : UIComponent
{ {
#region Event callbacks
#endregion
#region Content placeholders #region Content placeholders
/// <summary> /// <summary>

@ -8,223 +8,100 @@ namespace Connected.Components;
public abstract partial class DatePickerBase : Picker<DateTime?> public abstract partial class DatePickerBase : Picker<DateTime?>
{ {
#region Variables
private bool _dateFormatTouched; private bool _dateFormatTouched;
private DateTime? _picker_month;
protected DatePickerBase() : base(new DefaultConverter<DateTime?> protected virtual bool IsRange { get; } = false;
{ protected OpenTo CurrentView;
Format = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern,
Culture = CultureInfo.CurrentCulture
})
{
AdornmentAriaLabel = "Open Date Picker";
}
[Inject] protected IScrollManager ScrollManager { get; set; }
/// <summary>
/// Max selectable date.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Validation)]
public DateTime? MaxDate { get; set; }
/// <summary> /// <summary>
/// Min selectable date. /// We need a random id for the year items in the year list so we can scroll to the item safely in every DatePicker.
/// </summary> /// </summary>
[Parameter] private string _componentId = Guid.NewGuid().ToString();
[Category(CategoryTypes.FormComponent.Validation)]
public DateTime? MinDate { get; set; }
/// <summary> /// <summary>
/// First view to show in the MudDatePicker. /// Is set to true to scroll to the actual year after the next render
/// </summary> /// </summary>
[Parameter] private bool _scrollToYearAfterRender = false;
[Category(CategoryTypes.FormComponent.PickerBehavior)] #endregion
public OpenTo OpenTo { get; set; } = OpenTo.Date;
/// <summary> #region Events
/// String Format for selected date view private Typo GetMonthTypo(DateTime month)
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Behavior)]
public string DateFormat
{
get
{ {
return (Converter as DefaultConverter<DateTime?>)?.Format; if (GetMonthStart(0) == month)
return Typo.h5;
return Typo.subtitle1;
} }
set
{ protected abstract DateTime GetCalendarStartOfMonth();
if (Converter is DefaultConverter<DateTime?> defaultConverter)
private int GetCalendarDayOfMonth(DateTime date)
{ {
defaultConverter.Format = value; return Culture.Calendar.GetDayOfMonth(date);
_dateFormatTouched = true;
}
DateFormatChanged(value);
}
} }
/// <summary> /// <summary>
/// Date format value change hook for descendants. /// Converts gregorian year into whatever year it is in the provided culture
/// </summary> /// </summary>
protected virtual Task DateFormatChanged(string newFormat) /// <param name="year">Gregorian year</param>
/// <returns>Year according to culture</returns>
protected abstract int GetCalendarYear(int year);
public async void ScrollToYear()
{ {
return Task.CompletedTask; _scrollToYearAfterRender = false;
var id = $"{_componentId}{GetMonthStart(0).Year}";
await ScrollManager.ScrollToYearAsync(id);
StateHasChanged();
} }
protected override bool SetCulture(CultureInfo value) private int GetMinYear()
{ {
if (!base.SetCulture(value)) if (MinDate.HasValue)
return false; return MinDate.Value.Year;
return DateTime.Today.Year - 100;
if (!_dateFormatTouched && Converter is DefaultConverter<DateTime?> defaultConverter)
defaultConverter.Format = value.DateTimeFormat.ShortDatePattern;
return true;
} }
/// <summary> private int GetMaxYear()
/// Defines on which day the week starts. Depends on the value of Culture.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)]
public DayOfWeek? FirstDayOfWeek { get; set; } = null;
/// <summary>
/// The current month of the date picker (two-way bindable). This changes when the user browses through the calender.
/// The month is represented as a DateTime which is always the first day of that month. You can also set this to define which month is initially shown. If not set, the current month is shown.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)]
public DateTime? PickerMonth
{
get => _picker_month;
set
{ {
if (value == _picker_month) if (MaxDate.HasValue)
return; return MaxDate.Value.Year;
_picker_month = value; return DateTime.Today.Year + 100;
InvokeAsync(StateHasChanged);
PickerMonthChanged.InvokeAsync(value);
}
} }
private DateTime? _picker_month;
/// <summary>
/// Fired when the date changes.
/// </summary>
[Parameter] public EventCallback<DateTime?> PickerMonthChanged { get; set; }
/// <summary>
/// Sets the amount of time in milliseconds to wait before closing the picker. This helps the user see that the date was selected before the popover disappears.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)]
public int ClosingDelay { get; set; } = 100;
/// <summary>
/// Number of months to display in the calendar
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)]
public int DisplayMonths { get; set; } = 1;
/// <summary>
/// Maximum number of months in one row
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerAppearance)]
public int? MaxMonthColumns { get; set; }
/// <summary>
/// Start month when opening the picker.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)]
public DateTime? StartMonth { get; set; }
/// <summary>
/// Display week numbers according to the Culture parameter. If no culture is defined, CultureInfo.CurrentCulture will be used.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)]
public bool ShowWeekNumbers { get; set; }
/// <summary>
/// Format of the selected date in the title. By default, this is "ddd, dd MMM" which abbreviates day and month names.
/// For instance, display the long names like this "dddd, dd. MMMM".
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)]
public string TitleDateFormat { get; set; } = "ddd, dd MMM";
/// <summary>
/// If AutoClose is set to true and PickerActions are defined, selecting a day will close the MudDatePicker.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)]
public bool AutoClose { get; set; }
/// <summary> private Typo GetYearTypo(int year)
/// Function to determine whether a date is disabled
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Validation)]
public Func<DateTime, bool> IsDateDisabledFunc
{
get => _isDateDisabledFunc;
set
{ {
_isDateDisabledFunc = value ?? (_ => false); if (year == GetMonthStart(0).Year)
} return Typo.h5;
return Typo.subtitle1;
} }
private Func<DateTime, bool> _isDateDisabledFunc = _ => false;
/// <summary>
/// Function to conditionally apply new classes to specific days
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Appearance)]
public Func<DateTime, string> AdditionalDateClassesFunc { get; set; }
/// <summary>
/// Custom previous icon.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerAppearance)]
public string PreviousIcon { get; set; } = Icons.Material.Filled.ChevronLeft;
/// <summary> private void OnFormattedDateClick()
/// Custom next icon. {
/// </summary> // todo: raise an event the user can handle
[Parameter] }
[Category(CategoryTypes.FormComponent.PickerAppearance)]
public string NextIcon { get; set; } = Icons.Material.Filled.ChevronRight;
/// <summary>
/// Set a predefined fix year - no year can be selected
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)]
public int? FixYear { get; set; }
/// <summary>
/// Set a predefined fix month - no month can be selected
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)]
public int? FixMonth { get; set; }
/// <summary>
/// Set a predefined fix day - no day can be selected
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)]
public int? FixDay { get; set; }
protected virtual bool IsRange { get; } = false; private IEnumerable<DateTime> GetAllMonths()
{
var current = GetMonthStart(0);
var calendarYear = Culture.Calendar.GetYear(current);
var firstOfCalendarYear = Culture.Calendar.ToDateTime(calendarYear, 1, 1, 0, 0, 0, 0);
for (var i = 0; i < Culture.Calendar.GetMonthsInYear(calendarYear); i++)
yield return Culture.Calendar.AddMonths(firstOfCalendarYear, i);
}
protected OpenTo CurrentView; private string GetAbbreviatedMonthName(DateTime month)
{
var calendarMonth = Culture.Calendar.GetMonth(month);
return Culture.DateTimeFormat.AbbreviatedMonthNames[calendarMonth - 1];
}
private string GetMonthName(DateTime month)
{
var calendarMonth = Culture.Calendar.GetMonth(month);
return Culture.DateTimeFormat.MonthNames[calendarMonth - 1];
}
protected override void OnPickerOpened() protected override void OnPickerOpened()
{ {
base.OnPickerOpened(); base.OnPickerOpened();
@ -461,39 +338,73 @@ public abstract partial class DatePickerBase : Picker<DateTime?>
_scrollToYearAfterRender = true; _scrollToYearAfterRender = true;
} }
} }
/// <summary>
/// Date format value change hook for descendants.
/// </summary>
protected virtual Task DateFormatChanged(string newFormat)
{
return Task.CompletedTask;
}
protected override bool SetCulture(CultureInfo value)
{
if (!base.SetCulture(value))
return false;
if (!_dateFormatTouched && Converter is DefaultConverter<DateTime?> defaultConverter)
defaultConverter.Format = value.DateTimeFormat.ShortDatePattern;
return true;
}
/// <summary> /// <summary>
/// We need a random id for the year items in the year list so we can scroll to the item safely in every DatePicker. /// Fired when the date changes.
/// </summary> /// </summary>
private string _componentId = Guid.NewGuid().ToString(); [Parameter] public EventCallback<DateTime?> PickerMonthChanged { get; set; }
#endregion
#region Content
[Inject] protected IScrollManager ScrollManager { get; set; }
/// <summary> /// <summary>
/// Is set to true to scroll to the actual year after the next render /// Defines on which day the week starts. Depends on the value of Culture.
/// </summary> /// </summary>
private bool _scrollToYearAfterRender = false; [Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)]
public DayOfWeek? FirstDayOfWeek { get; set; } = null;
public async void ScrollToYear() /// <summary>
/// The current month of the date picker (two-way bindable). This changes when the user browses through the calender.
/// The month is represented as a DateTime which is always the first day of that month. You can also set this to define which month is initially shown. If not set, the current month is shown.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)]
public DateTime? PickerMonth
{ {
_scrollToYearAfterRender = false; get => _picker_month;
var id = $"{_componentId}{GetMonthStart(0).Year}"; set
await ScrollManager.ScrollToYearAsync(id);
StateHasChanged();
}
private int GetMinYear()
{ {
if (MinDate.HasValue) if (value == _picker_month)
return MinDate.Value.Year; return;
return DateTime.Today.Year - 100; _picker_month = value;
InvokeAsync(StateHasChanged);
PickerMonthChanged.InvokeAsync(value);
} }
}
#endregion
private int GetMaxYear() #region Styling
private string GetMonthClasses(DateTime month)
{ {
if (MaxDate.HasValue) if (GetMonthStart(0) == month)
return MaxDate.Value.Year; return $"picker-month-selected mud-{Color.ToDescription()}-text";
return DateTime.Today.Year + 100; return null;
} }
/// <summary>
/// Function to conditionally apply new classes to specific days
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Appearance)]
public Func<DateTime, string> AdditionalDateClassesFunc { get; set; }
private string GetYearClasses(int year) private string GetYearClasses(int year)
{ {
if (year == GetMonthStart(0).Year) if (year == GetMonthStart(0).Year)
@ -508,56 +419,161 @@ public abstract partial class DatePickerBase : Picker<DateTime?>
.AddClass($"picker-calendar-header-last", month == DisplayMonths - 1) .AddClass($"picker-calendar-header-last", month == DisplayMonths - 1)
.Build(); .Build();
} }
/// <summary>
/// Custom previous icon.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerAppearance)]
public string PreviousIcon { get; set; } = Icons.Material.Filled.ChevronLeft;
private Typo GetYearTypo(int year) /// <summary>
{ /// Custom next icon.
if (year == GetMonthStart(0).Year) /// </summary>
return Typo.h5; [Parameter]
return Typo.subtitle1; [Category(CategoryTypes.FormComponent.PickerAppearance)]
} public string NextIcon { get; set; } = Icons.Material.Filled.ChevronRight;
private void OnFormattedDateClick()
{
// todo: raise an event the user can handle
}
private IEnumerable<DateTime> GetAllMonths() /// <summary>
/// String Format for selected date view
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Behavior)]
public string DateFormat
{ {
var current = GetMonthStart(0); get
var calendarYear = Culture.Calendar.GetYear(current);
var firstOfCalendarYear = Culture.Calendar.ToDateTime(calendarYear, 1, 1, 0, 0, 0, 0);
for (var i = 0; i < Culture.Calendar.GetMonthsInYear(calendarYear); i++)
yield return Culture.Calendar.AddMonths(firstOfCalendarYear, i);
}
private string GetAbbreviatedMonthName(DateTime month)
{ {
var calendarMonth = Culture.Calendar.GetMonth(month); return (Converter as DefaultConverter<DateTime?>)?.Format;
return Culture.DateTimeFormat.AbbreviatedMonthNames[calendarMonth - 1];
} }
set
private string GetMonthName(DateTime month)
{ {
var calendarMonth = Culture.Calendar.GetMonth(month); if (Converter is DefaultConverter<DateTime?> defaultConverter)
return Culture.DateTimeFormat.MonthNames[calendarMonth - 1];
}
private string GetMonthClasses(DateTime month)
{ {
if (GetMonthStart(0) == month) defaultConverter.Format = value;
return $"picker-month-selected mud-{Color.ToDescription()}-text"; _dateFormatTouched = true;
return null;
} }
DateFormatChanged(value);
}
}
#endregion
private Typo GetMonthTypo(DateTime month) #region Behavior
/// <summary>
/// Max selectable date.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Validation)]
public DateTime? MaxDate { get; set; }
/// <summary>
/// Min selectable date.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Validation)]
public DateTime? MinDate { get; set; }
/// <summary>
/// First view to show in the DatePicker.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)]
public OpenTo OpenTo { get; set; } = OpenTo.Date;
/// <summary>
/// Set a predefined fix year - no year can be selected
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)]
public int? FixYear { get; set; }
/// <summary>
/// Set a predefined fix month - no month can be selected
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)]
public int? FixMonth { get; set; }
/// <summary>
/// Set a predefined fix day - no day can be selected
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)]
public int? FixDay { get; set; }
/// <summary>
/// Sets the amount of time in milliseconds to wait before closing the picker. This helps the user see that the date was selected before the popover disappears.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)]
public int ClosingDelay { get; set; } = 100;
/// <summary>
/// Number of months to display in the calendar
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)]
public int DisplayMonths { get; set; } = 1;
/// <summary>
/// Maximum number of months in one row
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerAppearance)]
public int? MaxMonthColumns { get; set; }
/// <summary>
/// Start month when opening the picker.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)]
public DateTime? StartMonth { get; set; }
/// <summary>
/// Display week numbers according to the Culture parameter. If no culture is defined, CultureInfo.CurrentCulture will be used.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)]
public bool ShowWeekNumbers { get; set; }
/// <summary>
/// Format of the selected date in the title. By default, this is "ddd, dd MMM" which abbreviates day and month names.
/// For instance, display the long names like this "dddd, dd. MMMM".
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)]
public string TitleDateFormat { get; set; } = "ddd, dd MMM";
/// <summary>
/// If AutoClose is set to true and PickerActions are defined, selecting a day will close the MudDatePicker.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)]
public bool AutoClose { get; set; }
/// <summary>
/// Function to determine whether a date is disabled
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Validation)]
public Func<DateTime, bool> IsDateDisabledFunc
{ {
if (GetMonthStart(0) == month) get => _isDateDisabledFunc;
return Typo.h5; set
return Typo.subtitle1; {
_isDateDisabledFunc = value ?? (_ => false);
} }
}
private Func<DateTime, bool> _isDateDisabledFunc = _ => false;
#endregion
#region Lifecycle
protected DatePickerBase() : base(new DefaultConverter<DateTime?>
{
Format = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern,
Culture = CultureInfo.CurrentCulture
})
{
AdornmentAriaLabel = "Open Date Picker";
}
protected override void OnInitialized() protected override void OnInitialized()
{ {
@ -584,17 +600,6 @@ public abstract partial class DatePickerBase : Picker<DateTime?>
ScrollToYear(); ScrollToYear();
} }
protected abstract DateTime GetCalendarStartOfMonth(); #endregion
private int GetCalendarDayOfMonth(DateTime date)
{
return Culture.Calendar.GetDayOfMonth(date);
}
/// <summary>
/// Converts gregorian year into whatever year it is in the provided culture
/// </summary>
/// <param name="year">Gregorian year</param>
/// <returns>Year according to culture</returns>
protected abstract int GetCalendarYear(int year);
} }

@ -7,23 +7,12 @@ namespace Connected.Components;
public partial class DateRangePicker : DatePickerBase public partial class DateRangePicker : DatePickerBase
{ {
#region Variables
private DateTime? _firstDate = null, _secondDate; private DateTime? _firstDate = null, _secondDate;
private DateRange _dateRange; private DateRange _dateRange;
private Range<string> _rangeText; private Range<string> _rangeText;
protected override bool IsRange => true; protected override bool IsRange => true;
private RangeInput<string> _rangeInput;
public DateRangePicker()
{
DisplayMonths = 2;
AdornmentAriaLabel = "Open Date Range Picker";
}
/// <summary>
/// Fired when the DateFormat changes.
/// </summary>
[Parameter] public EventCallback<DateRange> DateRangeChanged { get; set; }
/// <summary> /// <summary>
/// The currently selected range (two-way bindable). If null, then nothing was selected. /// The currently selected range (two-way bindable). If null, then nothing was selected.
/// </summary> /// </summary>
@ -34,7 +23,73 @@ public partial class DateRangePicker : DatePickerBase
get => _dateRange; get => _dateRange;
set => SetDateRangeAsync(value, true).AndForget(); set => SetDateRangeAsync(value, true).AndForget();
} }
#endregion
#region Events
protected override async void OnDayClicked(DateTime dateTime)
{
if (_firstDate == null || _firstDate > dateTime || _secondDate != null)
{
_secondDate = null;
_firstDate = dateTime;
return;
}
_secondDate = dateTime;
if (PickerActions == null || AutoClose)
{
Submit();
if (PickerVariant != PickerVariant.Static)
{
await Task.Delay(ClosingDelay);
Close(false);
}
}
}
protected override void OnOpened()
{
_secondDate = null;
base.OnOpened();
}
protected internal override async void Submit()
{
if (ReadOnly)
return;
if (_firstDate == null || _secondDate == null)
return;
await SetDateRangeAsync(new DateRange(_firstDate, _secondDate), true);
_firstDate = null;
_secondDate = null;
}
public override void Clear(bool close = true)
{
DateRange = null;
_firstDate = _secondDate = null;
base.Clear();
}
protected override Task DateFormatChanged(string newFormat)
{
Modified = true;
return SetTextAsync(_dateRange?.ToString(Converter), false);
}
protected override Task StringValueChanged(string value)
{
Modified = true;
// Update the daterange property (without updating back the Value property)
return SetDateRangeAsync(ParseDateRangeValue(value), false);
}
/// <summary>
/// Fired when the DateFormat changes.
/// </summary>
[Parameter] public EventCallback<DateRange> DateRangeChanged { get; set; }
protected async Task SetDateRangeAsync(DateRange range, bool updateValue) protected async Task SetDateRangeAsync(DateRange range, bool updateValue)
{ {
if (_dateRange != range) if (_dateRange != range)
@ -75,6 +130,9 @@ public partial class DateRangePicker : DatePickerBase
BeginValidate(); BeginValidate();
} }
} }
#endregion
#region Content
private Range<string> RangeText private Range<string> RangeText
{ {
@ -90,82 +148,9 @@ public partial class DateRangePicker : DatePickerBase
} }
} }
private RangeInput<string> _rangeInput; #endregion
/// <summary>
/// Focuses the start date of MudDateRangePicker
/// </summary>
/// <returns></returns>
public ValueTask FocusStartAsync() => _rangeInput.FocusStartAsync();
/// <summary>
/// Selects the start date of MudDateRangePicker
/// </summary>
/// <returns></returns>
public ValueTask SelectStartAsync() => _rangeInput.SelectStartAsync();
/// <summary>
/// Selects the specified range of the start date text
/// </summary>
/// <param name="pos1">Start position of the selection</param>
/// <param name="pos2">End position of the selection</param>
/// <returns></returns>
public ValueTask SelectRangeStartAsync(int pos1, int pos2) => _rangeInput.SelectRangeStartAsync(pos1, pos2);
/// <summary>
/// Focuses the end date of MudDateRangePicker
/// </summary>
/// <returns></returns>
public ValueTask FocusEndAsync() => _rangeInput.FocusEndAsync();
/// <summary>
/// Selects the end date of MudDateRangePicker
/// </summary>
/// <returns></returns>
public ValueTask SelectEndAsync() => _rangeInput.SelectEndAsync();
/// <summary>
/// Selects the specified range of the end date text
/// </summary>
/// <param name="pos1">Start position of the selection</param>
/// <param name="pos2">End position of the selection</param>
/// <returns></returns>
public ValueTask SelectRangeEndAsync(int pos1, int pos2) => _rangeInput.SelectRangeEndAsync(pos1, pos2);
protected override Task DateFormatChanged(string newFormat)
{
Modified = true;
return SetTextAsync(_dateRange?.ToString(Converter), false);
}
protected override Task StringValueChanged(string value)
{
Modified = true;
// Update the daterange property (without updating back the Value property)
return SetDateRangeAsync(ParseDateRangeValue(value), false);
}
protected override bool HasValue(DateTime? value)
{
return null != value && value.HasValue;
}
private DateRange ParseDateRangeValue(string value)
{
return DateRange.TryParse(value, Converter, out var dateRange) ? dateRange : null;
}
private DateRange ParseDateRangeValue(string start, string end)
{
return DateRange.TryParse(start, end, Converter, out var dateRange) ? dateRange : null;
}
protected override void OnPickerClosed()
{
_firstDate = null;
base.OnPickerClosed();
}
#region Styling
protected override string GetDayClasses(int month, DateTime day) protected override string GetDayClasses(int month, DateTime day)
{ {
var b = new CssBuilder("day"); var b = new CssBuilder("day");
@ -228,54 +213,9 @@ public partial class DateRangePicker : DatePickerBase
return b.Build(); return b.Build();
} }
#endregion
protected override async void OnDayClicked(DateTime dateTime) #region Behavior
{
if (_firstDate == null || _firstDate > dateTime || _secondDate != null)
{
_secondDate = null;
_firstDate = dateTime;
return;
}
_secondDate = dateTime;
if (PickerActions == null || AutoClose)
{
Submit();
if (PickerVariant != PickerVariant.Static)
{
await Task.Delay(ClosingDelay);
Close(false);
}
}
}
protected override void OnOpened()
{
_secondDate = null;
base.OnOpened();
}
protected internal override async void Submit()
{
if (ReadOnly)
return;
if (_firstDate == null || _secondDate == null)
return;
await SetDateRangeAsync(new DateRange(_firstDate, _secondDate), true);
_firstDate = null;
_secondDate = null;
}
public override void Clear(bool close = true)
{
DateRange = null;
_firstDate = _secondDate = null;
base.Clear();
}
protected override string GetTitleDateString() protected override string GetTitleDateString()
{ {
@ -300,4 +240,72 @@ public partial class DateRangePicker : DatePickerBase
var calenderYear = Culture.Calendar.GetYear(date); var calenderYear = Culture.Calendar.GetYear(date);
return calenderYear - diff; return calenderYear - diff;
} }
protected override bool HasValue(DateTime? value)
{
return null != value && value.HasValue;
}
private DateRange ParseDateRangeValue(string value)
{
return DateRange.TryParse(value, Converter, out var dateRange) ? dateRange : null;
}
private DateRange ParseDateRangeValue(string start, string end)
{
return DateRange.TryParse(start, end, Converter, out var dateRange) ? dateRange : null;
}
/// <summary>
/// Focuses the start date of MudDateRangePicker
/// </summary>
/// <returns></returns>
public ValueTask FocusStartAsync() => _rangeInput.FocusStartAsync();
/// <summary>
/// Selects the start date of MudDateRangePicker
/// </summary>
/// <returns></returns>
public ValueTask SelectStartAsync() => _rangeInput.SelectStartAsync();
/// <summary>
/// Selects the specified range of the start date text
/// </summary>
/// <param name="pos1">Start position of the selection</param>
/// <param name="pos2">End position of the selection</param>
/// <returns></returns>
public ValueTask SelectRangeStartAsync(int pos1, int pos2) => _rangeInput.SelectRangeStartAsync(pos1, pos2);
/// <summary>
/// Focuses the end date of MudDateRangePicker
/// </summary>
/// <returns></returns>
public ValueTask FocusEndAsync() => _rangeInput.FocusEndAsync();
/// <summary>
/// Selects the end date of MudDateRangePicker
/// </summary>
/// <returns></returns>
public ValueTask SelectEndAsync() => _rangeInput.SelectEndAsync();
/// <summary>
/// Selects the specified range of the end date text
/// </summary>
/// <param name="pos1">Start position of the selection</param>
/// <param name="pos2">End position of the selection</param>
/// <returns></returns>
public ValueTask SelectRangeEndAsync(int pos1, int pos2) => _rangeInput.SelectRangeEndAsync(pos1, pos2);
#endregion
#region Lifecycle
public DateRangePicker()
{
DisplayMonths = 2;
AdornmentAriaLabel = "Open Date Range Picker";
}
protected override void OnPickerClosed()
{
_firstDate = null;
base.OnPickerClosed();
}
#endregion
} }

@ -8,7 +8,7 @@
Variant="@Variant" Variant="@Variant"
HelperText="@HelperText" HelperText="@HelperText"
HelperTextOnFocus="@HelperTextOnFocus" HelperTextOnFocus="@HelperTextOnFocus"
CounterText="@CounterText" CounterText="@GetCounterText()"
FullWidth="@FullWidth" FullWidth="@FullWidth"
class="@CompiledHelperContainerClassList.Build()" class="@CompiledHelperContainerClassList.Build()"
Error="@HasErrors" Error="@HasErrors"

@ -198,6 +198,11 @@ public partial class Input<T> : InputBase<T>
/// </summary> /// </summary>
[Parameter] public bool HideSpinButtons { get; set; } = true; [Parameter] public bool HideSpinButtons { get; set; } = true;
internal override InputType GetInputType()
{
return InputType;
}
/// <summary> /// <summary>
/// Show clear button. /// Show clear button.
/// </summary> /// </summary>
@ -230,7 +235,7 @@ public partial class Input<T> : InputBase<T>
#region Input field class #region Input field class
[Parameter] [Parameter]
public string InputClass { get; set; } = string.Empty; public string Class { get; set; } = string.Empty;
protected CssBuilder CompiledInputClass protected CssBuilder CompiledInputClass
{ {
get get
@ -240,7 +245,7 @@ public partial class Input<T> : InputBase<T>
.AddClass($"input-root-{Variant.ToDescription()}") .AddClass($"input-root-{Variant.ToDescription()}")
.AddClass($"input-root-adorned-{Adornment.ToDescription()}", Adornment != Adornment.None) .AddClass($"input-root-adorned-{Adornment.ToDescription()}", Adornment != Adornment.None)
.AddClass($"input-root-margin-{Margin.ToDescription()}", when: () => Margin != Margin.None) .AddClass($"input-root-margin-{Margin.ToDescription()}", when: () => Margin != Margin.None)
.AddClass(InputClass); .AddClass(Class);
} }
} }
#endregion #endregion
@ -315,7 +320,17 @@ public partial class Input<T> : InputBase<T>
/// <summary> /// <summary>
/// The current character counter, displayed below the text field. /// The current character counter, displayed below the text field.
/// </summary> /// </summary>
[Parameter] public string CounterText { get; set; } [Parameter] public bool ShowCharacterCounter { get; set; }
/// <summary>
/// The current character counter, displayed below the text field.
/// </summary>
public string GetCounterText()
{
if (ShowCharacterCounter)
return Text.Length.ToString();
return string.Empty;
}
private System.Timers.Timer _timer; private System.Timers.Timer _timer;
@ -381,7 +396,7 @@ public partial class Input<T> : InputBase<T>
[Parameter] [Parameter]
public double Step { get; set; } = 1; public double Step { get; set; } = 1;
internal override InputType GetInputType() => InputType; //internal override InputType GetInputType() => InputType;
protected string InputTypeString => InputType.ToDescription(); protected string InputTypeString => InputType.ToDescription();
public ElementReference ElementReference { get; private set; } public ElementReference ElementReference { get; private set; }
private ElementReference _elementReference1; private ElementReference _elementReference1;

Loading…
Cancel
Save