You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Connected.Components/Components/Picker/Picker.razor.cs

558 lines
15 KiB

2 years ago
using System.Diagnostics.CodeAnalysis;
using Connected.Annotations;
using Connected.Services;
using Connected.Utilities;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
namespace Connected.Components;
public partial class Picker<T> : FormComponent<T, string>
{
protected IKeyInterceptor _keyInterceptor;
public Picker() : base(new Converter<T, string>()) { }
protected Picker(Converter<T, string> converter) : base(converter) { }
[Inject] private IKeyInterceptorFactory _keyInterceptorFactory { get; set; }
private string _elementId = "picker" + Guid.NewGuid().ToString().Substring(0, 8);
[Inject] private IBrowserWindowSizeProvider WindowSizeListener { get; set; }
protected string PickerClass =>
new CssBuilder("mud-picker")
.AddClass($"mud-picker-inline", PickerVariant != PickerVariant.Static)
.AddClass($"mud-picker-static", PickerVariant == PickerVariant.Static)
.AddClass($"mud-rounded", PickerVariant == PickerVariant.Static && !_pickerSquare)
.AddClass($"mud-elevation-{_pickerElevation}", PickerVariant == PickerVariant.Static)
.AddClass($"mud-picker-input-button", !Editable && PickerVariant != PickerVariant.Static)
.AddClass($"mud-picker-input-text", Editable && PickerVariant != PickerVariant.Static)
.AddClass($"mud-disabled", Disabled && PickerVariant != PickerVariant.Static)
.AddClass(Class)
.Build();
protected string PickerPaperClass =>
new CssBuilder("mud-picker")
.AddClass("mud-picker-paper")
.AddClass("mud-picker-view", PickerVariant == PickerVariant.Inline)
.AddClass("mud-picker-open", IsOpen && PickerVariant == PickerVariant.Inline)
.AddClass("mud-picker-popover-paper", PickerVariant == PickerVariant.Inline)
.AddClass("mud-dialog", PickerVariant == PickerVariant.Dialog)
.Build();
protected string PickerInlineClass =>
new CssBuilder("mud-picker-inline-paper")
.Build();
protected string PickerContainerClass =>
new CssBuilder("mud-picker-container")
.AddClass("mud-paper-square", _pickerSquare)
.AddClass("mud-picker-container-landscape",
Orientation == Orientation.Landscape && PickerVariant == PickerVariant.Static)
.Build();
protected string PickerInputClass =>
new CssBuilder("mud-input-input-control").AddClass(Class)
.Build();
protected string ActionClass => new CssBuilder("mud-picker-actions")
.AddClass(ClassActions)
.Build();
/// <summary>
/// Sets the icon of the input text field
/// </summary>
[ExcludeFromCodeCoverage]
[Parameter]
[Obsolete("Use AdornmentIcon instead.", true)]
public string InputIcon
{
get { return AdornmentIcon; }
set { AdornmentIcon = value; }
}
/// <summary>
/// The color of the adornment if used. It supports the theme colors.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Appearance)]
public ThemeColor AdornmentColor { get; set; } = ThemeColor.Default;
/// <summary>
/// Sets the icon of the input text field
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Behavior)]
public string AdornmentIcon { get; set; } = Icons.Material.Filled.Event;
/// <summary>
/// Sets the aria-label of the input text field icon
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Appearance)]
public string AdornmentAriaLabel { get; set; } = string.Empty;
/// <summary>
/// The short hint displayed in the input before the user enters a value.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Behavior)]
public string Placeholder { get; set; }
/// <summary>
/// Fired when the dropdown / dialog opens
/// </summary>
[Parameter]
public EventCallback PickerOpened { get; set; }
/// <summary>
/// Fired when the dropdown / dialog closes
/// </summary>
[Parameter]
public EventCallback PickerClosed { get; set; }
/// <summary>
/// The higher the number, the heavier the drop-shadow. 0 for no shadow set to 8 by default in inline mode and 0 in static mode.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerAppearance)]
public int Elevation { set; get; } = 8;
/// <summary>
/// If true, border-radius is set to 0 this is set to true automatically in static mode but can be overridden with Rounded bool.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerAppearance)]
public bool Square { get; set; }
/// <summary>
/// If true, no date or time can be defined.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Behavior)]
public bool ReadOnly { get; set; }
/// <summary>
/// If true, border-radius is set to theme default when in Static Mode.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerAppearance)]
public bool Rounded { get; set; }
/// <summary>
/// If string has value, HelperText will be applied.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Behavior)]
public string HelperText { get; set; }
/// <summary>
/// If true, the helper text will only be visible on focus.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Behavior)]
public bool HelperTextOnFocus { get; set; }
/// <summary>
/// If string has value the label text will be displayed in the input, and scaled down at the top if the input has value.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Behavior)]
public string Label { get; set; }
/// <summary>
/// Show clear button.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Behavior)]
public bool Clearable { get; set; } = false;
/// <summary>
/// If true, the picker will be disabled.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Behavior)]
public bool Disabled { get; set; }
/// <summary>
/// If true, the picker will be editable.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Behavior)]
public bool Editable { get; set; } = false;
/// <summary>
/// Hide toolbar and show only date/time views.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerAppearance)]
public bool DisableToolbar { get; set; }
/// <summary>
/// User class names for picker's ToolBar, separated by space
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerAppearance)]
public string ToolBarClass { get; set; }
/// <summary>
/// Picker container option
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Behavior)]
public PickerVariant PickerVariant { get; set; } = PickerVariant.Inline;
/// <summary>
/// Variant of the text input
/// </summary>
[ExcludeFromCodeCoverage]
[Parameter]
[Obsolete("Use Variant instead.", true)]
public Variant InputVariant
{
get { return Variant; }
set { Variant = value; }
}
/// <summary>
/// Variant of the text input
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Appearance)]
public Variant Variant { get; set; } = Variant.Text;
/// <summary>
/// Sets if the icon will be att start or end, set to false to disable.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Behavior)]
public Adornment Adornment { get; set; } = Adornment.End;
/// <summary>
/// What orientation to render in when in PickerVariant Static Mode.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerAppearance)]
public Orientation Orientation { get; set; } = Orientation.Portrait;
/// <summary>
/// Sets the Icon Size.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Appearance)]
public Size IconSize { get; set; } = Size.Medium;
/// <summary>
/// The color of the toolbar, selected and active. It supports the theme colors.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerAppearance)]
public ThemeColor Color { get; set; } = ThemeColor.Primary;
/// <summary>
/// Changes the cursor appearance.
/// </summary>
[Obsolete("This is enabled now by default when you use Editable=true. You can remove the parameter.", false)]
[Parameter]
public bool AllowKeyboardInput { get; set; }
/// <summary>
/// Fired when the text changes.
/// </summary>
[Parameter]
public EventCallback<string> TextChanged { get; set; }
/// <summary>
/// The currently selected string value (two-way bindable)
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Data)]
public string Text
{
get => _text;
set => SetTextAsync(value, true).AndForget();
}
private string _text;
/// <summary>
/// CSS class that will be applied to the action buttons container
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerAppearance)]
public string ClassActions { get; set; }
/// <summary>
/// Define the action buttons here
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.PickerBehavior)]
public RenderFragment<Picker<T>> PickerActions { get; set; }
/// <summary>
/// Will adjust vertical spacing.
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Appearance)]
public Margin Margin { get; set; } = Margin.None;
/// <summary>
/// A mask for structured input of the date (requires Editable to be true).
/// </summary>
[Parameter]
[Category(CategoryTypes.FormComponent.Behavior)]
public IMask Mask
{
get => _mask;
set => _mask = value;
}
/// <summary>
/// Gets or sets the origin of the popover's anchor. Defaults to <see cref="Origin.TopLeft"/>
/// </summary>
[Parameter]
[Category(CategoryTypes.Popover.Appearance)]
public Origin AnchorOrigin { get; set; } = Origin.TopLeft;
/// <summary>
/// Gets or sets the origin of the popover's transform. Defaults to <see cref="Origin.TopLeft"/>
/// </summary>
[Parameter]
[Category(CategoryTypes.Popover.Appearance)]
public Origin TransformOrigin { get; set; } = Origin.TopLeft;
protected IMask _mask = null;
protected async Task SetTextAsync(string value, bool callback)
{
if (_text != value)
{
_text = value;
if (callback)
await StringValueChanged(_text);
await TextChanged.InvokeAsync(_text);
}
}
/// <summary>
/// Value change hook for descendants.
/// </summary>
protected virtual Task StringValueChanged(string value)
{
return Task.CompletedTask;
}
protected bool IsOpen { get; set; }
public void ToggleOpen()
{
if (IsOpen)
Close();
else
Open();
}
public void Close(bool submit = true)
{
IsOpen = false;
if (submit)
{
Submit();
}
OnClosed();
StateHasChanged();
}
public void Open()
{
IsOpen = true;
StateHasChanged();
OnOpened();
}
private void CloseOverlay() => Close(PickerActions == null);
protected internal virtual void Submit() { }
public virtual void Clear(bool close = true)
{
if (close && PickerVariant != PickerVariant.Static)
{
Close(false);
}
}
protected override void ResetValue()
{
_inputReference?.Reset();
base.ResetValue();
}
protected internal TextField<string> _inputReference;
public virtual ValueTask FocusAsync() => _inputReference?.FocusAsync() ?? ValueTask.CompletedTask;
public virtual ValueTask BlurAsync() => _inputReference?.BlurAsync() ?? ValueTask.CompletedTask;
public virtual ValueTask SelectAsync() => _inputReference?.SelectAsync() ?? ValueTask.CompletedTask;
public virtual ValueTask SelectRangeAsync(int pos1, int pos2) =>
_inputReference?.SelectRangeAsync(pos1, pos2) ?? ValueTask.CompletedTask;
private bool _pickerSquare;
private int _pickerElevation;
private ElementReference _pickerInlineRef;
protected override void OnInitialized()
{
if (PickerVariant == PickerVariant.Static)
{
IsOpen = true;
if (Elevation == 8)
{
_pickerElevation = 0;
}
else
{
_pickerElevation = Elevation;
}
if (!Rounded)
{
_pickerSquare = true;
}
}
else
{
_pickerSquare = Square;
_pickerElevation = Elevation;
}
if (Label == null && For != null)
Label = For.GetLabelString();
}
private async Task EnsureKeyInterceptor()
{
if (_keyInterceptor == null)
{
_keyInterceptor = _keyInterceptorFactory.Create();
await _keyInterceptor.Connect(_elementId, new KeyInterceptorOptions()
{
//EnableLogging = true,
TargetClass = "mud-input-slot",
Keys =
{
new KeyOptions { Key = " ", PreventDown = "key+none" },
new KeyOptions { Key = "ArrowUp", PreventDown = "key+none" },
new KeyOptions { Key = "ArrowDown", PreventDown = "key+none" },
new KeyOptions { Key = "Enter", PreventDown = "key+none" },
new KeyOptions { Key = "NumpadEnter", PreventDown = "key+none" },
new KeyOptions { Key = "/./", SubscribeDown = true, SubscribeUp = true }, // for our users
},
});
_keyInterceptor.KeyDown += HandleKeyDown;
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender == true)
{
await EnsureKeyInterceptor();
}
await base.OnAfterRenderAsync(firstRender);
}
protected internal void ToggleState()
{
if (Disabled)
return;
if (IsOpen)
{
IsOpen = false;
OnClosed();
}
else
{
IsOpen = true;
OnOpened();
FocusAsync();
}
}
protected virtual async void OnOpened()
{
OnPickerOpened();
if (PickerVariant == PickerVariant.Inline)
{
await _pickerInlineRef.ChangeCssAsync(PickerInlineClass);
}
await EnsureKeyInterceptor();
await _keyInterceptor.UpdateKey(new() { Key = "Escape", StopDown = "key+none" });
}
protected virtual async void OnClosed()
{
OnPickerClosed();
await EnsureKeyInterceptor();
await _keyInterceptor.UpdateKey(new() { Key = "Escape", StopDown = "none" });
}
protected virtual void OnPickerOpened()
{
PickerOpened.InvokeAsync(this);
}
protected virtual void OnPickerClosed()
{
PickerClosed.InvokeAsync(this);
}
protected internal virtual void HandleKeyDown(KeyboardEventArgs obj)
{
if (Disabled || ReadOnly)
return;
switch (obj.Key)
{
case "Backspace":
if (obj.CtrlKey == true && obj.ShiftKey == true)
{
Clear();
_value = default(T);
Reset();
}
break;
case "Escape":
case "Tab":
Close(false);
break;
}
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing == true)
{
if (_keyInterceptor != null)
{
_keyInterceptor.KeyDown -= HandleKeyDown;
_keyInterceptor.Dispose();
}
}
}
}