Progress - Categorizing variables and methods
This commit is contained in:
		
							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,34 +51,36 @@ 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> | ||||||
| 	[Parameter] | 	[Parameter] | ||||||
| 	public string? HeaderClassList { get; set; } | 	public string? HeaderClassList { get; set; } | ||||||
| 
 | 
 | ||||||
| 	/// <summary> |  | ||||||
| 	/// Class names for the nested toolbar. In case of several, separate by spaces. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	public string? ToolbarClassList { get; set; } |  | ||||||
| 
 |  | ||||||
| 	/// <summary> | 	/// <summary> | ||||||
| 	/// Determines the vertical alignment of the AppBar. | 	/// Determines the vertical alignment of the AppBar. | ||||||
| 	/// </summary> | 	/// </summary> | ||||||
| 	[Parameter] | 	[Parameter] | ||||||
| 	public VerticalAlignment VerticalAlignment { get; set; } | 	public VerticalAlignment VerticalAlignment { get; set; } | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// If true, appbar will be fixed. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	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; | 	/// <summary> | ||||||
|  | 	/// Function to be invoked when checking whether an item should be disabled or not | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	public Func<T, bool> ItemDisabledFunc { get; set; } | ||||||
| 
 | 
 | ||||||
| 				SetConverter(new LambdaConverter<T, string>(_toStringFunc ?? (x => x?.ToString()), null)); |  | ||||||
| 		  } |  | ||||||
| 	 } |  | ||||||
| 	/// <summary> | 	/// <summary> | ||||||
| 	 /// Whether to show the progress indicator.  | 	/// If true, the currently selected item from the drop-down (if it is open) is selected. | ||||||
| 	/// </summary> | 	/// </summary> | ||||||
| 	[Parameter] | 	[Parameter] | ||||||
| 	 public bool ShowProgressIndicator { get; set; } = false; | 	public bool SelectValueOnTab { 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> | 	/// <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) | ||||||
| 
 |  | ||||||
| 	 protected string ClassList() |  | ||||||
| 	{ | 	{ | ||||||
| 		  return new CssBuilder("select") | 		switch (args.Key) | ||||||
| 			 .Build(); | 		{ | ||||||
|  | 			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); | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 	 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) | ||||||
| 	{ | 	{ | ||||||
| @ -464,83 +352,6 @@ public partial class Autocomplete<T> : InputBase<T>, IDisposable | |||||||
| 		return "null"; | 		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; |  | ||||||
| 		  } |  | ||||||
| 
 |  | ||||||
| 		  base.InvokeKeyUp(args); |  | ||||||
| 	 } |  | ||||||
| 
 |  | ||||||
| 	private ValueTask SelectNextItem(int increment) | 	private ValueTask SelectNextItem(int increment) | ||||||
| 	{ | 	{ | ||||||
| 		if (increment == 0 || _items is null || !_items.Any() || !_enabledItemIndices.Any()) | 		if (increment == 0 || _items is null || !_items.Any() || !_enabledItemIndices.Any()) | ||||||
| @ -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> | 	/// <summary> | ||||||
| 	/// Spacing between avatars where 0 is none and 16 max. | 	/// Custom class/classes for MaxAvatar | ||||||
| 	/// </summary> | 	/// </summary> | ||||||
| 	[Parameter] | 	[Parameter] | ||||||
| 	public int Spacing | 	[Category(CategoryTypes.AvatarGroup.Appearance)] | ||||||
| 	{ | 	public string MaxAvatarClass { get; set; } | ||||||
| 		get => _spacing; |  | ||||||
| 		set |  | ||||||
| 		{ |  | ||||||
| 			if (value != _spacing) |  | ||||||
| 			{ |  | ||||||
| 				_spacing = value; |  | ||||||
| 				_childrenNeedUpdates = true; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// A space separated list of class names, added on top of the default class list. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	public string? ClassList { 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> | 	/// </summary> | ||||||
| 	[Parameter] | 	[Parameter] | ||||||
| 	[Category(CategoryTypes.AvatarGroup.Appearance)] | 	public int Spacing | ||||||
| 	public string MaxAvatarClass { get; set; } |  | ||||||
| 
 |  | ||||||
| 	/// <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); | 		get => _spacing; | ||||||
| 		StateHasChanged(); | 		set | ||||||
|  | 		{ | ||||||
|  | 			if (value != _spacing) | ||||||
|  | 			{ | ||||||
|  | 				_spacing = value; | ||||||
|  | 				_childrenNeedUpdates = true; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	internal void RemoveAvatar(Avatar avatar) | 	#endregion | ||||||
| 	{ |  | ||||||
| 		_avatars.Remove(avatar); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	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) |  | ||||||
| 			return true; |  | ||||||
| 		else |  | ||||||
| 			return false; |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
|  | 	#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> | 	#region Lifecycle | ||||||
| 	/// 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> |  | ||||||
| 	/// 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,6 +5,27 @@ namespace Connected.Components; | |||||||
| 
 | 
 | ||||||
| public partial class Button : ButtonBase | public partial class Button : ButtonBase | ||||||
| { | { | ||||||
|  | 
 | ||||||
|  | 	#region Content | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// Child content of component. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	public RenderFragment? ChildContent { get; set; } | ||||||
|  | 	#endregion | ||||||
|  | 
 | ||||||
|  | 	#region Styling | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// A space separated list of class names, added on top of the default class list. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	public string? ClassList { get; set; } | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// The variant to use. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	public Variant Variant { get; set; } = Variant.Text; | ||||||
| 	/// <summary> | 	/// <summary> | ||||||
| 	/// Contains the default container classlist and the user defined classes. | 	/// Contains the default container classlist and the user defined classes. | ||||||
| 	/// </summary> | 	/// </summary> | ||||||
| @ -17,25 +38,6 @@ public partial class Button : ButtonBase | |||||||
| 			.AddClass(ClassList); | 			.AddClass(ClassList); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 |  | ||||||
| 	#region Styling properties |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// Child content of component. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	public RenderFragment? ChildContent { get; set; } |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// A space separated list of class names, added on top of the default class list. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	public string? ClassList { get; set; } |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// The variant to use. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	public Variant Variant { get; set; } = Variant.Text; |  | ||||||
| 	#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,6 +7,38 @@ namespace Connected.Components; | |||||||
| 
 | 
 | ||||||
| public partial class Fab : ButtonBase | public partial class Fab : ButtonBase | ||||||
| { | { | ||||||
|  | 
 | ||||||
|  | 	#region Content | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// If applied Glyph will be added at the start of the component. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.Button.Behavior)] | ||||||
|  | 	public string StartIcon { get; set; } | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// If applied Glyph will be added at the end of the component. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.Button.Behavior)] | ||||||
|  | 	public string EndIcon { get; set; } | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// If applied the text will be added to the component. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.Button.Behavior)] | ||||||
|  | 	public string Label { get; set; } | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// GlyphTitle of the icon used for accessibility. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.Button.Behavior)] | ||||||
|  | 	public string Title { get; set; } | ||||||
|  | 	#endregion | ||||||
|  | 
 | ||||||
|  | 	#region Styling | ||||||
| 	protected string Classname => | 	protected string Classname => | ||||||
| 	new CssBuilder("button-root mud-fab") | 	new CssBuilder("button-root mud-fab") | ||||||
| 	  .AddClass($"fab-extended", !string.IsNullOrEmpty(Label)) | 	  .AddClass($"fab-extended", !string.IsNullOrEmpty(Label)) | ||||||
| @ -28,25 +60,6 @@ public partial class Fab : ButtonBase | |||||||
| 	[Category(CategoryTypes.Button.Appearance)] | 	[Category(CategoryTypes.Button.Appearance)] | ||||||
| 	public Size Size { get; set; } = Size.Large; | 	public Size Size { get; set; } = Size.Large; | ||||||
| 
 | 
 | ||||||
| 	/// <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> |  | ||||||
| 	/// If applied Glyph will be added at the start of the component. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	[Category(CategoryTypes.Button.Behavior)] |  | ||||||
| 	public string StartIcon { get; set; } |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// If applied Glyph will be added at the end of the component. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	[Category(CategoryTypes.Button.Behavior)] |  | ||||||
| 	public string EndIcon { get; set; } |  | ||||||
| 
 |  | ||||||
| 	/// <summary> | 	/// <summary> | ||||||
| 	/// The color of the icon. It supports the theme colors. | 	/// The color of the icon. It supports the theme colors. | ||||||
| 	/// </summary> | 	/// </summary> | ||||||
| @ -60,18 +73,6 @@ public partial class Fab : ButtonBase | |||||||
| 	[Parameter] | 	[Parameter] | ||||||
| 	[Category(CategoryTypes.Button.Appearance)] | 	[Category(CategoryTypes.Button.Appearance)] | ||||||
| 	public Size IconSize { get; set; } = Size.Medium; | 	public Size IconSize { get; set; } = Size.Medium; | ||||||
|  | 	#endregion | ||||||
| 
 | 
 | ||||||
| 	/// <summary> |  | ||||||
| 	/// If applied the text will be added to the component. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	[Category(CategoryTypes.Button.Behavior)] |  | ||||||
| 	public string Label { get; set; } |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// GlyphTitle of the icon used for accessibility. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	[Category(CategoryTypes.Button.Behavior)] |  | ||||||
| 	public string Title { get; set; } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -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,67 +4,12 @@ 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; } | ||||||
| 	#endregion |  | ||||||
| 
 |  | ||||||
| 	#region Content placeholders |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// The glyph that will be used in the untoggled state. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	public string? Glyph { get; set; } |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// GlyphTitle of the icon used for accessibility. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	public string? GlyphTitle { get; set; } |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// The glyph that will be used in the toggled state. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	public string? ToggledGlyph { get; set; } |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// GlyphTitle used in toggled state, if different. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	public string? ToggledGlyphTitle { get; set; } |  | ||||||
| 	#endregion |  | ||||||
| 
 |  | ||||||
| 	#region Styling properties |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// A space separated list of class names, added on top of the default class list. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	public string? ClassList { get; set; } |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// The variant to use. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	public Variant Variant { get; set; } = Variant.Text; |  | ||||||
| 	#endregion |  | ||||||
| 
 |  | ||||||
| 	#region Behavior properties |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// If true, the button will be disabled. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	public bool Disabled { get; set; } |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// The button toggled state. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	public bool Toggled { get; set; } |  | ||||||
| 	#endregion |  | ||||||
| 
 | 
 | ||||||
| 	public async Task Toggle() | 	public async Task Toggle() | ||||||
| 	{ | 	{ | ||||||
| @ -86,4 +31,59 @@ public partial class ToggleIconButton : UIComponent | |||||||
| 			await ToggledChanged.InvokeAsync(Toggled); | 			await ToggledChanged.InvokeAsync(Toggled); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	#endregion | ||||||
|  | 
 | ||||||
|  | 	#region Content | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// The glyph that will be used in the untoggled state. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	public string? Icon { get; set; } | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// GlyphTitle of the icon used for accessibility. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	public string? IconTitle { get; set; } | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// The glyph that will be used in the toggled state. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	public string? ToggledIcon { get; set; } | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// GlyphTitle used in toggled state, if different. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	public string? ToggledIconTitle { get; set; } | ||||||
|  | 	#endregion | ||||||
|  | 
 | ||||||
|  | 	#region Styling | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// A space separated list of class names, added on top of the default class list. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	public string? ClassList { get; set; } | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// The variant to use. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	public Variant Variant { get; set; } = Variant.Text; | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// If true, the button will be disabled. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	public bool Disabled { get; set; } | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// The button toggled state. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	public bool Toggled { get; set; } | ||||||
|  | 
 | ||||||
|  | 	#endregion | ||||||
| } | } | ||||||
|  | |||||||
| @ -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(); | ||||||
| @ -15,6 +16,17 @@ public partial class CardMedia : UIComponent | |||||||
| 			.AddClass(AdditionalClassList) | 			.AddClass(AdditionalClassList) | ||||||
| 			.Build(); | 			.Build(); | ||||||
| 
 | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// Specifies the height of the image in px. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.Card.Behavior)] | ||||||
|  | 	public int Height { get; set; } = 300; | ||||||
|  | 
 | ||||||
|  | 	#endregion | ||||||
|  | 
 | ||||||
|  | 	#region Content | ||||||
|  | 
 | ||||||
| 	/// <summary> | 	/// <summary> | ||||||
| 	/// GlyphTitle of the image used for accessibility. | 	/// GlyphTitle of the image used for accessibility. | ||||||
| 	/// </summary> | 	/// </summary> | ||||||
| @ -29,10 +41,6 @@ public partial class CardMedia : UIComponent | |||||||
| 	[Category(CategoryTypes.Card.Behavior)] | 	[Category(CategoryTypes.Card.Behavior)] | ||||||
| 	public string Image { get; set; } | 	public string Image { get; set; } | ||||||
| 
 | 
 | ||||||
| 	/// <summary> | 	#endregion | ||||||
| 	/// Specifies the height of the image in px. | 
 | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	[Category(CategoryTypes.Card.Behavior)] |  | ||||||
| 	public int Height { get; set; } = 300; |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -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> | ||||||
|  | 	/// Gets or Sets if bar with Bullets must be visible | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Category(CategoryTypes.Carousel.Behavior)] | ||||||
|  | 	[Parameter] public bool ShowBullets { get; set; } = true; | ||||||
| 
 | 
 | ||||||
| 	/// <summary> | 	/// <summary> | ||||||
| 	/// Called when selected Index changed on base class | 	/// Gets or Sets if bottom bar with Delimiters must be visible. | ||||||
|  | 	/// Deprecated, use ShowBullets instead. | ||||||
| 	/// </summary> | 	/// </summary> | ||||||
| 	protected override void SelectionChanged() | 	[Category(CategoryTypes.Carousel.Behavior)] | ||||||
|  | 	[Obsolete($"Use {nameof(ShowBullets)} instead", false)] | ||||||
|  | 	[ExcludeFromCodeCoverage] | ||||||
|  | 	[Parameter] public bool ShowDelimiters { get => ShowBullets; set => ShowBullets = 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()); | 				InvokeAsync(async () => await ResetTimerAsync()); | ||||||
| 
 | 
 | ||||||
| 		_currentColor = SelectedContainer?.Color ?? ThemeColor.Inherit; | 			else | ||||||
| 	} | 				InvokeAsync(async () => await StopTimerAsync()); | ||||||
| 
 |  | ||||||
| 	//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> | 	/// <summary> | ||||||
| 	/// Provides Selection changes by horizontal swipe gesture | 	/// Gets or Sets the Auto Cycle time | ||||||
| 	/// </summary> | 	/// </summary> | ||||||
| 	private void OnSwipe(SwipeDirection direction) | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.Carousel.Behavior)] | ||||||
|  | 	public TimeSpan AutoCycleTime | ||||||
| 	{ | 	{ | ||||||
| 		if (!EnableSwipeGesture) | 		get => _cycleTimeout; | ||||||
|  | 		set | ||||||
| 		{ | 		{ | ||||||
| 			return; | 			_cycleTimeout = value; | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		switch (direction) | 			if (_autoCycle == true) | ||||||
| 		{ | 				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> |  | ||||||
| 	/// 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> | 	/// <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,99 +8,12 @@ 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] |  | ||||||
| 	[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(); |  | ||||||
| 
 |  | ||||||
| 	[Parameter] |  | ||||||
| 	[Category(CategoryTypes.Chart.Appearance)] |  | ||||||
| 	public ChartOptions ChartOptions { get; set; } = new(); |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// RenderFragment for costumization inside the chart's svg. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	[Category(CategoryTypes.Chart.Appearance)] |  | ||||||
| 	public RenderFragment CustomGraphics { get; set; } |  | ||||||
| 
 |  | ||||||
| 	protected string Classname => |  | ||||||
| 	new CssBuilder("chart") |  | ||||||
| 		.AddClass($"chart-legend-{ConvertLegendPosition(LegendPosition).ToDescription()}") |  | ||||||
| 	  .AddClass(AdditionalClassList) |  | ||||||
| 	.Build(); |  | ||||||
| 
 |  | ||||||
| 	[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> |  | ||||||
| 	/// The Width of the chart, end with % or px. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	[Category(CategoryTypes.Chart.Appearance)] |  | ||||||
| 	public string Width { get; set; } = "80%"; |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// The Height of the chart, end with % or px. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	[Category(CategoryTypes.Chart.Appearance)] |  | ||||||
| 	public string Height { get; set; } = "80%"; |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// The placement direction of the legend if used. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	[Category(CategoryTypes.Chart.Appearance)] |  | ||||||
| 	public Position LegendPosition { get; set; } = Position.Bottom; |  | ||||||
| 
 |  | ||||||
| 	private Position ConvertLegendPosition(Position position) |  | ||||||
| 	{ |  | ||||||
| 		return position switch |  | ||||||
| 		{ |  | ||||||
| 			Position.Start => RightToLeft ? Position.Right : Position.Left, |  | ||||||
| 			Position.End => RightToLeft ? Position.Left : Position.Right, |  | ||||||
| 			_ => position |  | ||||||
| 		}; |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
|  | 	#region Variables | ||||||
| 	private int _selectedIndex; | 	private int _selectedIndex; | ||||||
|  | 	#endregion | ||||||
| 
 | 
 | ||||||
| 	/// <summary> | 	#region Events | ||||||
| 	/// Selected index of a portion of the chart. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	[Category(CategoryTypes.Chart.Behavior)] |  | ||||||
| 	public int SelectedIndex |  | ||||||
| 	{ |  | ||||||
| 		get => _selectedIndex; |  | ||||||
| 		set |  | ||||||
| 		{ |  | ||||||
| 			if (value != _selectedIndex) |  | ||||||
| 			{ |  | ||||||
| 				_selectedIndex = value; |  | ||||||
| 				SelectedIndexChanged.InvokeAsync(value); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/// <summary> | 	/// <summary> | ||||||
| 	/// Selected index of a portion of the chart. | 	/// Selected index of a portion of the chart. | ||||||
| 	/// </summary> | 	/// </summary> | ||||||
| @ -124,6 +37,102 @@ public partial class Chart : UIComponent | |||||||
| 
 | 
 | ||||||
| 		return d.ToString(format); | 		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] | ||||||
|  | 	[Category(CategoryTypes.Chart.Appearance)] | ||||||
|  | 	public ChartOptions ChartOptions { get; set; } = new(); | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// RenderFragment for costumization inside the chart's svg. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.Chart.Appearance)] | ||||||
|  | 	public RenderFragment CustomGraphics { get; set; } | ||||||
|  | 
 | ||||||
|  | 	protected string Classname => | ||||||
|  | 	new CssBuilder("chart") | ||||||
|  | 		.AddClass($"chart-legend-{ConvertLegendPosition(LegendPosition).ToDescription()}") | ||||||
|  | 	  .AddClass(AdditionalClassList) | ||||||
|  | 	.Build(); | ||||||
|  | 
 | ||||||
|  | 	[CascadingParameter(Name = "RightToLeft")] public bool RightToLeft { get; set; } | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// The Width of the chart, end with % or px. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.Chart.Appearance)] | ||||||
|  | 	public string Width { get; set; } = "80%"; | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// The Height of the chart, end with % or px. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.Chart.Appearance)] | ||||||
|  | 	public string Height { get; set; } = "80%"; | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// The placement direction of the legend if used. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.Chart.Appearance)] | ||||||
|  | 	public Position LegendPosition { get; set; } = Position.Bottom; | ||||||
|  | 
 | ||||||
|  | 	#endregion | ||||||
|  | 
 | ||||||
|  | 	#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> | ||||||
|  | 	/// Selected index of a portion of the chart. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.Chart.Behavior)] | ||||||
|  | 	public int SelectedIndex | ||||||
|  | 	{ | ||||||
|  | 		get => _selectedIndex; | ||||||
|  | 		set | ||||||
|  | 		{ | ||||||
|  | 			if (value != _selectedIndex) | ||||||
|  | 			{ | ||||||
|  | 				_selectedIndex = value; | ||||||
|  | 				SelectedIndexChanged.InvokeAsync(value); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	#endregion | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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,134 +9,13 @@ namespace Connected.Components; | |||||||
| 
 | 
 | ||||||
| public partial class CheckBox<T> : BooleanInput<T> | public partial class CheckBox<T> : BooleanInput<T> | ||||||
| { | { | ||||||
| 	protected string Classname => | 	#region Variables | ||||||
| 	new CssBuilder("input-control-boolean-input") | 	private IKeyInterceptor _keyInterceptor; | ||||||
| 		.AddClass(AdditionalClassList) | 	[Inject] private IKeyInterceptorFactory _keyInterceptorFactory { get; set; } | ||||||
| 		.Build(); | 	private string _elementId = "checkbox" + Guid.NewGuid().ToString().Substring(0, 8); | ||||||
| 
 | 	#endregion | ||||||
| 	protected string LabelClassname => |  | ||||||
| 	new CssBuilder("checkbox") |  | ||||||
| 		.AddClass($"disabled", Disabled) |  | ||||||
| 		.AddClass($"readonly", ReadOnly) |  | ||||||
| 		.AddClass(LabelPosition == LabelPosition.End ? "ltr" : "rtl", true) |  | ||||||
| 	.Build(); |  | ||||||
| 
 |  | ||||||
| 	protected string CheckBoxClassname => |  | ||||||
| 	new CssBuilder("button-root mud-icon-button") |  | ||||||
| 		.AddClass($"{Color.ToDescription()}-text hover:mud-{Color.ToDescription()}-hover", UnCheckedColor == null || (UnCheckedColor != null && BoolValue == true)) |  | ||||||
| 		.AddClass($"{UnCheckedColor?.ToDescription()}-text hover:mud-{UnCheckedColor?.ToDescription()}-hover", UnCheckedColor != null && BoolValue == false) |  | ||||||
| 		.AddClass($"checkbox-dense", Dense) |  | ||||||
| 		.AddClass($"ripple mud-ripple-checkbox", !DisableRipple && !ReadOnly && !Disabled) |  | ||||||
| 		.AddClass($"disabled", Disabled) |  | ||||||
| 		.AddClass($"readonly", ReadOnly) |  | ||||||
| 	.Build(); |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// The color of the component. It supports the theme colors. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	[Category(CategoryTypes.FormComponent.Appearance)] |  | ||||||
| 	public ThemeColor Color { get; set; } = ThemeColor.Default; |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// The base color of the component in its none active/unchecked state. It supports the theme colors. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	[Category(CategoryTypes.Radio.Appearance)] |  | ||||||
| 	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> |  | ||||||
| 	/// If true, disables ripple effect. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	[Category(CategoryTypes.FormComponent.Appearance)] |  | ||||||
| 	public bool DisableRipple { get; set; } |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// If true, compact padding will be applied. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	[Category(CategoryTypes.FormComponent.Appearance)] |  | ||||||
| 	public bool Dense { get; set; } |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// The Size of the component. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	[Category(CategoryTypes.FormComponent.Appearance)] |  | ||||||
| 	public Size Size { get; set; } = Size.Medium; |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// Child content of component. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	[Category(CategoryTypes.FormComponent.Behavior)] |  | ||||||
| 	public RenderFragment ChildContent { get; set; } |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// Custom checked icon, leave null for default. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	[Category(CategoryTypes.FormComponent.Appearance)] |  | ||||||
| 	public string CheckedIcon { get; set; } = Icons.Material.Filled.CheckBox; |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// Custom unchecked icon, leave null for default. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	[Category(CategoryTypes.FormComponent.Appearance)] |  | ||||||
| 	public string UncheckedIcon { get; set; } = Icons.Material.Filled.CheckBoxOutlineBlank; |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// Custom indeterminate icon, leave null for default. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	[Category(CategoryTypes.FormComponent.Appearance)] |  | ||||||
| 	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() |  | ||||||
| 	{ |  | ||||||
| 		if (BoolValue == true) |  | ||||||
| 		{ |  | ||||||
| 			return CheckedIcon; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if (BoolValue == false) |  | ||||||
| 		{ |  | ||||||
| 			return UncheckedIcon; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		return IndeterminateIcon; |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
|  | 	#region Events | ||||||
| 	protected override Task OnChange(ChangeEventArgs args) | 	protected override Task OnChange(ChangeEventArgs args) | ||||||
| 	{ | 	{ | ||||||
| 		Modified = true; | 		Modified = true; | ||||||
| @ -202,12 +81,144 @@ public partial class CheckBox<T> : BooleanInput<T> | |||||||
| 				break; | 				break; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 	#endregion | ||||||
| 
 | 
 | ||||||
| 	private IKeyInterceptor _keyInterceptor; | 	#region Content | ||||||
| 	[Inject] private IKeyInterceptorFactory _keyInterceptorFactory { get; set; } | 	/// <summary> | ||||||
|  | 	/// Child content of component. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.FormComponent.Behavior)] | ||||||
|  | 	public RenderFragment ChildContent { get; set; } | ||||||
|  | 	#endregion | ||||||
| 
 | 
 | ||||||
| 	private string _elementId = "checkbox" + Guid.NewGuid().ToString().Substring(0, 8); | 	#region Styling | ||||||
|  | 	protected string Classname => | ||||||
|  | 	new CssBuilder("input-control-boolean-input") | ||||||
|  | 		.AddClass(AdditionalClassList) | ||||||
|  | 		.Build(); | ||||||
| 
 | 
 | ||||||
|  | 	protected string LabelClassname => | ||||||
|  | 	new CssBuilder("checkbox") | ||||||
|  | 		.AddClass($"disabled", Disabled) | ||||||
|  | 		.AddClass($"readonly", ReadOnly) | ||||||
|  | 		.AddClass(LabelPosition == LabelPosition.End ? "ltr" : "rtl", true) | ||||||
|  | 	.Build(); | ||||||
|  | 
 | ||||||
|  | 	protected string CheckBoxClassname => | ||||||
|  | 	new CssBuilder("button-root mud-icon-button") | ||||||
|  | 		.AddClass($"{Color.ToDescription()}-text hover:mud-{Color.ToDescription()}-hover", UnCheckedColor == null || (UnCheckedColor != null && BoolValue == true)) | ||||||
|  | 		.AddClass($"{UnCheckedColor?.ToDescription()}-text hover:mud-{UnCheckedColor?.ToDescription()}-hover", UnCheckedColor != null && BoolValue == false) | ||||||
|  | 		.AddClass($"checkbox-dense", Dense) | ||||||
|  | 		.AddClass($"ripple mud-ripple-checkbox", !DisableRipple && !ReadOnly && !Disabled) | ||||||
|  | 		.AddClass($"disabled", Disabled) | ||||||
|  | 		.AddClass($"readonly", ReadOnly) | ||||||
|  | 	.Build(); | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// The color of the component. It supports the theme colors. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.FormComponent.Appearance)] | ||||||
|  | 	public ThemeColor Color { get; set; } = ThemeColor.Default; | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// The base color of the component in its none active/unchecked state. It supports the theme colors. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.Radio.Appearance)] | ||||||
|  | 	public ThemeColor? UnCheckedColor { get; set; } = null; | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// If true, disables ripple effect. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.FormComponent.Appearance)] | ||||||
|  | 	public bool DisableRipple { get; set; } | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// If true, compact padding will be applied. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.FormComponent.Appearance)] | ||||||
|  | 	public bool Dense { get; set; } | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// The Size of the component. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.FormComponent.Appearance)] | ||||||
|  | 	public Size Size { get; set; } = Size.Medium; | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// Custom checked icon, leave null for default. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.FormComponent.Appearance)] | ||||||
|  | 	public string CheckedIcon { get; set; } = Icons.Material.Filled.CheckBox; | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// Custom unchecked icon, leave null for default. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.FormComponent.Appearance)] | ||||||
|  | 	public string UncheckedIcon { get; set; } = Icons.Material.Filled.CheckBoxOutlineBlank; | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// Custom indeterminate icon, leave null for default. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.FormComponent.Appearance)] | ||||||
|  | 	public string IndeterminateIcon { get; set; } = Icons.Material.Filled.IndeterminateCheckBox; | ||||||
|  | 
 | ||||||
|  | 	private string GetIcon() | ||||||
|  | 	{ | ||||||
|  | 		if (BoolValue == true) | ||||||
|  | 		{ | ||||||
|  | 			return CheckedIcon; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (BoolValue == false) | ||||||
|  | 		{ | ||||||
|  | 			return UncheckedIcon; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return IndeterminateIcon; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	#endregion | ||||||
|  | 
 | ||||||
|  | 	#region Behavior | ||||||
|  | 	/// <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> | ||||||
|  | 	/// 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,6 +217,92 @@ 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> | ||||||
|  | 	/// Set by MudChipSet | ||||||
|  | 	/// </summary> | ||||||
|  | 	public bool IsSelected | ||||||
|  | 	{ | ||||||
|  | 		get => _isSelected; | ||||||
|  | 		set | ||||||
|  | 		{ | ||||||
|  | 			if (_isSelected == value) | ||||||
|  | 				return; | ||||||
|  | 			_isSelected = value; | ||||||
|  | 			StateHasChanged(); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// If false, this chip has not been seen before | ||||||
|  | 	/// </summary> | ||||||
|  | 	public bool DefaultProcessed { get; set; } | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// Set by MudChipSet | ||||||
|  | 	/// </summary> | ||||||
|  | 	public bool IsChecked | ||||||
|  | 	{ | ||||||
|  | 		get => _isSelected && ChipSet?.Filter == true; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// If true, force browser to redirect outside component router-space. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.Chip.ClickAction)] | ||||||
|  | 	public bool ForceLoad { get; set; } | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// If true, this chip is selected by default if used in a ChipSet.  | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.Chip.Behavior)] | ||||||
|  | 	public bool? Default { get; set; } | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// A string you want to associate with the chip. If the ChildContent is not set this will be shown as chip text. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.Chip.Behavior)] | ||||||
|  | 	public string Text { get; set; } | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// A value that should be managed in the SelectedValues collection. | ||||||
|  | 	/// Note: do not change the value during the chip's lifetime | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.Chip.Behavior)] | ||||||
|  | 	public object Value { get; set; } | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// Avatar Glyph, Overrides the regular Glyph if set. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.Chip.Behavior)] | ||||||
|  | 	public string Avatar { 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> | ||||||
| 	/// Child content of component. | 	/// Child content of component. | ||||||
| 	/// </summary> | 	/// </summary> | ||||||
| @ -168,101 +321,9 @@ public partial class Chip : UIComponent, IDisposable | |||||||
| 		get => Href; | 		get => Href; | ||||||
| 		set => Href = value; | 		set => Href = value; | ||||||
| 	} | 	} | ||||||
|  | 	#endregion | ||||||
| 
 | 
 | ||||||
| 	/// <summary> | 	#region Lifecycle | ||||||
| 	/// 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> |  | ||||||
| 	/// A string you want to associate with the chip. If the ChildContent is not set this will be shown as chip text. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	[Category(CategoryTypes.Chip.Behavior)] |  | ||||||
| 	public string Text { get; set; } |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// A value that should be managed in the SelectedValues collection. |  | ||||||
| 	/// Note: do not change the value during the chip's lifetime |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	[Category(CategoryTypes.Chip.Behavior)] |  | ||||||
| 	public object Value { get; set; } |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// If true, force browser to redirect outside component router-space. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	[Category(CategoryTypes.Chip.ClickAction)] |  | ||||||
| 	public bool ForceLoad { get; set; } |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// If true, this chip is selected by default if used in a ChipSet.  |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	[Category(CategoryTypes.Chip.Behavior)] |  | ||||||
| 	public bool? Default { 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; } |  | ||||||
| 
 |  | ||||||
| 	/// <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; } |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// Set by MudChipSet |  | ||||||
| 	/// </summary> |  | ||||||
| 	public bool IsChecked |  | ||||||
| 	{ |  | ||||||
| 		get => _isSelected && ChipSet?.Filter == true; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// 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 | ||||||
|  | 	private IEqualityComparer<object> _comparer; | ||||||
|  | 	private HashSet<object> _selectedValues; | ||||||
|  | 	private HashSet<object> _initialSelectedValues; | ||||||
|  | 	private HashSet<Chip> _chips = new(); | ||||||
|  | 	private bool _filter; | ||||||
|  | 	private bool _disposed; | ||||||
|  | 	#endregion | ||||||
| 
 | 
 | ||||||
|    protected string Classname => | 	#region Events | ||||||
|    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> | 	/// <summary> | ||||||
| 	/// Called when the selected chip changes, in Choice mode | 	/// Called when the selected chip changes, in Choice mode | ||||||
| 	/// </summary> | 	/// </summary> | ||||||
| 	[Parameter] | 	[Parameter] | ||||||
| 	public EventCallback<Chip> SelectedChipChanged { get; set; } | 	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 HashSet<object> _selectedValues; |  | ||||||
|    private HashSet<object> _initialSelectedValues; |  | ||||||
| 
 |  | ||||||
|    /// <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> | 	/// <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,150 +41,12 @@ 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; |  | ||||||
|    [Inject] IEventListenerFactory ThrottledEventManagerFactory { get; set; } |  | ||||||
| 
 |  | ||||||
|    #endregion |  | ||||||
| 
 |  | ||||||
|    #region Parameters |  | ||||||
| 
 |  | ||||||
|    [CascadingParameter(Name = "RightToLeft")] public bool RightToLeft { get; set; } |  | ||||||
| 
 |  | ||||||
|    private bool _disableAlpha = false; |  | ||||||
| 
 |  | ||||||
|    /// <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 |  | ||||||
|    { |  | ||||||
|       get => _disableAlpha; |  | ||||||
|       set |  | ||||||
|       { |  | ||||||
|          if (value != _disableAlpha) |  | ||||||
|          { |  | ||||||
|             _disableAlpha = value; |  | ||||||
| 
 |  | ||||||
|             if (value == true) |  | ||||||
|             { |  | ||||||
|                Value = Value.SetAlpha(1.0); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             Text = GetColorTextValue(); |  | ||||||
|          } |  | ||||||
|       } |  | ||||||
|    } |  | ||||||
| 
 |  | ||||||
|    /// <summary> |  | ||||||
|    /// If true, the color field will not be displayed. |  | ||||||
|    /// </summary> |  | ||||||
|    [Parameter] |  | ||||||
|    [Category(CategoryTypes.FormComponent.PickerBehavior)] |  | ||||||
|    public bool DisableColorField { get; set; } = false; |  | ||||||
| 
 |  | ||||||
|    /// <summary> |  | ||||||
|    /// If true, the switch to change color mode will not be displayed. |  | ||||||
|    /// </summary> |  | ||||||
|    [Parameter] |  | ||||||
|    [Category(CategoryTypes.FormComponent.PickerBehavior)] |  | ||||||
|    public bool DisableModeSwitch { get; set; } = false; |  | ||||||
| 
 |  | ||||||
|    /// <summary> |  | ||||||
|    /// If true, textfield inputs and color mode switch will not be displayed. |  | ||||||
|    /// </summary> |  | ||||||
|    [Parameter] |  | ||||||
|    [Category(CategoryTypes.FormComponent.PickerBehavior)] |  | ||||||
|    public bool DisableInputs { get; set; } = false; |  | ||||||
| 
 |  | ||||||
|    /// <summary> |  | ||||||
|    /// If true, hue and alpha sliders will not be displayed. |  | ||||||
|    /// </summary> |  | ||||||
|    [Parameter] |  | ||||||
|    [Category(CategoryTypes.FormComponent.PickerBehavior)] |  | ||||||
|    public bool DisableSliders { get; set; } = false; |  | ||||||
| 
 |  | ||||||
|    /// <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. |  | ||||||
|    /// </summary> |  | ||||||
|    [Parameter] |  | ||||||
|    [Category(CategoryTypes.FormComponent.PickerBehavior)] |  | ||||||
|    public bool DisablePreview { get; set; } = false; |  | ||||||
| 
 |  | ||||||
|    /// <summary> |  | ||||||
|    /// The initial mode (RGB, HSL or HEX) the picker should open. Defaults to RGB  |  | ||||||
|    /// </summary> |  | ||||||
|    [Parameter] |  | ||||||
|    [Category(CategoryTypes.FormComponent.PickerBehavior)] |  | ||||||
|    public ColorPickerMode ColorPickerMode { get; set; } = ColorPickerMode.RGB; |  | ||||||
| 
 |  | ||||||
| 	private ColorPickerView _colorPickerView = ColorPickerView.Spectrum; | 	private ColorPickerView _colorPickerView = ColorPickerView.Spectrum; | ||||||
| 	private ColorPickerView _activeColorPickerView = ColorPickerView.Spectrum; | 	private ColorPickerView _activeColorPickerView = ColorPickerView.Spectrum; | ||||||
| 
 | 
 | ||||||
|    /// <summary> | 	private bool _disableAlpha = false; | ||||||
|    /// The initial view of the picker. Views can be changed if toolbar is enabled.  |  | ||||||
|    /// </summary> |  | ||||||
|    [Parameter] |  | ||||||
|    [Category(CategoryTypes.FormComponent.PickerBehavior)] |  | ||||||
|    public ColorPickerView ColorPickerView |  | ||||||
|    { |  | ||||||
|       get => _colorPickerView; |  | ||||||
|       set |  | ||||||
|       { |  | ||||||
|          if (value != _colorPickerView) |  | ||||||
|          { |  | ||||||
|             _colorPickerView = value; |  | ||||||
|             ChangeView(value).AndForget(); |  | ||||||
|          } |  | ||||||
|       } |  | ||||||
|    } |  | ||||||
| 
 | 
 | ||||||
|    /// <summary> | 	private IEventListener _throttledEventManager; | ||||||
|    /// If true, binding changes occurred also when HSL values changed without a corresponding RGB change  |  | ||||||
|    /// </summary> |  | ||||||
|    [Parameter] |  | ||||||
|    [Category(CategoryTypes.FormComponent.Behavior)] |  | ||||||
|    public bool UpdateBindingIfOnlyHSLChanged { get; set; } = false; |  | ||||||
| 
 |  | ||||||
|    /// <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 |  | ||||||
|    /// </summary> |  | ||||||
|    [Parameter] |  | ||||||
|    [Category(CategoryTypes.FormComponent.Data)] |  | ||||||
|    public Color Value |  | ||||||
|    { |  | ||||||
|       get => _color; |  | ||||||
|       set |  | ||||||
|       { |  | ||||||
|          if (value == null) { return; } |  | ||||||
| 
 |  | ||||||
|          var rgbChanged = value != _color; |  | ||||||
|          var hslChanged = _color == null ? false : value.HslChanged(_color); |  | ||||||
|          _color = value; |  | ||||||
| 
 |  | ||||||
|          if (rgbChanged) |  | ||||||
|          { |  | ||||||
|             if (_skipFeedback == false) |  | ||||||
|             { |  | ||||||
|                UpdateBaseColor(); |  | ||||||
|                UpdateColorSelectorBasedOnRgb(); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             SetTextAsync(GetColorTextValue(), false).AndForget(); |  | ||||||
|             ValueChanged.InvokeAsync(value).AndForget(); |  | ||||||
|             FieldChanged(value); |  | ||||||
|          } |  | ||||||
| 
 |  | ||||||
|          if (rgbChanged == false && UpdateBindingIfOnlyHSLChanged && hslChanged == true) |  | ||||||
|          { |  | ||||||
|             SetTextAsync(GetColorTextValue(), false).AndForget(); |  | ||||||
|             ValueChanged.InvokeAsync(value).AndForget(); |  | ||||||
|             FieldChanged(value); |  | ||||||
|          } |  | ||||||
|       } |  | ||||||
|    } |  | ||||||
| 
 |  | ||||||
|    [Parameter] public EventCallback<Color> ValueChanged { get; set; } |  | ||||||
| 
 | 
 | ||||||
| 	/// <summary> | 	/// <summary> | ||||||
| 	/// MudColor list of predefined colors. The first five colors will show up as the quick colors on preview dot click. | 	/// MudColor list of predefined colors. The first five colors will show up as the quick colors on preview dot click. | ||||||
| @ -224,52 +77,140 @@ public partial class ColorPicker : Picker<Color>, IAsyncDisposable | |||||||
| 		 "#d2effd","#d6e1fc","#d6c9fa","#e9cbfb","#f3d4df","#f9dcd9","#fae3d8","#fcecd7","#fdf2d8","#fefce0","#f7fade","#e3edd6" | 		 "#d2effd","#d6e1fc","#d6c9fa","#e9cbfb","#f3d4df","#f9dcd9","#fae3d8","#fcecd7","#fdf2d8","#fefce0","#f7fade","#e3edd6" | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
|    /// <summary> | 	[Inject] IEventListenerFactory ThrottledEventManagerFactory { get; set; } | ||||||
|    /// 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  |  | ||||||
|    /// Enabled by default |  | ||||||
|    /// </summary> |  | ||||||
|    [Parameter] |  | ||||||
|    [Category(CategoryTypes.FormComponent.PickerBehavior)] |  | ||||||
|    public bool DisableDragEffect { get; set; } = false; |  | ||||||
| 
 |  | ||||||
|    /// <summary> |  | ||||||
|    /// Custom close icon. |  | ||||||
|    /// </summary> |  | ||||||
|    [Parameter] |  | ||||||
|    [Category(CategoryTypes.FormComponent.PickerAppearance)] |  | ||||||
|    public string CloseIcon { get; set; } = Icons.Material.Filled.Close; |  | ||||||
| 
 |  | ||||||
|    /// <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 | 	#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> | ||||||
|  | 	/// Set the R (red) component of the color picker | ||||||
|  | 	/// </summary> | ||||||
|  | 	/// <param name="value">A value between 0 (no red) or 255 (max red)</param> | ||||||
|  | 	public void SetR(int value) => Value = Value.SetR(value); | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// Set the G (green) component of the color picker | ||||||
|  | 	/// </summary> | ||||||
|  | 	/// <param name="value">A value between 0 (no green) or 255 (max green)</param> | ||||||
|  | 	public void SetG(int value) => Value = Value.SetG(value); | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// Set the B (blue) component of the color picker | ||||||
|  | 	/// </summary> | ||||||
|  | 	/// <param name="value">A value between 0 (no blue) or 255 (max blue)</param> | ||||||
|  | 	public void SetB(int value) => Value = Value.SetB(value); | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// Set the H (hue) component of the color picker | ||||||
|  | 	/// </summary> | ||||||
|  | 	/// <param name="value">A value between 0 and 360 (degrees)</param> | ||||||
|  | 	public void SetH(double value) => Value = Value.SetH(value); | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// Set the S (saturation) component of the color picker | ||||||
|  | 	/// </summary> | ||||||
|  | 	/// <param name="value">A value between 0.0 (no saturation) and 1.0 (max saturation)</param> | ||||||
|  | 	public void SetS(double value) => Value = Value.SetS(value); | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// Set the L (Lightness) component of the color picker | ||||||
|  | 	/// </summary> | ||||||
|  | 	/// <param name="value">A value between 0.0 (no light, black) and 1.0 (max light, white)</param> | ||||||
|  | 	public void SetL(double value) => Value = Value.SetL(value); | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// Set the Alpha (transparency) component of the color picker | ||||||
|  | 	/// </summary> | ||||||
|  | 	/// <param name="value">A value between 0.0 (full transparent) and 1.0 (solid) </param> | ||||||
|  | 	public void SetAlpha(double value) => Value = Value.SetAlpha(value); | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// Set the Alpha (transparency) component of the color picker | ||||||
|  | 	/// </summary> | ||||||
|  | 	/// <param name="value">A value between 0 (full transparent) and 1 (solid) </param> | ||||||
|  | 	public void SetAlpha(int value) => Value = Value.SetAlpha(value); | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// Set the color of the picker based on the string input | ||||||
|  | 	/// </summary> | ||||||
|  | 	/// <param name="input">Accepting different formats for a color representation such as rbg, rgba, #</param> | ||||||
|  | 	public void SetInputString(string input) | ||||||
|  | 	{ | ||||||
|  | 		Color color; | ||||||
|  | 		try | ||||||
|  | 		{ | ||||||
|  | 			color = new Color(input); | ||||||
|  | 		} | ||||||
|  | 		catch (Exception) | ||||||
|  | 		{ | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		Value = color; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	protected override Task StringValueChanged(string value) | ||||||
|  | 	{ | ||||||
|  | 		SetInputString(value); | ||||||
|  | 		return Task.CompletedTask; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private bool _attachedMouseEvent = false; | ||||||
|  | 
 | ||||||
|  | 	protected override void OnPickerOpened() | ||||||
|  | 	{ | ||||||
|  | 		base.OnPickerOpened(); | ||||||
|  | 		_attachedMouseEvent = true; | ||||||
|  | 		StateHasChanged(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	protected override void OnPickerClosed() | ||||||
|  | 	{ | ||||||
|  | 		base.OnPickerClosed(); | ||||||
|  | 		RemoveMouseOverEvent().AndForget(); | ||||||
|  | 	} | ||||||
|  | 	private void HandleColorOverlayClicked() | ||||||
|  | 	{ | ||||||
|  | 		UpdateColorBaseOnSelection(); | ||||||
|  | 
 | ||||||
|  | 		if (IsAnyControlVisible() == false) | ||||||
|  | 		{ | ||||||
|  | 			Close(); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private void OnSelectorClicked(MouseEventArgs e) | ||||||
|  | 	{ | ||||||
|  | 		SetSelectorBasedOnMouseEvents(e, false); | ||||||
|  | 		HandleColorOverlayClicked(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private void OnColorOverlayClick(MouseEventArgs e) | ||||||
|  | 	{ | ||||||
|  | 		SetSelectorBasedOnMouseEvents(e, true); | ||||||
|  | 		HandleColorOverlayClicked(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private void OnMouseOver(MouseEventArgs e) | ||||||
|  | 	{ | ||||||
|  | 		if (e.Buttons == 1) | ||||||
|  | 		{ | ||||||
|  | 			SetSelectorBasedOnMouseEvents(e, true); | ||||||
|  | 			UpdateColorBaseOnSelection(); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private void SetSelectorBasedOnMouseEvents(MouseEventArgs e, bool offsetIsAbsolute) | ||||||
|  | 	{ | ||||||
|  | 		_selectorX = (offsetIsAbsolute == true ? e.OffsetX : (e.OffsetX - _selctorSize / 2.0) + _selectorX).EnsureRange(_maxX); | ||||||
|  | 		_selectorY = (offsetIsAbsolute == true ? e.OffsetY : (e.OffsetY - _selctorSize / 2.0) + _selectorY).EnsureRange(_maxY); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	[Parameter] public EventCallback<Color> ValueChanged { get; set; } | ||||||
|  | 
 | ||||||
| 	private void ToggleCollection() | 	private void ToggleCollection() | ||||||
| 	{ | 	{ | ||||||
| 		_collectionOpen = !_collectionOpen; | 		_collectionOpen = !_collectionOpen; | ||||||
| @ -391,156 +332,210 @@ public partial class ColorPicker : Picker<Color>, IAsyncDisposable | |||||||
| 		_selectorX = relation * _maxX; | 		_selectorX = relation * _maxX; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|    #region mouse interactions |  | ||||||
| 
 |  | ||||||
|    private void HandleColorOverlayClicked() |  | ||||||
|    { |  | ||||||
|       UpdateColorBaseOnSelection(); |  | ||||||
| 
 |  | ||||||
|       if (IsAnyControlVisible() == false) |  | ||||||
|       { |  | ||||||
|          Close(); |  | ||||||
|       } |  | ||||||
|    } |  | ||||||
| 
 |  | ||||||
|    private void OnSelectorClicked(MouseEventArgs e) |  | ||||||
|    { |  | ||||||
|       SetSelectorBasedOnMouseEvents(e, false); |  | ||||||
|       HandleColorOverlayClicked(); |  | ||||||
|    } |  | ||||||
| 
 |  | ||||||
|    private void OnColorOverlayClick(MouseEventArgs e) |  | ||||||
|    { |  | ||||||
|       SetSelectorBasedOnMouseEvents(e, true); |  | ||||||
|       HandleColorOverlayClicked(); |  | ||||||
|    } |  | ||||||
| 
 |  | ||||||
|    private void OnMouseOver(MouseEventArgs e) |  | ||||||
|    { |  | ||||||
|       if (e.Buttons == 1) |  | ||||||
|       { |  | ||||||
|          SetSelectorBasedOnMouseEvents(e, true); |  | ||||||
|          UpdateColorBaseOnSelection(); |  | ||||||
|       } |  | ||||||
|    } |  | ||||||
| 
 |  | ||||||
|    private void SetSelectorBasedOnMouseEvents(MouseEventArgs e, bool offsetIsAbsolute) |  | ||||||
|    { |  | ||||||
|       _selectorX = (offsetIsAbsolute == true ? e.OffsetX : (e.OffsetX - _selctorSize / 2.0) + _selectorX).EnsureRange(_maxX); |  | ||||||
|       _selectorY = (offsetIsAbsolute == true ? e.OffsetY : (e.OffsetY - _selctorSize / 2.0) + _selectorY).EnsureRange(_maxY); |  | ||||||
|    } |  | ||||||
| 
 |  | ||||||
| 	#endregion | 	#endregion | ||||||
| 
 | 
 | ||||||
|    #region updating inputs | 	#region Styling | ||||||
| 
 |  | ||||||
|    /// <summary> |  | ||||||
|    /// Set the R (red) component of the color picker |  | ||||||
|    /// </summary> |  | ||||||
|    /// <param name="value">A value between 0 (no red) or 255 (max red)</param> |  | ||||||
|    public void SetR(int value) => Value = Value.SetR(value); |  | ||||||
| 
 |  | ||||||
|    /// <summary> |  | ||||||
|    /// Set the G (green) component of the color picker |  | ||||||
|    /// </summary> |  | ||||||
|    /// <param name="value">A value between 0 (no green) or 255 (max green)</param> |  | ||||||
|    public void SetG(int value) => Value = Value.SetG(value); |  | ||||||
| 
 |  | ||||||
|    /// <summary> |  | ||||||
|    /// Set the B (blue) component of the color picker |  | ||||||
|    /// </summary> |  | ||||||
|    /// <param name="value">A value between 0 (no blue) or 255 (max blue)</param> |  | ||||||
|    public void SetB(int value) => Value = Value.SetB(value); |  | ||||||
| 
 |  | ||||||
|    /// <summary> |  | ||||||
|    /// Set the H (hue) component of the color picker |  | ||||||
|    /// </summary> |  | ||||||
|    /// <param name="value">A value between 0 and 360 (degrees)</param> |  | ||||||
|    public void SetH(double value) => Value = Value.SetH(value); |  | ||||||
| 
 |  | ||||||
|    /// <summary> |  | ||||||
|    /// Set the S (saturation) component of the color picker |  | ||||||
|    /// </summary> |  | ||||||
|    /// <param name="value">A value between 0.0 (no saturation) and 1.0 (max saturation)</param> |  | ||||||
|    public void SetS(double value) => Value = Value.SetS(value); |  | ||||||
| 
 |  | ||||||
|    /// <summary> |  | ||||||
|    /// Set the L (Lightness) component of the color picker |  | ||||||
|    /// </summary> |  | ||||||
|    /// <param name="value">A value between 0.0 (no light, black) and 1.0 (max light, white)</param> |  | ||||||
|    public void SetL(double value) => Value = Value.SetL(value); |  | ||||||
| 
 |  | ||||||
|    /// <summary> |  | ||||||
|    /// Set the Alpha (transparency) component of the color picker |  | ||||||
|    /// </summary> |  | ||||||
|    /// <param name="value">A value between 0.0 (full transparent) and 1.0 (solid) </param> |  | ||||||
|    public void SetAlpha(double value) => Value = Value.SetAlpha(value); |  | ||||||
| 
 |  | ||||||
|    /// <summary> |  | ||||||
|    /// Set the Alpha (transparency) component of the color picker |  | ||||||
|    /// </summary> |  | ||||||
|    /// <param name="value">A value between 0 (full transparent) and 1 (solid) </param> |  | ||||||
|    public void SetAlpha(int value) => Value = Value.SetAlpha(value); |  | ||||||
| 
 |  | ||||||
|    /// <summary> |  | ||||||
|    /// Set the color of the picker based on the string input |  | ||||||
|    /// </summary> |  | ||||||
|    /// <param name="input">Accepting different formats for a color representation such as rbg, rgba, #</param> |  | ||||||
|    public void SetInputString(string input) |  | ||||||
|    { |  | ||||||
|       Color color; |  | ||||||
|       try |  | ||||||
|       { |  | ||||||
|          color = new Color(input); |  | ||||||
|       } |  | ||||||
|       catch (Exception) |  | ||||||
|       { |  | ||||||
|          return; |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       Value = color; |  | ||||||
|    } |  | ||||||
| 
 |  | ||||||
|    protected override Task StringValueChanged(string value) |  | ||||||
|    { |  | ||||||
|       SetInputString(value); |  | ||||||
|       return Task.CompletedTask; |  | ||||||
|    } |  | ||||||
| 
 |  | ||||||
|    private bool _attachedMouseEvent = false; |  | ||||||
| 
 |  | ||||||
|    protected override void OnPickerOpened() |  | ||||||
|    { |  | ||||||
|       base.OnPickerOpened(); |  | ||||||
|       _attachedMouseEvent = true; |  | ||||||
|       StateHasChanged(); |  | ||||||
|    } |  | ||||||
| 
 |  | ||||||
|    protected override void OnPickerClosed() |  | ||||||
|    { |  | ||||||
|       base.OnPickerClosed(); |  | ||||||
|       RemoveMouseOverEvent().AndForget(); |  | ||||||
|    } |  | ||||||
| 
 |  | ||||||
|    #endregion |  | ||||||
| 
 |  | ||||||
|    #region helper |  | ||||||
| 
 | 
 | ||||||
|  | 	private bool IsAnyControlVisible() => !(DisablePreview && DisableSliders && DisableInputs); | ||||||
|  | 	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(); | ||||||
| 	private string GetSelectorLocation() => $"translate({Math.Round(_selectorX, 2).ToString(CultureInfo.InvariantCulture)}px, {Math.Round(_selectorY, 2).ToString(CultureInfo.InvariantCulture)}px);"; | 	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 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 int GetHexColorInputMaxLength() => DisableAlpha ? 7 : 9; | ||||||
| 
 | 
 | ||||||
|    private EventCallback<MouseEventArgs> GetEventCallback() => EventCallback.Factory.Create<MouseEventArgs>(this, () => Close()); | 	/// <summary> | ||||||
|    private bool IsAnyControlVisible() => !(DisablePreview && DisableSliders && DisableInputs); | 	/// When set to true, no mouse move events in the spectrum mode will be captured, so the selector circle won't fellow the mouse.  | ||||||
|    private EventCallback<MouseEventArgs> GetSelectPaletteColorCallback(Color color) => new EventCallbackFactory().Create(this, (MouseEventArgs e) => SelectPaletteColor(color)); | 	/// Under some conditions like long latency the visual representation might not reflect the user behaviour anymore. So, it can be disabled  | ||||||
|  | 	/// Enabled by default | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.FormComponent.PickerBehavior)] | ||||||
|  | 	public bool DisableDragEffect { get; set; } = false; | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// Custom close icon. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.FormComponent.PickerAppearance)] | ||||||
|  | 	public string CloseIcon { get; set; } = Icons.Material.Filled.Close; | ||||||
|  | 
 | ||||||
|  | 	/// <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 | ||||||
|  | 	{ | ||||||
|  | 		get => _disableAlpha; | ||||||
|  | 		set | ||||||
|  | 		{ | ||||||
|  | 			if (value != _disableAlpha) | ||||||
|  | 			{ | ||||||
|  | 				_disableAlpha = value; | ||||||
|  | 
 | ||||||
|  | 				if (value == true) | ||||||
|  | 				{ | ||||||
|  | 					Value = Value.SetAlpha(1.0); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				Text = GetColorTextValue(); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// If true, the color field will not be displayed. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.FormComponent.PickerBehavior)] | ||||||
|  | 	public bool DisableColorField { get; set; } = false; | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// If true, the switch to change color mode will not be displayed. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.FormComponent.PickerBehavior)] | ||||||
|  | 	public bool DisableModeSwitch { get; set; } = false; | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// If true, textfield inputs and color mode switch will not be displayed. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.FormComponent.PickerBehavior)] | ||||||
|  | 	public bool DisableInputs { get; set; } = false; | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// If true, hue and alpha sliders will not be displayed. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.FormComponent.PickerBehavior)] | ||||||
|  | 	public bool DisableSliders { get; set; } = false; | ||||||
|  | 
 | ||||||
|  | 	/// <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. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.FormComponent.PickerBehavior)] | ||||||
|  | 	public bool DisablePreview { get; set; } = false; | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// The initial mode (RGB, HSL or HEX) the picker should open. Defaults to RGB  | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.FormComponent.PickerBehavior)] | ||||||
|  | 	public ColorPickerMode ColorPickerMode { get; set; } = ColorPickerMode.RGB; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// The initial view of the picker. Views can be changed if toolbar is enabled.  | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.FormComponent.PickerBehavior)] | ||||||
|  | 	public ColorPickerView ColorPickerView | ||||||
|  | 	{ | ||||||
|  | 		get => _colorPickerView; | ||||||
|  | 		set | ||||||
|  | 		{ | ||||||
|  | 			if (value != _colorPickerView) | ||||||
|  | 			{ | ||||||
|  | 				_colorPickerView = value; | ||||||
|  | 				ChangeView(value).AndForget(); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// If true, binding changes occurred also when HSL values changed without a corresponding RGB change  | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.FormComponent.Behavior)] | ||||||
|  | 	public bool UpdateBindingIfOnlyHSLChanged { get; set; } = false; | ||||||
|  | 
 | ||||||
|  | 	/// <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 | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.FormComponent.Data)] | ||||||
|  | 	public Color Value | ||||||
|  | 	{ | ||||||
|  | 		get => _color; | ||||||
|  | 		set | ||||||
|  | 		{ | ||||||
|  | 			if (value == null) { return; } | ||||||
|  | 
 | ||||||
|  | 			var rgbChanged = value != _color; | ||||||
|  | 			var hslChanged = _color == null ? false : value.HslChanged(_color); | ||||||
|  | 			_color = value; | ||||||
|  | 
 | ||||||
|  | 			if (rgbChanged) | ||||||
|  | 			{ | ||||||
|  | 				if (_skipFeedback == false) | ||||||
|  | 				{ | ||||||
|  | 					UpdateBaseColor(); | ||||||
|  | 					UpdateColorSelectorBasedOnRgb(); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				SetTextAsync(GetColorTextValue(), false).AndForget(); | ||||||
|  | 				ValueChanged.InvokeAsync(value).AndForget(); | ||||||
|  | 				FieldChanged(value); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if (rgbChanged == false && UpdateBindingIfOnlyHSLChanged && hslChanged == true) | ||||||
|  | 			{ | ||||||
|  | 				SetTextAsync(GetColorTextValue(), false).AndForget(); | ||||||
|  | 				ValueChanged.InvokeAsync(value).AndForget(); | ||||||
|  | 				FieldChanged(value); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
|    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; | ||||||
| 
 |  | ||||||
| 	protected DatePickerBase() : base(new DefaultConverter<DateTime?> |  | ||||||
| 	{ |  | ||||||
| 		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> |  | ||||||
| 	/// Min selectable date. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	[Category(CategoryTypes.FormComponent.Validation)] |  | ||||||
| 	public DateTime? MinDate { get; set; } |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// First view to show in the MudDatePicker. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	[Category(CategoryTypes.FormComponent.PickerBehavior)] |  | ||||||
| 	public OpenTo OpenTo { get; set; } = OpenTo.Date; |  | ||||||
| 
 |  | ||||||
| 	/// <summary> |  | ||||||
| 	/// String Format for selected date view |  | ||||||
| 	/// </summary> |  | ||||||
| 	[Parameter] |  | ||||||
| 	[Category(CategoryTypes.FormComponent.Behavior)] |  | ||||||
| 	public string DateFormat |  | ||||||
| 	{ |  | ||||||
| 		get |  | ||||||
| 		{ |  | ||||||
| 			return (Converter as DefaultConverter<DateTime?>)?.Format; |  | ||||||
| 		} |  | ||||||
| 		set |  | ||||||
| 		{ |  | ||||||
| 			if (Converter is DefaultConverter<DateTime?> defaultConverter) |  | ||||||
| 			{ |  | ||||||
| 				defaultConverter.Format = value; |  | ||||||
| 				_dateFormatTouched = true; |  | ||||||
| 			} |  | ||||||
| 			DateFormatChanged(value); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/// <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> |  | ||||||
| 	/// 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) |  | ||||||
| 				return; |  | ||||||
| 			_picker_month = value; |  | ||||||
| 			InvokeAsync(StateHasChanged); |  | ||||||
| 			PickerMonthChanged.InvokeAsync(value); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	private DateTime? _picker_month; | 	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> |  | ||||||
| 	/// 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); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	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> |  | ||||||
| 	/// Custom next icon. |  | ||||||
| 	/// </summary> |  | ||||||
| 	[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; | 	protected virtual bool IsRange { get; } = false; | ||||||
| 
 |  | ||||||
| 	protected OpenTo CurrentView; | 	protected OpenTo CurrentView; | ||||||
|  | 	/// <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. | ||||||
|  | 	/// </summary> | ||||||
|  | 	private string _componentId = Guid.NewGuid().ToString(); | ||||||
| 
 | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// Is set to true to scroll to the actual year after the next render | ||||||
|  | 	/// </summary> | ||||||
|  | 	private bool _scrollToYearAfterRender = false; | ||||||
|  | 	#endregion | ||||||
|  | 
 | ||||||
|  | 	#region Events | ||||||
|  | 	private Typo GetMonthTypo(DateTime month) | ||||||
|  | 	{ | ||||||
|  | 		if (GetMonthStart(0) == month) | ||||||
|  | 			return Typo.h5; | ||||||
|  | 		return Typo.subtitle1; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	protected abstract DateTime GetCalendarStartOfMonth(); | ||||||
|  | 
 | ||||||
|  | 	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); | ||||||
|  | 	public async void ScrollToYear() | ||||||
|  | 	{ | ||||||
|  | 		_scrollToYearAfterRender = false; | ||||||
|  | 		var id = $"{_componentId}{GetMonthStart(0).Year}"; | ||||||
|  | 		await ScrollManager.ScrollToYearAsync(id); | ||||||
|  | 		StateHasChanged(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private int GetMinYear() | ||||||
|  | 	{ | ||||||
|  | 		if (MinDate.HasValue) | ||||||
|  | 			return MinDate.Value.Year; | ||||||
|  | 		return DateTime.Today.Year - 100; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private int GetMaxYear() | ||||||
|  | 	{ | ||||||
|  | 		if (MaxDate.HasValue) | ||||||
|  | 			return MaxDate.Value.Year; | ||||||
|  | 		return DateTime.Today.Year + 100; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 	private Typo GetYearTypo(int year) | ||||||
|  | 	{ | ||||||
|  | 		if (year == GetMonthStart(0).Year) | ||||||
|  | 			return Typo.h5; | ||||||
|  | 		return Typo.subtitle1; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private void OnFormattedDateClick() | ||||||
|  | 	{ | ||||||
|  | 		// todo: raise an event the user can handle | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 	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); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	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> | ||||||
|  | 	/// Fired when the date changes. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] public EventCallback<DateTime?> PickerMonthChanged { get; set; } | ||||||
|  | 
 | ||||||
|  | 	#endregion | ||||||
|  | 
 | ||||||
|  | 	#region Content | ||||||
|  | 	[Inject] protected IScrollManager ScrollManager { get; set; } | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// 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> | 	/// <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. | 	/// 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> | 	/// </summary> | ||||||
| 	private string _componentId = Guid.NewGuid().ToString(); | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.FormComponent.PickerBehavior)] | ||||||
|  | 	public DateTime? PickerMonth | ||||||
|  | 	{ | ||||||
|  | 		get => _picker_month; | ||||||
|  | 		set | ||||||
|  | 		{ | ||||||
|  | 			if (value == _picker_month) | ||||||
|  | 				return; | ||||||
|  | 			_picker_month = value; | ||||||
|  | 			InvokeAsync(StateHasChanged); | ||||||
|  | 			PickerMonthChanged.InvokeAsync(value); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	#endregion | ||||||
| 
 | 
 | ||||||
|  | 	#region Styling | ||||||
|  | 	private string GetMonthClasses(DateTime month) | ||||||
|  | 	{ | ||||||
|  | 		if (GetMonthStart(0) == month) | ||||||
|  | 			return $"picker-month-selected mud-{Color.ToDescription()}-text"; | ||||||
|  | 		return null; | ||||||
|  | 	} | ||||||
| 	/// <summary> | 	/// <summary> | ||||||
| 	/// Is set to true to scroll to the actual year after the next render | 	/// Function to conditionally apply new classes to specific days | ||||||
| 	/// </summary> | 	/// </summary> | ||||||
| 	private bool _scrollToYearAfterRender = false; | 	[Parameter] | ||||||
| 
 | 	[Category(CategoryTypes.FormComponent.Appearance)] | ||||||
| 	public async void ScrollToYear() | 	public Func<DateTime, string> AdditionalDateClassesFunc { get; set; } | ||||||
| 	{ |  | ||||||
| 		_scrollToYearAfterRender = false; |  | ||||||
| 		var id = $"{_componentId}{GetMonthStart(0).Year}"; |  | ||||||
| 		await ScrollManager.ScrollToYearAsync(id); |  | ||||||
| 		StateHasChanged(); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	private int GetMinYear() |  | ||||||
| 	{ |  | ||||||
| 		if (MinDate.HasValue) |  | ||||||
| 			return MinDate.Value.Year; |  | ||||||
| 		return DateTime.Today.Year - 100; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	private int GetMaxYear() |  | ||||||
| 	{ |  | ||||||
| 		if (MaxDate.HasValue) |  | ||||||
| 			return MaxDate.Value.Year; |  | ||||||
| 		return DateTime.Today.Year + 100; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	private string GetYearClasses(int year) | 	private string GetYearClasses(int year) | ||||||
| 	{ | 	{ | ||||||
| 		if (year == GetMonthStart(0).Year) | 		if (year == GetMonthStart(0).Year) | ||||||
| @ -508,57 +419,162 @@ 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. | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.FormComponent.PickerAppearance)] | ||||||
|  | 	public string NextIcon { get; set; } = Icons.Material.Filled.ChevronRight; | ||||||
|  | 
 | ||||||
|  | 	 | ||||||
|  | 
 | ||||||
|  | 	/// <summary> | ||||||
|  | 	/// String Format for selected date view | ||||||
|  | 	/// </summary> | ||||||
|  | 	[Parameter] | ||||||
|  | 	[Category(CategoryTypes.FormComponent.Behavior)] | ||||||
|  | 	public string DateFormat | ||||||
| 	{ | 	{ | ||||||
| 		if (year == GetMonthStart(0).Year) | 		get | ||||||
| 			return Typo.h5; |  | ||||||
| 		return Typo.subtitle1; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	private void OnFormattedDateClick() |  | ||||||
| 		{ | 		{ | ||||||
| 		// todo: raise an event the user can handle | 			return (Converter as DefaultConverter<DateTime?>)?.Format; | ||||||
| 		} | 		} | ||||||
| 
 | 		set | ||||||
| 
 |  | ||||||
| 	private IEnumerable<DateTime> GetAllMonths() |  | ||||||
| 		{ | 		{ | ||||||
| 		var current = GetMonthStart(0); | 			if (Converter is DefaultConverter<DateTime?> defaultConverter) | ||||||
| 		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); | 				defaultConverter.Format = value; | ||||||
| 		return Culture.DateTimeFormat.AbbreviatedMonthNames[calendarMonth - 1]; | 				_dateFormatTouched = true; | ||||||
| 			} | 			} | ||||||
|  | 			DateFormatChanged(value); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	#endregion | ||||||
| 
 | 
 | ||||||
| 	private string GetMonthName(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 | ||||||
| 	{ | 	{ | ||||||
| 		var calendarMonth = Culture.Calendar.GetMonth(month); | 		get => _isDateDisabledFunc; | ||||||
| 		return Culture.DateTimeFormat.MonthNames[calendarMonth - 1]; | 		set | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	private string GetMonthClasses(DateTime month) |  | ||||||
| 		{ | 		{ | ||||||
| 		if (GetMonthStart(0) == month) | 			_isDateDisabledFunc = value ?? (_ => false); | ||||||
| 			return $"picker-month-selected mud-{Color.ToDescription()}-text"; |  | ||||||
| 		return null; |  | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
|  | 	private Func<DateTime, bool> _isDateDisabledFunc = _ => false; | ||||||
|  | 	#endregion | ||||||
| 
 | 
 | ||||||
| 	private Typo GetMonthTypo(DateTime month) | 	#region Lifecycle | ||||||
|  | 
 | ||||||
|  | 	protected DatePickerBase() : base(new DefaultConverter<DateTime?> | ||||||
| 	{ | 	{ | ||||||
| 		if (GetMonthStart(0) == month) | 		Format = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern, | ||||||
| 			return Typo.h5; | 		Culture = CultureInfo.CurrentCulture | ||||||
| 		return Typo.subtitle1; | 	}) | ||||||
|  | 	{ | ||||||
|  | 		AdornmentAriaLabel = "Open Date Picker"; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 	protected override void OnInitialized() | 	protected override void OnInitialized() | ||||||
| 	{ | 	{ | ||||||
| 		base.OnInitialized(); | 		base.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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 stm
						stm