parent
8049495a57
commit
08a8a5338f
@ -0,0 +1,26 @@
|
||||
@namespace Connected.Components
|
||||
|
||||
@inherits UIComponent
|
||||
|
||||
<div @attributes="UserAttributes" role="alert" class="@ClassList" Style="@Style" @onclick="OnClick">
|
||||
<div class="@ClassPosition">
|
||||
|
||||
@if (!GlyphVisible)
|
||||
{
|
||||
<div class="alert-icon alert-icon-left">
|
||||
<Icon Glyph="@Glyph" />
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="alert-message">
|
||||
@ChildContent
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (CloseGlyphVisible)
|
||||
{
|
||||
<div class="alert-close">
|
||||
<ToggleIconButton Glyph="@CloseGlyph" @onclick="OnCloseGlyphClick" Size="Size.Small" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
@ -0,0 +1,10 @@
|
||||
@namespace Connected.Components
|
||||
@inherits UIComponent
|
||||
|
||||
<header @attributes="UserAttributes" class="@ClassList" style="@Style">
|
||||
<ToolBar Dense="@Dense" DisableGutters="@DisableGutters" Class="@ToolBarClassList()">
|
||||
@ChildContent
|
||||
</ToolBar>
|
||||
</header>
|
||||
|
||||
|
@ -0,0 +1,96 @@
|
||||
@namespace Connected.Components
|
||||
@inherits InputBase<T>
|
||||
@typeparam T
|
||||
|
||||
<CascadingValue Name="SubscribeToParentForm" Value="false" IsFixed="true">
|
||||
<div class="@AutocompleteClassList">
|
||||
<InputControl Label="@Label" Variant="@Variant" HelperText="@HelperText" HelperTextOnFocus="@HelperTextOnFocus" FullWidth="@FullWidth" Margin="@Margin" Class="@ClassList()" Style="@Style"
|
||||
Error="@Error" ErrorText="@ErrorText" Disabled="@Disabled" @onclick="@ToggleMenu" Required="@Required" ForId="@FieldId">
|
||||
<InputContent>
|
||||
<Input @ref="_elementReference" @key="_elementKey" InputType="InputType.Text"
|
||||
Class="select-input" Margin="@Margin"
|
||||
Variant="@Variant"
|
||||
TextUpdateSuppression="@TextUpdateSuppression"
|
||||
Value="@Text" DisableUnderLine="@DisableUnderLine"
|
||||
Disabled="@Disabled" ReadOnly="@ReadOnly" Error="@Error"
|
||||
OnAdornmentClick="@OnAdornmentClick" AdornmentIcon="@CurrentIcon" Adornment="@Adornment" AdornmentColor="@AdornmentColor" IconSize="@IconSize" AdornmentText="@AdornmentText"
|
||||
Clearable="@Clearable" OnClearButtonClick="@OnClearButtonClick"
|
||||
@attributes="UserAttributes"
|
||||
TextChanged="OnTextChanged" OnBlur="OnInputBlurred"
|
||||
OnKeyDown="@this.OnInputKeyDown"
|
||||
OnKeyUp="@this.OnInputKeyUp" autocomplete=@("disabled-"+Guid.NewGuid()) KeyUpPreventDefault="KeyUpPreventDefault"
|
||||
Placeholder="@Placeholder" Immediate="true"
|
||||
InputMode="@InputMode" Pattern="@Pattern"
|
||||
T="string" />
|
||||
|
||||
@if (ShowProgressIndicator && IsLoading)
|
||||
{
|
||||
@if (ProgressIndicatorTemplate is not null)
|
||||
{
|
||||
@ProgressIndicatorTemplate
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="@CircularProgressClassList">
|
||||
<ProgressCircular Color="ProgressIndicatorColor" Indeterminate="true" Size="Size.Small"/>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
<Popover Open="@IsOpen" MaxHeight="@MaxHeight" AnchorOrigin="@AnchorOrigin" TransformOrigin="@TransformOrigin" Class="@PopoverClass" RelativeWidth="true">
|
||||
@if(ProgressIndicatorInPopoverTemplate != null && IsLoading)
|
||||
{
|
||||
@ProgressIndicatorInPopoverTemplate
|
||||
}
|
||||
else if (_items != null && _items.Length != 0)
|
||||
{
|
||||
<List Clickable="true" Dense="@Dense">
|
||||
@for (var index = 0; index < _items.Length; index++)
|
||||
{
|
||||
var item = _items[index];
|
||||
bool is_selected = index == _selectedListItemIndex;
|
||||
bool is_disabled = !_enabledItemIndices.Contains(index);
|
||||
<ListItem @key="@item" id="@GetListItemId(index)" Disabled="@(is_disabled)" OnClick="@(async() => await ListItemOnClick(item))" OnClickHandlerPreventDefault="true" Class="@(is_selected ? "selected-item primary-text primary-hover" : "")">
|
||||
@if (ItemTemplate == null)
|
||||
{
|
||||
@GetItemString(item)
|
||||
}
|
||||
else if (is_disabled && ItemDisabledTemplate is not null)
|
||||
{
|
||||
@ItemDisabledTemplate(item)
|
||||
}
|
||||
else if (is_selected)
|
||||
{
|
||||
@if (ItemSelectedTemplate is null)
|
||||
@ItemTemplate(item)
|
||||
else
|
||||
@ItemSelectedTemplate(item)
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@ItemTemplate(item)
|
||||
}
|
||||
</ListItem>
|
||||
}
|
||||
@if (MoreItemsTemplate is not null && _itemsReturned > MaxItems)
|
||||
{
|
||||
<div class="pa-1">
|
||||
@MoreItemsTemplate
|
||||
</div>
|
||||
}
|
||||
</List>
|
||||
}
|
||||
else if (NoItemsTemplate is not null)
|
||||
{
|
||||
<div class="pa-1">
|
||||
@NoItemsTemplate
|
||||
</div>
|
||||
}
|
||||
</Popover>
|
||||
</InputContent>
|
||||
</InputControl>
|
||||
</div>
|
||||
</CascadingValue>
|
||||
|
||||
<Overlay Visible="IsOpen" OnClick="@ToggleMenu" @ontouchstart="@ToggleMenu" LockScroll="false" />
|
@ -0,0 +1,696 @@
|
||||
using Connected.Utilities;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
|
||||
namespace Connected.Components;
|
||||
|
||||
public partial class Autocomplete<T> : InputBase<T>, IDisposable
|
||||
{
|
||||
private Func<T, string>? _toStringFunc;
|
||||
private Task _currentSearchTask;
|
||||
private CancellationTokenSource _cancellationTokenSrc;
|
||||
private bool _isOpen;
|
||||
private Timer _timer;
|
||||
private T[] _items;
|
||||
private int _selectedListItemIndex = 0;
|
||||
private IList<int> _enabledItemIndices = new List<int>();
|
||||
private int _itemsReturned; //the number of items returned by the search function
|
||||
int _elementKey = 0;
|
||||
/// <summary>
|
||||
/// This boolean will keep track if the clear function is called too keep the set text function to be called.
|
||||
/// </summary>
|
||||
private bool _isCleared;
|
||||
private Input<string> _elementReference;
|
||||
/// <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 readonly string _componentId = Guid.NewGuid().ToString();
|
||||
|
||||
public Autocomplete()
|
||||
{
|
||||
Adornment = Adornment.End;
|
||||
IconSize = Size.Medium;
|
||||
}
|
||||
|
||||
[Inject]
|
||||
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 Icon
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string OpenIcon { get; set; } = Icons.Material.Filled.ArrowDropDown;
|
||||
/// <summary>
|
||||
/// The Close Autocomplete Icon
|
||||
/// </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;
|
||||
|
||||
Converter = new Converter<T>
|
||||
{
|
||||
SetFunc = _toStringFunc ?? (x => x?.ToString()),
|
||||
};
|
||||
}
|
||||
}
|
||||
/// <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>
|
||||
/// 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.
|
||||
/// This can be used to cancel expensive asynchronous work occuring within the SearchFunc itself.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<string, CancellationToken, Task<IEnumerable<T>>> SearchFuncWithCancel { get; set; }
|
||||
/// <summary>
|
||||
/// The SearchFunc returns a list of items matching the typed text
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
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>
|
||||
/// An event triggered when the state of IsOpen has changed
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
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>
|
||||
/// Button click event for clear button. Called after text and value has been cleared.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public EventCallback<MouseEventArgs> OnClearButtonClick { get; set; }
|
||||
|
||||
private string CurrentIcon => !string.IsNullOrWhiteSpace(AdornmentIcon) ? AdornmentIcon : _isOpen ? CloseIcon : OpenIcon;
|
||||
|
||||
protected string ClassList()
|
||||
{
|
||||
return new CssBuilder("select")
|
||||
.AddClass(Class)
|
||||
.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();
|
||||
}
|
||||
|
||||
public async Task SelectOption(T value)
|
||||
{
|
||||
await SetValueAsync(value);
|
||||
|
||||
if (_items is not null)
|
||||
_selectedListItemIndex = Array.IndexOf(_items, value);
|
||||
|
||||
var optionText = GetItemString(value);
|
||||
|
||||
if (!_isCleared)
|
||||
await SetTextAsync(optionText, false);
|
||||
|
||||
_timer?.Dispose();
|
||||
|
||||
IsOpen = false;
|
||||
|
||||
BeginValidate();
|
||||
|
||||
if (!_isCleared)
|
||||
_elementReference?.SetText(optionText);
|
||||
|
||||
_elementReference?.FocusAsync().AndForget();
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
/// <summary>
|
||||
/// Toggle the menu (if not disabled or not readonly, and is opened).
|
||||
/// </summary>
|
||||
public async Task ToggleMenu()
|
||||
{
|
||||
if ((Disabled || ReadOnly) && !IsOpen)
|
||||
return;
|
||||
|
||||
await ChangeMenu(!IsOpen);
|
||||
}
|
||||
|
||||
private async Task ChangeMenu(bool open)
|
||||
{
|
||||
if (open)
|
||||
{
|
||||
if (SelectOnClick)
|
||||
await _elementReference.SelectAsync();
|
||||
|
||||
await OnSearchAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
_timer?.Dispose();
|
||||
|
||||
RestoreScrollPosition();
|
||||
|
||||
await CoerceTextToValue();
|
||||
|
||||
IsOpen = false;
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
_timer?.Dispose();
|
||||
// This keeps the text from being set when clear() was called
|
||||
if (_isCleared)
|
||||
return Task.CompletedTask;
|
||||
|
||||
return base.UpdateTextPropertyAsync(updateValue);
|
||||
}
|
||||
|
||||
protected override async Task UpdateValuePropertyAsync(bool updateText)
|
||||
{
|
||||
_timer?.Dispose();
|
||||
|
||||
if (ResetValueOnEmptyText && string.IsNullOrWhiteSpace(Text))
|
||||
await SetValueAsync(default, updateText);
|
||||
|
||||
if (DebounceInterval <= 0)
|
||||
await OnSearchAsync();
|
||||
else
|
||||
_timer = new Timer(OnTimerComplete, null, DebounceInterval, Timeout.Infinite);
|
||||
}
|
||||
|
||||
private void OnTimerComplete(object stateInfo)
|
||||
{
|
||||
InvokeAsync(OnSearchAsync);
|
||||
}
|
||||
private void CancelToken()
|
||||
{
|
||||
try
|
||||
{
|
||||
_cancellationTokenSrc?.Cancel();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
_cancellationTokenSrc = new CancellationTokenSource();
|
||||
}
|
||||
/// <remarks>
|
||||
/// This async method needs to return a task and be awaited in order for
|
||||
/// unit tests that trigger this method to work correctly.
|
||||
/// </remarks>
|
||||
private async Task OnSearchAsync()
|
||||
{
|
||||
if (MinCharacters > 0 && (string.IsNullOrWhiteSpace(Text) || Text.Length < MinCharacters))
|
||||
{
|
||||
IsOpen = false;
|
||||
StateHasChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
IEnumerable<T> searchedItems = Array.Empty<T>();
|
||||
CancelToken();
|
||||
|
||||
try
|
||||
{
|
||||
if (ProgressIndicatorInPopoverTemplate is not null)
|
||||
IsOpen = true;
|
||||
|
||||
var searchTask = SearchFuncWithCancel is not null ? SearchFuncWithCancel(Text, _cancellationTokenSrc.Token) : SearchFunc(Text);
|
||||
|
||||
_currentSearchTask = searchTask;
|
||||
|
||||
StateHasChanged();
|
||||
|
||||
searchedItems = await searchTask ?? Array.Empty<T>();
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine($"The search function failed to return results: {e.Message}");
|
||||
}
|
||||
|
||||
_itemsReturned = searchedItems.Count();
|
||||
|
||||
if (MaxItems.HasValue)
|
||||
searchedItems = searchedItems.Take(MaxItems.Value);
|
||||
|
||||
_items = searchedItems.ToArray();
|
||||
_enabledItemIndices = _items.Select((item, idx) => (item, idx)).Where(tuple => ItemDisabledFunc?.Invoke(tuple.item) != true).Select(tuple => tuple.idx).ToList();
|
||||
_selectedListItemIndex = _enabledItemIndices.Any() ? _enabledItemIndices.First() : -1;
|
||||
|
||||
IsOpen = true;
|
||||
|
||||
if (_items?.Length == 0)
|
||||
{
|
||||
await CoerceValueToText();
|
||||
|
||||
StateHasChanged();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the autocomplete's text
|
||||
/// </summary>
|
||||
public async Task Clear()
|
||||
{
|
||||
_isCleared = true;
|
||||
IsOpen = false;
|
||||
|
||||
await SetTextAsync(string.Empty, updateValue: false);
|
||||
await CoerceValueToText();
|
||||
|
||||
if (_elementReference is not null)
|
||||
await _elementReference.SetText(string.Empty);
|
||||
|
||||
_timer?.Dispose();
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
protected override async void ResetValue()
|
||||
{
|
||||
await Clear();
|
||||
base.ResetValue();
|
||||
}
|
||||
|
||||
private string GetItemString(T item)
|
||||
{
|
||||
if (item is null)
|
||||
return string.Empty;
|
||||
try
|
||||
{
|
||||
return Converter.Set(item);
|
||||
}
|
||||
catch (NullReferenceException) { }
|
||||
|
||||
return "null";
|
||||
}
|
||||
|
||||
internal virtual async Task OnInputKeyDown(KeyboardEventArgs args)
|
||||
{
|
||||
switch (args.Key)
|
||||
{
|
||||
case "Tab":
|
||||
// NOTE: We need to catch Tab in Keydown because a tab will move focus to the next element and thus
|
||||
// in OnInputKeyUp we'd never get the tab key
|
||||
if (!IsOpen)
|
||||
return;
|
||||
if (SelectValueOnTab)
|
||||
await OnEnterKey();
|
||||
else
|
||||
IsOpen = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual async Task OnInputKeyUp(KeyboardEventArgs args)
|
||||
{
|
||||
switch (args.Key)
|
||||
{
|
||||
case "Enter":
|
||||
case "NumpadEnter":
|
||||
if (!IsOpen)
|
||||
await ToggleMenu();
|
||||
else
|
||||
await OnEnterKey();
|
||||
|
||||
break;
|
||||
case "ArrowDown":
|
||||
if (!IsOpen)
|
||||
await ToggleMenu();
|
||||
else
|
||||
{
|
||||
var increment = _enabledItemIndices.ElementAtOrDefault(_enabledItemIndices.IndexOf(_selectedListItemIndex) + 1) - _selectedListItemIndex;
|
||||
|
||||
await SelectNextItem(increment < 0 ? 1 : increment);
|
||||
}
|
||||
|
||||
break;
|
||||
case "ArrowUp":
|
||||
if (args.AltKey)
|
||||
await ChangeMenu(false);
|
||||
else if (!IsOpen)
|
||||
await ToggleMenu();
|
||||
else
|
||||
{
|
||||
var decrement = _selectedListItemIndex - _enabledItemIndices.ElementAtOrDefault(_enabledItemIndices.IndexOf(_selectedListItemIndex) - 1);
|
||||
await SelectNextItem(-(decrement < 0 ? 1 : decrement));
|
||||
}
|
||||
|
||||
break;
|
||||
case "Escape":
|
||||
await ChangeMenu(false);
|
||||
|
||||
break;
|
||||
case "Tab":
|
||||
await Task.Delay(1);
|
||||
|
||||
if (!IsOpen)
|
||||
return;
|
||||
|
||||
if (SelectValueOnTab)
|
||||
await OnEnterKey();
|
||||
else
|
||||
await ToggleMenu();
|
||||
|
||||
break;
|
||||
case "Backspace":
|
||||
if (args.CtrlKey && args.ShiftKey)
|
||||
Reset();
|
||||
break;
|
||||
}
|
||||
|
||||
base.InvokeKeyUp(args);
|
||||
}
|
||||
|
||||
private ValueTask SelectNextItem(int increment)
|
||||
{
|
||||
if (increment == 0 || _items is null || !_items.Any() || !_enabledItemIndices.Any())
|
||||
return ValueTask.CompletedTask;
|
||||
// if we are at the end, or the beginning we just do an rollover
|
||||
_selectedListItemIndex = Math.Clamp(value: (10 * _items.Length + _selectedListItemIndex + increment) % _items.Length, min: 0, max: _items.Length - 1);
|
||||
|
||||
return ScrollToListItem(_selectedListItemIndex);
|
||||
}
|
||||
/// <summary>
|
||||
/// Scroll to a specific item index in the Autocomplete list of items.
|
||||
/// </summary>
|
||||
/// <param name="index">the index to scroll to</param>
|
||||
public ValueTask ScrollToListItem(int index)
|
||||
{
|
||||
var id = GetListItemId(index);
|
||||
//id of the scrolled element
|
||||
return ScrollManager.ScrollToListItemAsync(id);
|
||||
}
|
||||
/*
|
||||
* This restores the scroll position after closing the menu and element being 0
|
||||
*/
|
||||
private void RestoreScrollPosition()
|
||||
{
|
||||
if (_selectedListItemIndex != 0)
|
||||
return;
|
||||
|
||||
ScrollManager.ScrollToListItemAsync(GetListItemId(0));
|
||||
}
|
||||
|
||||
private string GetListItemId(in int index)
|
||||
{
|
||||
return $"{_componentId}_item{index}";
|
||||
}
|
||||
|
||||
internal Task OnEnterKey()
|
||||
{
|
||||
if (!IsOpen)
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (_items is null || !_items.Any())
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (_selectedListItemIndex >= 0 && _selectedListItemIndex < _items.Length)
|
||||
return SelectOption(_items[_selectedListItemIndex]);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task OnInputBlurred(FocusEventArgs args)
|
||||
{
|
||||
OnBlur.InvokeAsync(args);
|
||||
|
||||
return Task.CompletedTask;
|
||||
// we should not validate on blur in autocomplete, because the user needs to click out of the input to select a value,
|
||||
// resulting in a premature validation. thus, don't call base
|
||||
//base.OnBlurred(args);
|
||||
}
|
||||
|
||||
private Task CoerceTextToValue()
|
||||
{
|
||||
if (!CoerceText)
|
||||
return Task.CompletedTask;
|
||||
|
||||
_timer?.Dispose();
|
||||
|
||||
var text = Value is null ? null : GetItemString(Value);
|
||||
/*
|
||||
* Don't update the value to prevent the popover from opening again after coercion
|
||||
*/
|
||||
if (text != Text)
|
||||
return SetTextAsync(text, updateValue: false);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task CoerceValueToText()
|
||||
{
|
||||
if (!CoerceValue)
|
||||
return Task.CompletedTask;
|
||||
|
||||
_timer?.Dispose();
|
||||
|
||||
var value = Converter.Get(Text);
|
||||
|
||||
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>
|
||||
/// Focus the input in the Autocomplete component.
|
||||
/// </summary>
|
||||
public override ValueTask FocusAsync()
|
||||
{
|
||||
return _elementReference.FocusAsync();
|
||||
}
|
||||
/// <summary>
|
||||
/// Blur from the input in the Autocomplete component.
|
||||
/// </summary>
|
||||
public override ValueTask BlurAsync()
|
||||
{
|
||||
return _elementReference.BlurAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Select all text within the Autocomplete input.
|
||||
/// </summary>
|
||||
public override ValueTask SelectAsync()
|
||||
{
|
||||
return _elementReference.SelectAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Select all text within the Autocomplete input and aligns its start and end points to the text content of the current input.
|
||||
/// </summary>
|
||||
public override ValueTask SelectRangeAsync(int pos1, int pos2)
|
||||
{
|
||||
return _elementReference.SelectRangeAsync(pos1, pos2);
|
||||
}
|
||||
|
||||
private async Task OnTextChanged(string text)
|
||||
{
|
||||
await TextChanged.InvokeAsync();
|
||||
|
||||
if (text is null)
|
||||
return;
|
||||
|
||||
await SetTextAsync(text, true);
|
||||
}
|
||||
|
||||
private async Task ListItemOnClick(T item)
|
||||
{
|
||||
await SelectOption(item);
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
@namespace Connected.Components
|
||||
@inherits UIComponent
|
||||
|
||||
@if(AvatarGroup == null || AvatarGroup.MaxGroupReached(this))
|
||||
{
|
||||
<div @attributes="UserAttributes" class="@Classname" style="@Stylesname">
|
||||
@if (!String.IsNullOrEmpty(Image))
|
||||
{
|
||||
if (!String.IsNullOrEmpty(Alt))
|
||||
{
|
||||
<img src="@Image" alt="@Alt" class="mud-avatar-img" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<img src="@Image" class="mud-avatar-img" />
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ChildContent
|
||||
}
|
||||
</div>
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
@namespace Connected.Components
|
||||
@inherits UIComponent
|
||||
|
||||
<div @attributes="UserAttributes" class="@Classname" style="@Style">
|
||||
<CascadingValue Value="this" IsFixed="true">
|
||||
@ChildContent
|
||||
</CascadingValue>
|
||||
@if(_avatars.Count > Max)
|
||||
{
|
||||
<Avatar Class="@MaxAvatarClassname" Color="@MaxColor" Size="@MaxSize" Variant="@MaxVariant" Rounded="@MaxRounded" Square="@MaxSquare" Elevation="@MaxElevation">
|
||||
@($"+{_avatars.Count - Max}")
|
||||
</Avatar>
|
||||
}
|
||||
</div>
|
@ -0,0 +1,24 @@
|
||||
@namespace Connected.Components
|
||||
@inherits UIComponent
|
||||
|
||||
<span @attributes="UserAttributes" class="@Classname" style="@Style">
|
||||
@ChildContent
|
||||
@if (Visible)
|
||||
{
|
||||
<span class="@WrapperClass">
|
||||
<span class="@BadgeClassName" @onclick="HandleBadgeClick">
|
||||
@if (!Dot)
|
||||
{
|
||||
@if (!String.IsNullOrEmpty(Icon))
|
||||
{
|
||||
<Icon Icon="@Icon" Class="mud-icon-badge" />
|
||||
}
|
||||
else
|
||||
{
|
||||
@_content
|
||||
}
|
||||
}
|
||||
</span>
|
||||
</span>
|
||||
}
|
||||
</span>
|
@ -0,0 +1,19 @@
|
||||
@namespace Connected.Components
|
||||
@inherits UIComponent
|
||||
|
||||
<li class="@Classname">
|
||||
@if (Parent?.ItemTemplate is null)
|
||||
{
|
||||
<a href="@(Item?.Href ?? "#")">
|
||||
@if (Item?.Icon != null)
|
||||
{
|
||||
<Icon Icon="@Item?.Icon" Size="Size.Small" />
|
||||
}
|
||||
@Item?.Text
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
@Parent?.ItemTemplate(Item);
|
||||
}
|
||||
</li>
|
@ -0,0 +1,13 @@
|
||||
@namespace Connected.Components
|
||||
@inherits UIComponent
|
||||
|
||||
<li aria-hidden="true" class="mud-breadcrumb-separator mud-ltr mud-flip-x-rtl">
|
||||
@if (Parent?.SeparatorTemplate is null)
|
||||
{
|
||||
<span>@Parent?.Separator</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
@Parent?.SeparatorTemplate
|
||||
}
|
||||
</li>
|
@ -0,0 +1,35 @@
|
||||
@namespace Connected.Components
|
||||
@inherits UIComponent
|
||||
|
||||
@if (Items is null || !Items.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
<CascadingValue Value="this" IsFixed="true">
|
||||
<ul @attributes="UserAttributes" class=@Classname style="@Style">
|
||||
@if (MaxItems is not null && Collapsed && Items.Count > MaxItems)
|
||||
{
|
||||
<BreadcrumbLink Item="Items[0]"></BreadcrumbLink>
|
||||
<BreadcrumbSeparator></BreadcrumbSeparator>
|
||||
<li class="mud-breadcrumbs-expander" @onclick="Expand">
|
||||
<Icon Icon="@ExpanderIcon" Size="Size.Small"></Icon>
|
||||
</li>
|
||||
<BreadcrumbSeparator></BreadcrumbSeparator>
|
||||
<BreadcrumbLink Item="Items[Items.Count - 1]"></BreadcrumbLink>
|
||||
}
|
||||
else
|
||||
{
|
||||
@for (var i = 0; i < Items.Count; i++)
|
||||
{
|
||||
var item = Items[i];
|
||||
<BreadcrumbLink Item="item"></BreadcrumbLink>
|
||||
|
||||
if (i != Items.Count - 1)
|
||||
{
|
||||
<BreadcrumbSeparator></BreadcrumbSeparator>
|
||||
}
|
||||
}
|
||||
}
|
||||
</ul>
|
||||
</CascadingValue>
|
@ -0,0 +1,6 @@
|
||||
@namespace Connected.Components
|
||||
@inherits UIComponent
|
||||
|
||||
<CascadingValue Value="Breakpoint">
|
||||
@ChildContent
|
||||
</CascadingValue>
|
@ -0,0 +1,31 @@
|
||||
@namespace Connected.Components
|
||||
@using Connected.Extensions;
|
||||
@inherits ButtonBase
|
||||
|
||||
<Element @bind-Ref="@_elementReference"
|
||||
HtmlTag="@HtmlTag"
|
||||
Class="@Classname"
|
||||
Style="@Style"
|
||||
@attributes="UserAttributes"
|
||||
@onclick="OnClickHandler"
|
||||
type="@ButtonType.ToDescriptionString()"
|
||||
href="@Href"
|
||||
target="@Target"
|
||||
rel="@(Target=="_blank"?"noopener":null)"
|
||||
disabled="@Disabled">
|
||||
<span class="mud-button-label">
|
||||
@if (!string.IsNullOrWhiteSpace(StartIcon))
|
||||
{
|
||||
<span class="@StartIconClass">
|
||||
<Icon Icon="@StartIcon" Size="@Size" Color="@IconColor" />
|
||||
</span>
|
||||
}
|
||||
@ChildContent
|
||||
@if (!string.IsNullOrWhiteSpace(@EndIcon))
|
||||
{
|
||||
<span class="@EndIconClass">
|
||||
<Icon Glyph="@EndIcon" Size="@Size" Color="@IconColor" />
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
</Element>
|
@ -0,0 +1,30 @@
|
||||
@namespace Connected.Components
|
||||
|
||||
@inherits ButtonBase
|
||||
|
||||
@using Connected.Extensions;
|
||||
|
||||
<Element @bind-Ref="@_elementReference"
|
||||
HtmlTag="@HtmlTag"
|
||||
Class="@Classname"
|
||||
Style="@Style"
|
||||
@attributes="UserAttributes"
|
||||
@onclick="OnClickHandler"
|
||||
type="@ButtonType.ToDescriptionString()"
|
||||
href="@Href"
|
||||
target="@Target"
|
||||
rel="@(Target=="_blank"?"noopener":null)"
|
||||
disabled="@Disabled"
|
||||
title="@Title">
|
||||
<span class="mud-fab-label">
|
||||
@if (!string.IsNullOrWhiteSpace(StartIcon))
|
||||
{
|
||||
<Icon Icon="@StartIcon" Color="@IconColor" Size="@IconSize" />
|
||||
}
|
||||
@Label
|
||||
@if (!string.IsNullOrWhiteSpace(EndIcon))
|
||||
{
|
||||
<Icon Icon="@EndIcon" Color="@IconColor" Size="@IconSize" />
|
||||
}
|
||||
</span>
|
||||
</Element>
|
@ -0,0 +1,29 @@
|
||||
@namespace Connected.Components
|
||||
|
||||
@inherits ButtonBase
|
||||
|
||||
@using Connected.Extensions;
|
||||
|
||||
<Element @bind-Ref="@_elementReference"
|
||||
HtmlTag="@HtmlTag"
|
||||
Class="@Classname"
|
||||
Style="@Style"
|
||||
@attributes="UserAttributes"
|
||||
@onclick="OnClickHandler"
|
||||
type="@ButtonType.ToDescriptionString()"
|
||||
href="@Href"
|
||||
target="@Target"
|
||||
rel="@(Target=="_blank"?"noopener":null)"
|
||||
disabled="@Disabled"
|
||||
title="@Title">
|
||||
@if (!String.IsNullOrEmpty(Icon))
|
||||
{
|
||||
<span class="mud-icon-button-label">
|
||||
<Icon Glyph="@Icon" Size="@Size" />
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<TextContent Typo="Typo.body2" Color="ThemeColor.Inherit">@ChildContent</TextContent>
|
||||
}
|
||||
</Element>
|
@ -0,0 +1,21 @@
|
||||
@namespace Connected.Components
|
||||
|
||||
@inherits UIComponent
|
||||
|
||||
@{
|
||||
var ariaPressedValue = @Toggled ? "true" : "false";
|
||||
}
|
||||
|
||||
<IconButton Icon="@(Toggled ? ToggledIcon : Icon)"
|
||||
Size="(Toggled ? ToggledSize : Size)"
|
||||
Color="(Toggled ? ToggledColor : Color)"
|
||||
Title="@(Toggled && ToggledTitle != null ? ToggledTitle : Title)"
|
||||
Variant="Variant"
|
||||
Disabled="Disabled"
|
||||
Edge="Edge"
|
||||
DisableRipple="DisableRipple"
|
||||
OnClick="Toggle"
|
||||
Class="@Class"
|
||||
Style="@Style"
|
||||
aria-pressed="@ariaPressedValue"
|
||||
@attributes="UserAttributes" />
|
@ -0,0 +1,12 @@
|
||||
@namespace Connected.Components
|
||||
|
||||
@inherits UIComponent
|
||||
|
||||
<Element HtmlTag="div" Class="@Classname" Style="@Style" Tag="@Tag" UserAttributes="@UserAttributes">
|
||||
@ChildContent
|
||||
</Element>
|
||||
|
||||
@{
|
||||
if(!UserAttributes.ContainsKey("role"))
|
||||
UserAttributes.Add("role", "group");
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
@namespace Connected.Components
|
||||
|
||||
@inherits UIComponent
|
||||
|
||||
<Paper @attributes="UserAttributes" Class="@Classname" Style="@Style" Outlined="@Outlined" Square="@Square" Elevation="@Elevation">
|
||||
@ChildContent
|
||||
</Paper>
|
@ -0,0 +1,7 @@
|
||||
@namespace Connected.Components
|
||||
|
||||
@inherits UIComponent
|
||||
|
||||
<div @attributes="UserAttributes" class="@Classname" style="@Style">
|
||||
@ChildContent
|
||||
</div>
|
@ -0,0 +1,7 @@
|
||||
@namespace Connected.Components
|
||||
|
||||
@inherits UIComponent
|
||||
|
||||
<div @attributes="UserAttributes" class="@Classname" style="@Style">
|
||||
@ChildContent
|
||||
</div>
|
@ -0,0 +1,22 @@
|
||||
@namespace Connected.Components
|
||||
|
||||
@inherits UIComponent
|
||||
|
||||
<div @attributes="UserAttributes" class="@Classname" style="@Style">
|
||||
@if (CardHeaderAvatar is not null)
|
||||
{
|
||||
<div class="mud-card-header-avatar">@CardHeaderAvatar</div>
|
||||
}
|
||||
@if (CardHeaderContent is not null)
|
||||
{
|
||||
<div class="mud-card-header-content">@CardHeaderContent</div>
|
||||
}
|
||||
@if (CardHeaderActions is not null)
|
||||
{
|
||||
<div class="mud-card-header-actions">@CardHeaderActions</div>
|
||||
}
|
||||
@if (ChildContent is not null)
|
||||
{
|
||||
@ChildContent
|
||||
}
|
||||
</div>
|
@ -0,0 +1,5 @@
|
||||
@namespace Connected.Components
|
||||
|
||||
@inherits UIComponent
|
||||
|
||||
<div @attributes="UserAttributes" title="@Title" class="@Classname" style="@StyleString"></div>
|
@ -0,0 +1,84 @@
|
||||
@namespace Connected.Components
|
||||
@inherits BindableItemsControlBase<CarouselItem, TData>
|
||||
@using Connected.Extensions
|
||||
@implements IAsyncDisposable
|
||||
@typeparam TData
|
||||
|
||||
<section @attributes="UserAttributes" aria-roledescription="carousel" class="@Classname" style="@Style">
|
||||
<CascadingValue Value="this">
|
||||
|
||||
<SwipeArea OnSwipe="OnSwipe" Class="mud-carousel-swipe">
|
||||
@*Selected Content*@
|
||||
@if (ItemsSource == null)
|
||||
{
|
||||
@ChildContent
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (TData item in ItemsSource)
|
||||
{
|
||||
<CarouselItem>
|
||||
@ItemTemplate(item)
|
||||
</CarouselItem>
|
||||
}
|
||||
}
|
||||
</SwipeArea>
|
||||
|
||||
@*Controls*@
|
||||
<div class="d-flex flex-grow-1 align-self-auto">
|
||||
@*Left Arrow*@
|
||||
@if (ShowArrows)
|
||||
{
|
||||
@if (PreviousButtonTemplate == null)
|
||||
{
|
||||
<IconButton tabindex="1" aria-label="Go to previous" Class="@NavigationButtonsClassName" Style="z-index:3;opacity:0.75" Icon="@PreviousIcon" OnClick="Previous" Color="ThemeColor.Inherit" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<div @onclick="Previous" tabindex="1" aria-label="Go to previous" class="@NavigationButtonsClassName" style="z-index:3">
|
||||
@PreviousButtonTemplate
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@*Bullets*@
|
||||
<div class="@($"flex-grow-1 align-self-{ConvertPosition(BulletsPosition).ToDescriptionString()}")" style="z-index:3">
|
||||
@if (ShowBullets)
|
||||
{
|
||||
<div class="d-flex justify-center">
|
||||
@for (int i = 0; i < Items.Count; i++)
|
||||
{
|
||||
int current = i;
|
||||
if (BulletTemplate == null)
|
||||
{
|
||||
<IconButton tabindex="@(i+3)" aria-label="@(i+1)" Class="@BulletsButtonsClassName" Style="z-index:3;opacity:0.75" Icon="@(current == SelectedIndex ? CheckedIcon : UncheckedIcon)" OnClick="(() => MoveTo(current))" Color="ThemeColor.Inherit" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<div @onclick="() => MoveTo(current)" class="@BulletsButtonsClassName" style="z-index:3">
|
||||
@BulletTemplate(current == SelectedIndex)
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@*Right Arrow*@
|
||||
@if (ShowArrows)
|
||||
{
|
||||
@if (NextButtonTemplate == null)
|
||||
{
|
||||
<IconButton tabindex="2" aria-label="Go to next" Class="@NavigationButtonsClassName" Style="z-index:3;opacity:0.75" Icon="@NextIcon" OnClick="Next" Color="ThemeColor.Inherit" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<div @onclick="Next" tabindex="2" aria-label="Go to next" class="@NavigationButtonsClassName" style="z-index:3">
|
||||
@NextButtonTemplate
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
</CascadingValue>
|
||||
</section>
|
@ -0,0 +1,10 @@
|
||||
@namespace Connected.Components
|
||||
|
||||
@inherits UIComponent
|
||||
|
||||
@if (IsVisible)
|
||||
{
|
||||
<div @attributes="UserAttributes" class="@Classname" style="@Style">
|
||||
@ChildContent
|
||||
</div>
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
@namespace Connected.Components
|
||||
|
||||
@inherits UIComponent
|
||||
|
||||
<CascadingValue Value="@this" IsFixed="true">
|
||||
<div @attributes="UserAttributes" class="@Classname" style="@Style" dir="ltr">
|
||||
@if (ChartType == ChartType.Donut)
|
||||
{
|
||||
<Donut InputData="@InputData" @bind-SelectedIndex="@SelectedIndex" InputLabels="@InputLabels"></Donut>
|
||||
}
|
||||
@if (ChartType == ChartType.Pie)
|
||||
{
|
||||
<Pie InputData="@InputData" @bind-SelectedIndex="@SelectedIndex" InputLabels="@InputLabels"></Pie>
|
||||
}
|
||||
@if (ChartType == ChartType.Line)
|
||||
{
|
||||
<Line ChartSeries="@ChartSeries" @bind-SelectedIndex="@SelectedIndex" XAxisLabels="@XAxisLabels"></Line>
|
||||
}
|
||||
@if (ChartType == ChartType.Bar)
|
||||
{
|
||||
<Bar ChartSeries="@ChartSeries" @bind-SelectedIndex="@SelectedIndex" XAxisLabels="@XAxisLabels"></Bar>
|
||||
}
|
||||
</div>
|
||||
</CascadingValue>
|
||||
|
@ -0,0 +1,45 @@
|
||||
@namespace Connected.Components
|
||||
@using System.Globalization;
|
||||
@inherits Chart
|
||||
|
||||
<svg @attributes="UserAttributes" class="mud-chart-line mud-ltr" width="@ChartParent?.Width" height="@ChartParent?.Height" viewBox="0 0 650 350">
|
||||
<g class="mud-charts-grid">
|
||||
<g class="mud-charts-gridlines-yaxis">
|
||||
@foreach (var horizontalLine in _horizontalLines)
|
||||
{
|
||||
<path stroke="#e0e0e0" stroke-width="0.3" d="@horizontalLine.Data"></path>
|
||||
}
|
||||
</g>
|
||||
@if (ChartParent?.ChartOptions.XAxisLines==true)
|
||||
{
|
||||
<g class="mud-charts-gridlines-xaxis-lines">
|
||||
@foreach (var verticalLine in _verticalLines)
|
||||
{
|
||||
<path stroke="gray" stroke-width="0.3" d="@verticalLine.Data"></path>
|
||||
}
|
||||
</g>
|
||||
}
|
||||
</g>
|
||||
<g class="mud-charts-yaxis">
|
||||
@foreach (var horizontalLineValue in _horizontalValues)
|
||||
{
|
||||
@((MarkupString)$"<text x='{horizontalLineValue.X.ToString(CultureInfo.InvariantCulture)}' y='{horizontalLineValue.Y.ToString(CultureInfo.InvariantCulture)}' font-size='12px' text-anchor='end' dominant-baseline='auto'>{horizontalLineValue.Value.ToString(CultureInfo.InvariantCulture)}</text>")
|
||||
}
|
||||
</g>
|
||||
<g class="mud-charts-xaxis">
|
||||
@foreach (var verticalLineValue in _verticalValues)
|
||||
{
|
||||
@((MarkupString)$"<text x='{verticalLineValue.X.ToString(CultureInfo.InvariantCulture)}' y='{verticalLineValue.Y.ToString(CultureInfo.InvariantCulture)}' font-size='12px' text-anchor='middle'>{verticalLineValue.Value.ToString(CultureInfo.InvariantCulture)}</text>")
|
||||
}
|
||||
</g>
|
||||
<g class="mud-charts-bar-series">
|
||||
@foreach (var bar in _bars)
|
||||
{
|
||||
<path class="mud-chart-bar" @onclick="() => SelectedIndex = bar.Index" fill="@(ChartParent.ChartOptions.ChartPalette.GetValue(bar.Index % ChartOptions.ChartPalette.Count()))" stroke="@(ChartParent.ChartOptions.ChartPalette.GetValue(bar.Index % ChartOptions.ChartPalette.Count()))" stroke-width="8" d="@bar.Data"></path>
|
||||
}
|
||||
</g>
|
||||
|
||||
|
||||
@ChartParent?.CustomGraphics
|
||||
</svg>
|
||||
<Legend Data="@_legends" />
|
@ -0,0 +1,22 @@
|
||||
@namespace Connected.Components
|
||||
@using System.Globalization
|
||||
@inherits Chart
|
||||
|
||||
<svg @attributes="UserAttributes" class="mud-chart-donut" width="@ParentWidth" height="@ParentHeight" viewBox="0 0 42 42">
|
||||
<Filters />
|
||||
<circle class="mud-donut-ring" cx="21" cy="21" r="15.91549430918954"></circle>
|
||||
@foreach (var item in _circles.ToList())
|
||||
{
|
||||
<circle class="mud-chart-serie mud-donut-segment" @onclick="() => SelectedIndex = item.Index" stroke="@(ChartParent.ChartOptions.ChartPalette.GetValue(item.Index % ChartOptions.ChartPalette.Count()))"
|
||||
cx="@ToS(item.CX)"
|
||||
cy="@ToS(item.CY)"
|
||||
r="@ToS(item.Radius)"
|
||||
stroke-dasharray="@item.StrokeDashArray"
|
||||
stroke-dashoffset="@ToS(item.StrokeDashOffset)">
|
||||
</circle>
|
||||
}
|
||||
<circle class="mud-donut-hole" cx="21" cy="21" r="13.4"></circle>
|
||||
|
||||
@ChartParent?.CustomGraphics
|
||||
</svg>
|
||||
<Legend Data="@_legends" />
|
@ -0,0 +1,44 @@
|
||||
@namespace Connected.Components
|
||||
@using System.Globalization;
|
||||
@inherits Chart
|
||||
|
||||
<svg @attributes="UserAttributes" class="mud-chart-line mud-ltr" width="@ChartParent?.Width" height="@ChartParent?.Height" viewBox="0 0 650 350">
|
||||
<g class="mud-charts-grid">
|
||||
<g class="mud-charts-gridlines-yaxis">
|
||||
@foreach (var horizontalLine in _horizontalLines)
|
||||
{
|
||||
<path stroke="#e0e0e0" stroke-width="0.3" d="@horizontalLine.Data"></path>
|
||||
}
|
||||
</g>
|
||||
@if (ChartParent?.ChartOptions.XAxisLines==true)
|
||||
{
|
||||
<g class="mud-charts-gridlines-xaxis-lines">
|
||||
@foreach (var verticalLine in _verticalLines)
|
||||
{
|
||||
<path stroke="gray" stroke-width="0.3" d="@verticalLine.Data"></path>
|
||||
}
|
||||
</g>
|
||||
}
|
||||
</g>
|
||||
<g class="mud-charts-yaxis">
|
||||
@foreach (var horizontalLineValue in _horizontalValues)
|
||||
{
|
||||
@((MarkupString)$"<text x='{horizontalLineValue.X.ToString(CultureInfo.InvariantCulture)}' y='{horizontalLineValue.Y.ToString(CultureInfo.InvariantCulture)}' font-size='12px' text-anchor='end' dominant-baseline='auto'>{horizontalLineValue.Value.ToString(CultureInfo.InvariantCulture)}</text>")
|
||||
}
|
||||
</g>
|
||||
<g class="mud-charts-xaxis">
|
||||
@foreach (var verticalLineValue in _verticalValues)
|
||||
{
|
||||
@((MarkupString)$"<text x='{verticalLineValue.X.ToString(CultureInfo.InvariantCulture)}' y='{verticalLineValue.Y.ToString(CultureInfo.InvariantCulture)}' font-size='12px' text-anchor='middle'>{verticalLineValue.Value.ToString(CultureInfo.InvariantCulture)}</text>")
|
||||
}
|
||||
</g>
|
||||
<g class="mud-charts-line-series">
|
||||
@foreach (var chartLine in _chartLines)
|
||||
{
|
||||
<path class="mud-chart-line" @onclick="() => SelectedIndex = chartLine.Index" fill="none" stroke="@(ChartParent.ChartOptions.ChartPalette.GetValue(chartLine.Index % ChartOptions.ChartPalette.Count()))" stroke-width="@(ChartParent.ChartOptions.LineStrokeWidth)" d="@chartLine.Data"></path>
|
||||
}
|
||||
</g>
|
||||
|
||||
@ChartParent?.CustomGraphics
|
||||
</svg>
|
||||
<Legend Data="@_legends" />
|
@ -0,0 +1,14 @@
|
||||
@namespace Connected.Components
|
||||
@using System.Globalization
|
||||
@inherits Chart
|
||||
|
||||
<svg @attributes="UserAttributes" class="mud-chart-pie" width="@ChartParent?.Width" height="@ChartParent?.Height" viewBox="-1 -1 2 2" style="transform: rotate(-90deg);">
|
||||
<Filters />
|
||||
@foreach (var item in _paths.ToList())
|
||||
{
|
||||
<path @onclick="() => SelectedIndex = item.Index" class="mud-chart-serie" fill="@(ChartParent.ChartOptions.ChartPalette.GetValue(item.Index % ChartOptions.ChartPalette.Count()))" d="@item.Data"></path>
|
||||
}
|
||||
|
||||
@ChartParent?.CustomGraphics
|
||||
</svg>
|
||||
<Legend Data="@_legends" />
|
@ -0,0 +1,5 @@
|
||||
@namespace Connected.Components
|
||||
|
||||
<filter id="lighten">
|
||||
<feColorMatrix type="matrix" values="1.5 0 0 0 0 0 1.5 0 0 0 0 0 1.5 0 0 0 0 0 1 0" />
|
||||
</filter>
|
@ -0,0 +1,15 @@
|
||||
@namespace Connected.Components
|
||||
@inherits Chart
|
||||
|
||||
@if (ChartParent?.ChartOptions.DisableLegend != true)
|
||||
{
|
||||
<div @attributes="UserAttributes" class="mud-chart-legend">
|
||||
@foreach (var item in Data)
|
||||
{
|
||||
<div class="mud-chart-legend-item" @onclick=@(()=>{ if (ChartParent!=null) { ChartParent.SelectedIndex=item.Index; }}) @onclick:stopPropagation=@(ChartParent!=null)>
|
||||
<span class="mud-chart-legend-marker" style="@($"background-color:{ChartParent.ChartOptions.ChartPalette.GetValue(item.Index % ChartOptions.ChartPalette.Count())}")"></span>
|
||||
<TextContent Typo="Typo.body2" Inline="true">@item.Labels</TextContent>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
@namespace Connected.Components
|
||||
@inherits BooleanInput<T>
|
||||
@typeparam T
|
||||
|
||||
<InputControl Class="@Classname" Style="@Style" Error="@HasErrors" ErrorText="@GetErrorText()" Required="@Required">
|
||||
<InputContent>
|
||||
<label class="@LabelClassname" id="@_elementId" @onkeydown="HandleKeyDown" @onclick:stopPropagation="@StopClickPropagation">
|
||||
<span tabindex="0" class="@CheckBoxClassname">
|
||||
@*note: stopping the click propagation is important here. otherwise checking the checkbox results in click events on its parent (i.e. table row), which is generally not what you would want*@
|
||||
<input tabindex="-1" @attributes="UserAttributes" type="checkbox" class="mud-checkbox-input" checked="@BoolValue" @onchange="@OnChange" disabled="@Disabled" @onclick:preventDefault="@ReadOnly" />
|
||||
<Icon Icon="@GetIcon()" Color="HasErrors ? ThemeColor.Error : ThemeColor.Inherit" Size="@Size" />
|
||||
</span>
|
||||
@if (!String.IsNullOrEmpty(Label))
|
||||
{
|
||||
<TextContent Color="HasErrors ? ThemeColor.Error : ThemeColor.Inherit">@Label</TextContent>
|
||||
}
|
||||
@if (ChildContent != null)
|
||||
{
|
||||
<TextContent Color="HasErrors ? ThemeColor.Error : ThemeColor.Inherit">
|
||||
@ChildContent
|
||||
</TextContent>
|
||||
}
|
||||
</label>
|
||||
</InputContent>
|
||||
</InputControl>
|
@ -0,0 +1,33 @@
|
||||
@namespace Connected.Components
|
||||
@inherits UIComponent
|
||||
|
||||
<div tabindex="0" @attributes="UserAttributes" class="@Classname" style="@Style" @onclick="@OnClickHandler">
|
||||
@if (!String.IsNullOrEmpty(Avatar))
|
||||
{
|
||||
<Avatar Class="@AvatarClass" Color="@Color">
|
||||
<MudIcon Icon="@Avatar" />
|
||||
</Avatar>
|
||||
}
|
||||
else if (!String.IsNullOrEmpty(Icon) && !IsChecked)
|
||||
{
|
||||
<Icon Icon="@Icon" Class="mud-chip-icon" Size="Size.Small" Color="@IconColor" />
|
||||
}
|
||||
else if (IsChecked)
|
||||
{
|
||||
<Icon Icon="@CheckedIcon" Class="mud-chip-icon" Size="Size.Small" />
|
||||
}
|
||||
<span class="mud-chip-content">
|
||||
@if (ChildContent == null)
|
||||
{
|
||||
@Text
|
||||
}
|
||||
else
|
||||
{
|
||||
@ChildContent
|
||||
}
|
||||
@if (OnClose.HasDelegate || ChipSet?.AllClosable==true)
|
||||
{
|
||||
<IconButton Class="mud-chip-close-button" Icon="@(String.IsNullOrEmpty(CloseIcon) ? $"{Icons.Material.Filled.Cancel}" : $"{CloseIcon}")" OnClick="OnCloseHandler" Size="Size.Small"/>
|
||||
}
|
||||
</span>
|
||||
</div>
|
@ -0,0 +1,8 @@
|
||||
@namespace Connected.Components
|
||||
@inherits UIComponent
|
||||
|
||||
<div @attributes="UserAttributes" class="@Classname" style="@Style" >
|
||||
<CascadingValue IsFixed="true" Value="this">
|
||||
@ChildContent
|
||||
</CascadingValue>
|
||||
</div>
|
@ -0,0 +1,11 @@
|
||||
@namespace Connected.Components
|
||||
@inherits UIComponent
|
||||
@using System.Globalization;
|
||||
|
||||
<div @onanimationend="EventUtil.AsNonRenderingEventHandler(AnimationEnd)" @attributes="UserAttributes" class="@Classname" style="@Stylename">
|
||||
<div @ref="_wrapper" class="mud-collapse-wrapper">
|
||||
<div class="mud-collapse-wrapper-inner">
|
||||
@ChildContent
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,151 @@
|
||||
@namespace Connected.Components
|
||||
@using System.Globalization;
|
||||
@using Connected.Utilities;
|
||||
@inherits Picker<Color>
|
||||
|
||||
@Render
|
||||
|
||||
@code {
|
||||
|
||||
protected override RenderFragment PickerContent =>
|
||||
@<CascadingValue Value="@this" IsFixed="true">
|
||||
<PickerToolbar DisableToolbar="@DisableToolbar" Class="mud-picker-color-toolbar">
|
||||
@if (PickerVariant != PickerVariant.Static)
|
||||
{
|
||||
<IconButton Class="pa-1 mud-close-picker-button" Size="Size.Small" Color="ThemeColor.Inherit" Icon="@CloseIcon" OnClick="@GetEventCallback()" />
|
||||
}
|
||||
<Spacer />
|
||||
<IconButton Class="pa-1" Size="Size.Small" Color="GetButtonColor(ColorPickerView.Spectrum)" Icon="@SpectrumIcon" OnClick="(() => ChangeView(ColorPickerView.Spectrum))" />
|
||||
<IconButton Class="pa-1 mx-1" Size="Size.Small" Color="GetButtonColor(ColorPickerView.Grid)" Icon="@GridIcon" OnClick="(() => ChangeView(ColorPickerView.Grid))" />
|
||||
<IconButton Class="pa-1" Size="Size.Small" Color="GetButtonColor(ColorPickerView.Palette)" Icon="@PaletteIcon" OnClick="(() => ChangeView(ColorPickerView.Palette))" />
|
||||
</PickerToolbar>
|
||||
<PickerContent Class="mud-picker-color-content">
|
||||
@if (!DisableColorField)
|
||||
{
|
||||
<div class="mud-picker-color-picker">
|
||||
@if (_activeColorPickerView == ColorPickerView.Spectrum)
|
||||
{
|
||||
<div class="mud-picker-color-overlay" style="@($"background-color: {_baseColor.ToString(ColorOutputFormats.RGB)}")">
|
||||
<div class="mud-picker-color-overlay mud-picker-color-overlay-white">
|
||||
<div class="mud-picker-color-overlay mud-picker-color-overlay-black">
|
||||
<div class="mud-picker-color-overlay" id="@_id" @onclick="OnColorOverlayClick">
|
||||
<svg class="mud-picker-color-selector" height="26" width="26" style="transform: @GetSelectorLocation()" @onclick="OnSelectorClicked" @onclick:stopPropagation="true">
|
||||
<defs>
|
||||
<filter id="mud-picker-color-selector-shadow" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur in="SourceAlpha" stdDeviation="1" />
|
||||
<feOffset dx="0" dy="5" result="offsetblur" />
|
||||
<feOffset dx="0" dy="-5" result="offsetblur" />
|
||||
<feMerge><feMergeNode /><feMergeNode in="SourceGraphic" /></feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<circle r="10" cx="13" cy="13" stroke="white" stroke-width="1" fill="transparent" style="filter: url(#mud-picker-color-selector-shadow)" />
|
||||
<circle r="11" cx="13" cy="13" stroke="white" stroke-width="1.5" fill="transparent" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_activeColorPickerView is ColorPickerView.Grid or ColorPickerView.GridCompact)
|
||||
{
|
||||
<div class="mud-picker-color-grid">
|
||||
@foreach (var item in _gridList)
|
||||
{
|
||||
<div class="@GetColorDotClass(item)" style="@($"background: {item.ToString(ColorOutputFormats.RGBA)};")" @onclick="@GetSelectPaletteColorCallback(item)"></div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (_activeColorPickerView != ColorPickerView.GridCompact)
|
||||
{
|
||||
@if (!DisableSliders || !DisableInputs || !DisablePreview)
|
||||
{
|
||||
<div class="mud-picker-color-controls">
|
||||
@if (!DisableSliders || !DisablePreview)
|
||||
{
|
||||
<div class="mud-picker-color-controls-row">
|
||||
@if (!DisablePreview)
|
||||
{
|
||||
<div class="mud-picker-color-dot mud-picker-color-dot-current mud-ripple" @onclick="ToggleCollection">
|
||||
<div class="mud-picker-color-fill" style="@($"background: {_color.ToString(ColorOutputFormats.RGBA)};")"></div>
|
||||
</div>
|
||||
}
|
||||
@if (!DisableSliders && !_collectionOpen)
|
||||
{
|
||||
<div class="mud-picker-color-sliders">
|
||||
@if (_activeColorPickerView != ColorPickerView.Grid)
|
||||
{
|
||||
<Slider Class="mud-picker-color-slider hue" dir="ltr" T="int" Value="(int)_color.H" ValueChanged="UpdateBaseColorSlider" Step="1" Min="0" Max="360" />
|
||||
}
|
||||
@if (!DisableAlpha)
|
||||
{
|
||||
<Slider Class="mud-picker-color-slider alpha" Value="_color.A" ValueChanged="SetAlpha" T="int" Min="0" Max="255" Step="1" Style="@AlphaSliderStyle" />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (_collectionOpen)
|
||||
{
|
||||
<div class="mud-picker-color-collection">
|
||||
@foreach (var item in Palette.Take(5))
|
||||
{
|
||||
<div class="@GetColorDotClass(item)" style="@($"background: {item.ToString(ColorOutputFormats.RGBA)};")" @onclick="@GetSelectPaletteColorCallback(item)"></div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (!DisableInputs)
|
||||
{
|
||||
<div class="mud-picker-color-controls-row">
|
||||
<div class="mud-picker-color-inputs">
|
||||
@switch (ColorPickerMode)
|
||||
{
|
||||
case ColorPickerMode.RGB:
|
||||
<NumericField Value="_color.R" T="int" ValueChanged="SetR" Class="mud-picker-color-inputfield" HelperText="R" Min="0" Max="255" Variant="Variant.Outlined" HideSpinButtons="true" />
|
||||
<NumericField Value="_color.G" T="int" ValueChanged="SetG" Class="mud-picker-color-inputfield" HelperText="G" Min="0" Max="255" Variant="Variant.Outlined" HideSpinButtons="true" />
|
||||
<NumericField Value="_color.B" T="int" ValueChanged="SetB" Class="mud-picker-color-inputfield" HelperText="B" Min="0" Max="255" Variant="Variant.Outlined" HideSpinButtons="true" />
|
||||
break;
|
||||
case ColorPickerMode.HSL:
|
||||
<NumericField Value="@_color.H" T="double" ValueChanged="SetH" Class="mud-picker-color-inputfield" HelperText="H" Step="1" Min="0" Max="360" Variant="Variant.Outlined" HideSpinButtons="true" />
|
||||
<NumericField Value="@_color.S" T="double" ValueChanged="SetS" Class="mud-picker-color-inputfield" HelperText="S" Step="0.01" Min="0" Max="100" Variant="Variant.Outlined" HideSpinButtons="true" />
|
||||
<NumericField Value="@_color.L" T="double" ValueChanged="SetL" Class="mud-picker-color-inputfield" HelperText="L" Step="0.01" Min="0" Max="100" Variant="Variant.Outlined" HideSpinButtons="true" />
|
||||
break;
|
||||
case ColorPickerMode.HEX:
|
||||
<TextField Value="@GetColorTextValue()" ValueChanged="SetInputString" T="string" Class="mud-picker-color-inputfield" Variant="Variant.Outlined" MaxLength="@GetHexColorInputMaxLength()" HelperText="HEX" />
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@if (!DisableAlpha && ColorPickerMode != ColorPickerMode.HEX)
|
||||
{
|
||||
<NumericField Value="@( Math.Round(_color.A / 255.0, 2))" T="double" ValueChanged="SetAlpha" Class="mud-picker-color-inputfield input-field-alpha" HelperText="A" Min="0" Max="1" Step="0.01" Variant="Variant.Outlined" HideSpinButtons="true" />
|
||||
}
|
||||
</div>
|
||||
@if (!DisableModeSwitch)
|
||||
{
|
||||
<div class="mud-picker-control-switch">
|
||||
<IconButton OnClick="ChangeMode" Icon="@ImportExportIcon" Class="pa-1 me-n1"></IconButton>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (_activeColorPickerView == ColorPickerView.Palette)
|
||||
{
|
||||
<div class="mud-picker-color-view">
|
||||
<div class="mud-picker-color-view-collection">
|
||||
@foreach (var item in Palette)
|
||||
{
|
||||
<div class="@GetColorDotClass(item)" style="@($"background: {item.ToString(ColorOutputFormats.RGBA)};")" @onclick="@GetSelectPaletteColorCallback(item)"></div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</PickerContent>
|
||||
</CascadingValue>;
|
||||
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
@namespace Connected.Components
|
||||
|
||||
@inherits UIComponent
|
||||
|
||||
<div @attributes="UserAttributes" class="@Classname" style="@Style">
|
||||
@ChildContent
|
||||
</div>
|
@ -0,0 +1,512 @@
|
||||
@namespace Connected.Components
|
||||
@inherits UIComponent
|
||||
@typeparam T
|
||||
@using Connected.Utilities
|
||||
|
||||
<CascadingValue IsFixed="true" Value="this">@Columns</CascadingValue>
|
||||
|
||||
<CascadingValue IsFixed="true" Value="this">
|
||||
<div @attributes="UserAttributes" class="@_classname" style="@_style">
|
||||
@if (Items != null || ServerData != null)
|
||||
{
|
||||
@if (ToolBarContent != null)
|
||||
{
|
||||
<ToolBar Class="mud-table-toolbar">
|
||||
@ToolBarContent
|
||||
@if (ShowMenuIcon)
|
||||
{
|
||||
@ToolbarMenu(this)
|
||||
}
|
||||
</ToolBar>
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (ShowMenuIcon)
|
||||
{
|
||||
@*Add the default toolbar.*@
|
||||
<ToolBar Class="mud-table-toolbar">
|
||||
<Spacer />
|
||||
@ToolbarMenu(this)
|
||||
</ToolBar>
|
||||
}
|
||||
}
|
||||
<div @ref=_gridElement class="@_tableClass" style="@_tableStyle">
|
||||
<table class="mud-table-root">
|
||||
@if (ColGroup != null)
|
||||
{
|
||||
<colgroup>
|
||||
@ColGroup
|
||||
</colgroup>
|
||||
}
|
||||
<thead class="@_headClassname">
|
||||
<CascadingValue Name="IsOnlyHeader" IsFixed="true" Value="true">
|
||||
@Header
|
||||
</CascadingValue>
|
||||
<tr class="mud-table-row">
|
||||
@foreach (var column in RenderedColumns)
|
||||
{
|
||||
<HeaderCell SortDirection="@(GetColumnSortDirection(column.Field))" T="T" Column="@column"></HeaderCell>
|
||||
}
|
||||
</tr>
|
||||
@if (_filtersMenuVisible && FilterMode == DataGridFilterMode.Simple)
|
||||
{
|
||||
<Overlay Visible="true" OnClick="@(() => _filtersMenuVisible = false)"></Overlay>
|
||||
<Paper Class="pa-2 filters-panel" Style="position:absolute;width:auto;min-width:700px;z-index:var(--mud-zindex-drawer)" Elevation="4">
|
||||
@if (FilterTemplate == null)
|
||||
{
|
||||
<Grid Spacing="0">
|
||||
@foreach (var f in FilterDefinitions)
|
||||
{
|
||||
@Filter(f, null)
|
||||
}
|
||||
</Grid>
|
||||
<Button Class="mt-2" StartIcon="@Icons.Material.Filled.Add" OnClick="@AddFilter" Color="@ThemeColor.Primary">Add Filter</Button>
|
||||
@if (ServerData != null)
|
||||
{
|
||||
<Button Class="mt-2" StartIcon="@Icons.Material.Filled.Add" OnClick="@ApplyFilters" Color="@ThemeColor.Primary">Apply</Button>
|
||||
}
|
||||
<Button Class="mt-2" StartIcon="@Icons.Material.Filled.Remove" OnClick="@ClearFilters" Color="@ThemeColor.Primary">Clear</Button>
|
||||
}
|
||||
else
|
||||
{
|
||||
@FilterTemplate(FilterDefinitions)
|
||||
}
|
||||
</Paper>
|
||||
}
|
||||
@if (_columnsPanelVisible)
|
||||
{
|
||||
<Overlay Visible="true" OnClick="@(() => _columnsPanelVisible = false)"></Overlay>
|
||||
<Paper Class="pa-2 columns-panel" Style="position:absolute;max-width:450px;z-index:var(--mud-zindex-drawer)" Elevation="4">
|
||||
<Grid Spacing="0">
|
||||
@foreach (var column in RenderedColumns)
|
||||
{
|
||||
<Item xs="12">
|
||||
<Switch T="bool" Disabled="@(!(column.Hideable ?? false))" Checked="@column.Hidden" CheckedChanged="@column.ToggleAsync" Converter="_oppositeBoolConverter" Color="ThemeColor.Primary" Label="@(column.Title ?? @column.Field)" />
|
||||
</Item>
|
||||
}
|
||||
<Item xs="6">
|
||||
<Button OnClick="@HideAllColumnsAsync" Color="@ThemeColor.Primary">Hide All</Button>
|
||||
</Item>
|
||||
<Item xs="6" Class="d-flex justify-end">
|
||||
<Button OnClick="@ShowAllColumnsAsync" Color="@ThemeColor.Primary">Show All</Button>
|
||||
</Item>
|
||||
</Grid>
|
||||
|
||||
</Paper>
|
||||
}
|
||||
@if (Filterable && FilterMode == DataGridFilterMode.ColumnFilterRow)
|
||||
{
|
||||
<tr class="mud-table-row">
|
||||
@foreach (var column in RenderedColumns)
|
||||
{
|
||||
<FilterHeaderCell T="T" Column="@column" />
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</thead>
|
||||
<tbody class="mud-table-body">
|
||||
@if (Loading)
|
||||
{
|
||||
<tr>
|
||||
<td colspan="1000" class="mud-table-loading">
|
||||
<ProgressLinear Color="@LoadingProgressColor" Class="mud-table-loading-progress" Indeterminate="true" />
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
|
||||
@{var resolvedPageItems = CurrentPageItems.ToList();}
|
||||
@if (resolvedPageItems != null && resolvedPageItems.Count > 0)
|
||||
{
|
||||
if (GroupedColumn != null)
|
||||
{
|
||||
foreach (var g in _groups)
|
||||
{
|
||||
<tr class="mud-table-row">
|
||||
@{ var groupClass = new CssBuilder(GroupClass).AddClass(GroupClassFunc?.Invoke(g)).Build(); }
|
||||
@{ var groupStyle = new StyleBuilder().AddStyle(GroupStyle).AddStyle(GroupStyleFunc?.Invoke(g)).Build(); }
|
||||
|
||||
<td class="mud-table-cell @groupClass" colspan="1000" style="background-color:var(--mud-palette-background-grey);@groupStyle">
|
||||
<IconButton
|
||||
Class="mud-table-row-expander"
|
||||
Icon="@(g.IsExpanded ? Icons.Material.Filled.ExpandMore : Icons.Material.Filled.ChevronRight)"
|
||||
OnClick="@(() => ToggleGroupExpansion(g))" />
|
||||
|
||||
@if (GroupedColumn.GroupTemplate == null)
|
||||
{
|
||||
<span style="font-weight:bold">@GroupedColumn.computedTitle: @g.Grouping.Key</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
@GroupedColumn.GroupTemplate(@g)
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
var rowIndex = 0;
|
||||
|
||||
@if (g.IsExpanded)
|
||||
{
|
||||
<Virtualize IsEnabled="@Virtualize" Items="@g.Grouping.ToList()" OverscanCount=@OverscanCount Context="item">
|
||||
@{ var rowClass = new CssBuilder(RowClass).AddClass(RowClassFunc?.Invoke(item, rowIndex)).Build(); }
|
||||
@{ var rowStyle = new StyleBuilder().AddStyle(RowStyle).AddStyle(RowStyleFunc?.Invoke(item, rowIndex)).Build(); }
|
||||
@{ var tmpRowIndex = rowIndex; }
|
||||
<tr class="mud-table-row @rowClass" Style="@rowStyle" @key="item" @onclick="@((args) => OnRowClickedAsync(args, item, tmpRowIndex))">
|
||||
<CascadingValue Value="@Validator" IsFixed="true">
|
||||
@foreach (var column in RenderedColumns)
|
||||
{
|
||||
if (!column.Hidden)
|
||||
{
|
||||
@Cell(column, item)
|
||||
}
|
||||
}
|
||||
</CascadingValue>
|
||||
</tr>
|
||||
@if (ChildRowContent != null && (_openHierarchies.Contains(item) || !hasHierarchyColumn))
|
||||
{
|
||||
<tr class="mud-table-row">
|
||||
<td class="mud-table-cell" colspan="1000">
|
||||
@ChildRowContent(new CellContext<T>(this, item))
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@{ rowIndex++; }
|
||||
</Virtualize>
|
||||
@*Group Footer*@
|
||||
<tr class="mud-table-row">
|
||||
@FooterCells(g.Grouping.ToList())
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var rowIndex = 0;
|
||||
<Virtualize IsEnabled="@Virtualize" Items="@resolvedPageItems" OverscanCount=@OverscanCount Context="item">
|
||||
@{ var rowClass = new CssBuilder(RowClass).AddClass(RowClassFunc?.Invoke(item, rowIndex)).Build(); }
|
||||
@{ var rowStyle = new StyleBuilder().AddStyle(RowStyle).AddStyle(RowStyleFunc?.Invoke(item, rowIndex)).Build(); }
|
||||
@{ var tmpRowIndex = rowIndex; }
|
||||
<tr class="mud-table-row @rowClass" Style="@rowStyle" @key="item" @onclick="@((args) => OnRowClickedAsync(args, item, tmpRowIndex))">
|
||||
<CascadingValue Value="@Validator" IsFixed="true">
|
||||
@foreach (var column in RenderedColumns)
|
||||
{
|
||||
if (!column.Hidden)
|
||||
{
|
||||
@Cell(column, item)
|
||||
}
|
||||
}
|
||||
</CascadingValue>
|
||||
</tr>
|
||||
@if (ChildRowContent != null && (_openHierarchies.Contains(item) || !hasHierarchyColumn))
|
||||
{
|
||||
<tr class="mud-table-row">
|
||||
<td class="mud-table-cell" colspan="1000">
|
||||
@ChildRowContent(new CellContext<T>(this, item))
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@{ rowIndex++; }
|
||||
</Virtualize>
|
||||
}
|
||||
}
|
||||
else if(Loading ? LoadingContent != null : NoRecordsContent != null)
|
||||
{
|
||||
<tr>
|
||||
<th colspan="1000" class="mud-table-empty-row">
|
||||
<div Class="my-3">
|
||||
@if(Loading)
|
||||
{
|
||||
@LoadingContent
|
||||
}
|
||||
else
|
||||
{
|
||||
@NoRecordsContent
|
||||
}
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
<tfoot class="@_footClassname">
|
||||
<tr class="mud-table-row">
|
||||
@FooterCells(resolvedPageItems)
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
@if (PagerContent != null)
|
||||
{
|
||||
<div class="mud-table-pagination">
|
||||
@PagerContent
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<Dialog @bind-IsVisible="isEditFormOpen" Options="EditDialogOptions">
|
||||
<TitleContent>
|
||||
<Text Typo="Typo.h6">
|
||||
Edit
|
||||
</Text>
|
||||
</TitleContent>
|
||||
<DialogContent>
|
||||
<Form FieldChanged="FormFieldChanged">
|
||||
@foreach (var column in RenderedColumns)
|
||||
{
|
||||
var cell = new Cell<T>(this, column, _editingItem);
|
||||
|
||||
if (column.EditTemplate != null)
|
||||
{
|
||||
@column.EditTemplate(cell.cellContext)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (column.dataType == typeof(string))
|
||||
{
|
||||
<TextField T="string" Label="@column.computedTitle" Value="@cell.valueString" ValueChanged="@cell.StringValueChangedAsync" Margin="@Margin.Dense"
|
||||
Required="true" Variant="@Variant.Outlined" Disabled="@(!column.IsEditable || ReadOnly)" Class="mt-4" />
|
||||
}
|
||||
else if (column.isNumber)
|
||||
{
|
||||
<NumericField T="double?" Label="@column.computedTitle" Value="@cell.valueNumber" ValueChanged="@cell.NumberValueChangedAsync" Margin="@Margin.Dense"
|
||||
Required="true" Variant="@Variant.Outlined" Disabled="@(!column.IsEditable || ReadOnly)" Class="mt-4" />
|
||||
}
|
||||
}
|
||||
}
|
||||
</Form>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button Variant="Variant.Filled" Color="ThemeColor.Default" OnClick="@CancelEditingItemAsync" Class="px-10">Cancel</Button>
|
||||
<Button Variant="Variant.Filled" Color="ThemeColor.Primary" OnClick="@CommitItemChangesAsync" Class="px-10">Save</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</CascadingValue>
|
||||
|
||||
@code {
|
||||
internal RenderFragment ToolbarMenu(DataGrid<T> __this)
|
||||
{
|
||||
return
|
||||
@<text>
|
||||
<Menu Icon="@Icons.Material.Outlined.Settings" Size="Size.Small" AnchorOrigin="Origin.BottomCenter" Dense="true">
|
||||
<MenuItem OnClick="@__this.ShowColumnsPanel">Columns</MenuItem>
|
||||
@if (__this.Groupable)
|
||||
{
|
||||
<MenuItem OnClick="@__this.ExpandAllGroups">Expand All Groups</MenuItem>
|
||||
<MenuItem OnClick="@__this.CollapseAllGroups">Collapse All Groups</MenuItem>
|
||||
}
|
||||
@if(__this.ServerData != null)
|
||||
{
|
||||
<MenuItem OnClick="@__this.InvokeServerLoadFunc">Refresh Data</MenuItem>
|
||||
}
|
||||
</Menu>
|
||||
</text>;
|
||||
}
|
||||
|
||||
internal RenderFragment FooterCells(IEnumerable<T> currentItems)
|
||||
{
|
||||
return
|
||||
@<text>
|
||||
@if (currentItems != null)
|
||||
{
|
||||
foreach (var column in RenderedColumns)
|
||||
{
|
||||
if (!column.Hidden)
|
||||
{
|
||||
if (column.AggregateDefinition != null || column.FooterTemplate != null || hasFooter)
|
||||
{
|
||||
<FooterCell T="T" Column="@column" CurrentItems="@currentItems"></FooterCell>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</text>;
|
||||
}
|
||||
|
||||
internal RenderFragment Cell(Column<T> column, T item)
|
||||
{
|
||||
return
|
||||
@<text>
|
||||
@{
|
||||
var cell = new Cell<T>(this, column, item);
|
||||
}
|
||||
<td data-label="@column.computedTitle" class="@cell.computedClass" style="@cell.computedStyle">
|
||||
@if (column.IsEditable && !ReadOnly && DataGridEditMode.Cell == EditMode)
|
||||
{
|
||||
if (column.EditTemplate != null)
|
||||
{
|
||||
@column.EditTemplate(cell.cellContext)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (column.dataType == typeof(string))
|
||||
{
|
||||
<TextField T="string" Value="@cell.valueString" ValueChanged="@cell.StringValueChangedAsync" Margin="@Margin.Dense" Style="margin-top:0"
|
||||
Required="true" Variant="@Variant.Text" />
|
||||
}
|
||||
else if (column.isNumber)
|
||||
{
|
||||
<NumericField T="double?" Value="@cell.valueNumber" ValueChanged="@cell.NumberValueChangedAsync" Margin="@Margin.Dense" Style="margin-top:0"
|
||||
Required="true" Variant="@Variant.Text" Culture="@column.Culture" />
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (column.CellTemplate != null)
|
||||
{
|
||||
@column.CellTemplate(cell.cellContext)
|
||||
}
|
||||
else if (column.Culture != null && column.isNumber)
|
||||
{
|
||||
@cell.valueNumber?.ToString(column.Culture)
|
||||
}
|
||||
else
|
||||
{
|
||||
@cell.ComputedValue
|
||||
}
|
||||
}
|
||||
</td>
|
||||
</text>
|
||||
;
|
||||
}
|
||||
|
||||
internal RenderFragment Filter(FilterDefinition<T> f, Column<T> column)
|
||||
{
|
||||
return
|
||||
@<text>
|
||||
@{
|
||||
var filter = new Filter<T>(this, f, column);
|
||||
}
|
||||
@if (column == null)
|
||||
{
|
||||
<Item xs="1" Class="d-flex">
|
||||
<IconButton Class="remove-filter-button" Icon="@Icons.Material.Filled.Close" OnClick="@filter.RemoveFilter" Size="@Size.Small" Style="align-self:flex-end"></IconButton>
|
||||
</Item>
|
||||
<Item xs="4">
|
||||
<Select T="string" Value="@f.Field" ValueChanged="@filter.FieldChanged" FullWidth="true" Label="Column" Dense="true" Margin="@Margin.Dense"
|
||||
Class="filter-field">
|
||||
@foreach (var column in RenderedColumns ?? Enumerable.Empty<Column<T>>())
|
||||
{
|
||||
<SelectItem Value="@column.Field">@column.computedTitle</SelectItem>
|
||||
}
|
||||
</Select>
|
||||
</Item>
|
||||
<Item xs="3">
|
||||
<Select @bind-Value="f.Operator" FullWidth="true" Label="Operator" Dense="true" Margin="@Margin.Dense"
|
||||
Class="filter-operator">
|
||||
@foreach (var o in FilterOperator.GetOperatorByDataType(filter.dataType))
|
||||
{
|
||||
<MudSelectItem Value="@o">@o</MudSelectItem>
|
||||
}
|
||||
</Select>
|
||||
</Item>
|
||||
<Item xs="4">
|
||||
@if (filter.dataType == typeof(string) && !(f.Operator ?? "").EndsWith("empty"))
|
||||
{
|
||||
<TextField T="string" Value="@filter._valueString" ValueChanged="@filter.StringValueChanged" FullWidth="true" Label="Value" Placeholder="Filter value" Margin="@Margin.Dense"
|
||||
Immediate="true" Class="filter-input" />
|
||||
}
|
||||
else if (filter.isNumber && !(f.Operator ?? "").EndsWith("empty"))
|
||||
{
|
||||
<NumericField T="double?" Value="@filter._valueNumber" ValueChanged="@filter.NumberValueChanged" FullWidth="true" Label="Value" Placeholder="Filter value" Margin="@Margin.Dense"
|
||||
Immediate="true" Class="filter-input" Culture="@filter.filterColumn?.Culture" />
|
||||
}
|
||||
else if (filter.isEnum)
|
||||
{
|
||||
<Select T="Enum" Value="@filter._valueEnum" ValueChanged="@filter.EnumValueChanged" FullWidth="true" Dense="true" Margin="@Margin.Dense"
|
||||
Class="filter-input">
|
||||
<SelectItem T="Enum" Value="@(null)"></SelectItem>
|
||||
@foreach (var item in Enum.GetValues(filter.dataType))
|
||||
{
|
||||
<SelectItem T="Enum" Value="@((Enum)item)">@item</SelectItem>
|
||||
}
|
||||
</Select>
|
||||
}
|
||||
else if (filter.dataType == typeof(bool))
|
||||
{
|
||||
<Select T="bool?" Value="@filter._valueBool" ValueChanged="@filter.BoolValueChanged" FullWidth="true" Dense="true" Margin="@Margin.Dense"
|
||||
Class="filter-input">
|
||||
<SelectItem T="bool?" Value="@(null)"></SelectItem>
|
||||
<SelectItem T="bool?" Value="@(true)">true</SelectItem>
|
||||
<SelectItem T="bool?" Value="@(false)">false</SelectItem>
|
||||
</Select>
|
||||
}
|
||||
else if (filter.dataType == typeof(DateTime) && !(f.Operator ?? "").EndsWith("empty"))
|
||||
{
|
||||
<Grid Spacing="0">
|
||||
<Item xs="7">
|
||||
<DatePicker Date="@filter._valueDate" DateChanged="@filter.DateValueChanged" Margin="@Margin.Dense" Class="filter-input-date" />
|
||||
</Item>
|
||||
<Item xs="5">
|
||||
<TimePicker Time="@filter._valueTime" TimeChanged="@filter.TimeValueChanged" Margin="@Margin.Dense" Class="filter-input-time" />
|
||||
</Item>
|
||||
</Grid>
|
||||
}
|
||||
else if (filter.dataType == typeof(Guid))
|
||||
{
|
||||
<TextField T="string" Value="@filter._valueString" ValueChanged="@filter.StringValueChanged" FullWidth="true" Label="Value" Placeholder="Filter value" Margin="@Margin.Dense"
|
||||
Immediate="true" Class="filter-input" />
|
||||
}
|
||||
</Item>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Item xs="12">
|
||||
<Select @bind-Value="f.Operator" FullWidth="true" Dense="true" Margin="@Margin.Dense"
|
||||
Class="filter-operator">
|
||||
@foreach (var o in FilterOperator.GetOperatorByDataType(filter.dataType))
|
||||
{
|
||||
<SelectItem Value="@o">@o</SelectItem>
|
||||
}
|
||||
</Select>
|
||||
</Item>
|
||||
<Item xs="12">
|
||||
@if (filter.dataType == typeof(string) && !(f.Operator ?? "").EndsWith("empty"))
|
||||
{
|
||||
<TextField T="string" Value="@filter._valueString" ValueChanged="@filter.StringValueChanged" FullWidth="true" Placeholder="Filter value" Margin="@Margin.Dense"
|
||||
Immediate="true" Class="filter-input" />
|
||||
}
|
||||
else if (filter.isNumber && !(f.Operator ?? "").EndsWith("empty"))
|
||||
{
|
||||
<NumericField T="double?" Value="@filter._valueNumber" ValueChanged="@filter.NumberValueChanged" FullWidth="true" Placeholder="Filter value" Margin="@Margin.Dense"
|
||||
Immediate="true" Class="filter-input" Culture="@filter.filterColumn?.Culture" />
|
||||
}
|
||||
else if (filter.isEnum)
|
||||
{
|
||||
<Select T="Enum" Value="@filter._valueEnum" ValueChanged="@filter.EnumValueChanged" FullWidth="true" Dense="true" Margin="@Margin.Dense"
|
||||
Class="filter-input">
|
||||
<SelectItem T="Enum" Value="@(null)"></SelectItem>
|
||||
@foreach (var item in Enum.GetValues(filter.dataType))
|
||||
{
|
||||
<SelectItem T="Enum" Value="@((Enum)item)">@item</SelectItem>
|
||||
}
|
||||
</Select>
|
||||
}
|
||||
else if (filter.dataType == typeof(bool))
|
||||
{
|
||||
<Select T="bool?" Value="@filter._valueBool" ValueChanged="@filter.BoolValueChanged" FullWidth="true" Dense="true" Margin="@Margin.Dense"
|
||||
Class="filter-input">
|
||||
<SelectItem T="bool?" Value="@(null)"></SelectItem>
|
||||
<SelectItem T="bool?" Value="@(true)">true</SelectItem>
|
||||
<SelectItem T="bool?" Value="@(false)">false</SelectItem>
|
||||
</Select>
|
||||
}
|
||||
else if (filter.dataType == typeof(DateTime) && !(f.Operator ?? "").EndsWith("empty"))
|
||||
{
|
||||
<Grid Spacing="0">
|
||||
<Item xs="7">
|
||||
<DatePicker Date="@filter._valueDate" DateChanged="@filter.DateValueChanged" Margin="@Margin.Dense" Class="filter-input-date" />
|
||||
</Item>
|
||||
<Item xs="5">
|
||||
<TimePicker Time="@filter._valueTime" TimeChanged="@filter.TimeValueChanged" Margin="@Margin.Dense" Class="filter-input-time" />
|
||||
</Item>
|
||||
</Grid>
|
||||
}
|
||||
else if (filter.dataType == typeof(Guid))
|
||||
{
|
||||
<TextField T="string" Value="@filter._valueString" ValueChanged="@filter.StringValueChanged" FullWidth="true" Label="Value" Placeholder="Filter value" Margin="@Margin.Dense"
|
||||
Immediate="true" Class="filter-input" />
|
||||
}
|
||||
</Item>
|
||||
}
|
||||
</text>
|
||||
;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue