|
|
@ -9,39 +9,18 @@ namespace Connected.Components;
|
|
|
|
public partial class Input<T> : InputBase<T>
|
|
|
|
public partial class Input<T> : InputBase<T>
|
|
|
|
{
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
#region Event Callbacks
|
|
|
|
* Debounce
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// <summary>
|
|
|
|
/// The current character counter, displayed below the text field.
|
|
|
|
/// Paste hook for descendants.
|
|
|
|
/// </summary>
|
|
|
|
/// </summary>
|
|
|
|
[Parameter] public string CounterText { get; set; }
|
|
|
|
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
|
|
|
|
|
|
|
|
|
|
|
private System.Timers.Timer _timer;
|
|
|
|
|
|
|
|
private double _debounceInterval;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
protected virtual async Task OnPaste(ClipboardEventArgs args)
|
|
|
|
/// Interval to be awaited in MILLISECONDS before changing the Text value
|
|
|
|
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
[Parameter]
|
|
|
|
|
|
|
|
public double TextChangeDelay
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
get => _debounceInterval;
|
|
|
|
// do nothing
|
|
|
|
set
|
|
|
|
return;
|
|
|
|
{
|
|
|
|
|
|
|
|
if (NumericConverter<double>.AreEqual(_debounceInterval, value))
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
_debounceInterval = value;
|
|
|
|
|
|
|
|
if (_debounceInterval == 0)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// not debounced, dispose timer if any
|
|
|
|
|
|
|
|
ClearTimer(suppressTick: false);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
SetTimer();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// <summary>
|
|
|
@ -81,126 +60,232 @@ public partial class Input<T> : InputBase<T>
|
|
|
|
return Task.CompletedTask;
|
|
|
|
return Task.CompletedTask;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected override void OnParametersSet()
|
|
|
|
private void OnTimerTick(object sender, ElapsedEventArgs e)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
base.OnParametersSet();
|
|
|
|
InvokeAsync(OnTimerTickGuiThread).AndForget();
|
|
|
|
// if input is to be debounced, makes sense to bind the change of the text to oninput
|
|
|
|
|
|
|
|
// so we set Immediate to true
|
|
|
|
|
|
|
|
if (TextChangeDelay > 0)
|
|
|
|
|
|
|
|
Immediate = true;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void SetTimer()
|
|
|
|
private async Task OnTimerTickGuiThread()
|
|
|
|
{
|
|
|
|
{
|
|
|
|
if (_timer == null)
|
|
|
|
await base.UpdateValuePropertyAsync(false);
|
|
|
|
|
|
|
|
await OnDebounceIntervalElapsed.InvokeAsync(Text);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
protected Task OnInput(ChangeEventArgs args)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (!ChangeTextImmediately)
|
|
|
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
|
|
|
|
_isFocused = true;
|
|
|
|
|
|
|
|
return SetTextAsync(args?.Value as string);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected async Task OnChange(ChangeEventArgs args)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (TextChangeDelay > 0 && _timer != null)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
_timer = new System.Timers.Timer();
|
|
|
|
_timer.Stop();
|
|
|
|
_timer.Elapsed += OnTimerTick;
|
|
|
|
await UpdateValuePropertyAsync(false);
|
|
|
|
_timer.AutoReset = false;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_timer.Interval = TextChangeDelay;
|
|
|
|
else
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_internalText = args?.Value as string;
|
|
|
|
|
|
|
|
await OnInternalInputChanged.InvokeAsync(args);
|
|
|
|
|
|
|
|
if (!ChangeTextImmediately)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
await SetTextAsync(args?.Value as string);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnTimerTick(object sender, ElapsedEventArgs e)
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Invokes the callback when the Up arrow button is clicked when the input is set to <see cref="InputType.Number"/>.
|
|
|
|
|
|
|
|
/// Note: use the optimized control <see cref="NumericField{T}"/> if you need to deal with numbers.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
[Parameter] public EventCallback OnIncrement { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Invokes the callback when the Down arrow button is clicked when the input is set to <see cref="InputType.Number"/>.
|
|
|
|
|
|
|
|
/// Note: use the optimized control <see cref="NumericField{T}"/> if you need to deal with numbers.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
[Parameter] public EventCallback OnDecrement { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Button click event for clear button. Called after text and value has been cleared.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
[Parameter] public EventCallback<MouseEventArgs> OnClearButtonClick { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Mouse wheel event for input.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
[Parameter] public EventCallback<WheelEventArgs> OnMouseWheel { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public override async ValueTask FocusAsync()
|
|
|
|
{
|
|
|
|
{
|
|
|
|
InvokeAsync(OnTimerTickGuiThread).AndForget();
|
|
|
|
try
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (InputType == InputType.Hidden && ChildContent != null)
|
|
|
|
|
|
|
|
await _elementReference1.FocusAsync();
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
await ElementReference.FocusAsync();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
Console.WriteLine("Input.FocusAsync: " + e.Message);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task OnTimerTickGuiThread()
|
|
|
|
public override ValueTask BlurAsync()
|
|
|
|
{
|
|
|
|
{
|
|
|
|
await base.UpdateValuePropertyAsync(false);
|
|
|
|
return ElementReference.BlurAsync();
|
|
|
|
await OnDebounceIntervalElapsed.InvokeAsync(Text);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ClearTimer(bool suppressTick = false)
|
|
|
|
public override ValueTask SelectAsync()
|
|
|
|
{
|
|
|
|
{
|
|
|
|
if (_timer == null)
|
|
|
|
return ElementReference.SelectAsync();
|
|
|
|
return;
|
|
|
|
|
|
|
|
var wasEnabled = _timer.Enabled;
|
|
|
|
|
|
|
|
_timer.Stop();
|
|
|
|
|
|
|
|
_timer.Elapsed -= OnTimerTick;
|
|
|
|
|
|
|
|
_timer.Dispose();
|
|
|
|
|
|
|
|
_timer = null;
|
|
|
|
|
|
|
|
if (wasEnabled && !suppressTick)
|
|
|
|
|
|
|
|
OnTimerTickGuiThread().AndForget();
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
public override ValueTask SelectRangeAsync(int pos1, int pos2)
|
|
|
|
* Debounce end
|
|
|
|
{
|
|
|
|
*/
|
|
|
|
return ElementReference.SelectRangeAsync(pos1, pos2);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected CssBuilder CompiledHelperContainerClassList
|
|
|
|
private void UpdateClearable(object value)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
get
|
|
|
|
var showClearable = Clearable && ((value is string stringValue && !string.IsNullOrWhiteSpace(stringValue)) || (value is not string && value is not null));
|
|
|
|
{
|
|
|
|
if (_showClearable != showClearable)
|
|
|
|
return new CssBuilder("input-control-helper-container")
|
|
|
|
_showClearable = showClearable;
|
|
|
|
.AddClass($"px-1", Variant == Variant.Filled)
|
|
|
|
|
|
|
|
.AddClass($"px-2", Variant == Variant.Outlined)
|
|
|
|
|
|
|
|
.AddClass($"px-1", Variant == Variant.Text)
|
|
|
|
|
|
|
|
.AddClass(CompiledClearButtonClassList.Build())
|
|
|
|
|
|
|
|
.AddClass(CompiledHelperClassList.Build());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected CssBuilder CompiledClearButtonClassList
|
|
|
|
protected override async Task UpdateTextPropertyAsync(bool updateValue)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
get
|
|
|
|
await base.UpdateTextPropertyAsync(updateValue);
|
|
|
|
{
|
|
|
|
if (Clearable)
|
|
|
|
return new CssBuilder()
|
|
|
|
UpdateClearable(Text);
|
|
|
|
.AddClass("me-n1", Adornment == Adornment.End && HideSpinButtons == false)
|
|
|
|
}
|
|
|
|
.AddClass("icon-button-edge-end", Adornment == Adornment.End && HideSpinButtons == true)
|
|
|
|
|
|
|
|
.AddClass("me-6", Adornment != Adornment.End && HideSpinButtons == false)
|
|
|
|
protected virtual async Task ClearButtonClickHandlerAsync(MouseEventArgs e)
|
|
|
|
.AddClass("icon-button-edge-margin-end", Adornment != Adornment.End && HideSpinButtons == true);
|
|
|
|
{
|
|
|
|
}
|
|
|
|
await SetTextAsync(string.Empty, updateValue: true);
|
|
|
|
|
|
|
|
await ElementReference.FocusAsync();
|
|
|
|
|
|
|
|
await OnClearButtonClick.InvokeAsync(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// <summary>
|
|
|
|
/// A space separated list of class names, added on top of the default helper container class list.
|
|
|
|
/// Custom input Attributes
|
|
|
|
/// </summary>
|
|
|
|
/// </summary>
|
|
|
|
[Parameter]
|
|
|
|
[Parameter] public Dictionary<string, object>? Attributes { get; set; } = null;
|
|
|
|
public string? HelperContainerClassList { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#region Style properties
|
|
|
|
|
|
|
|
|
|
|
|
protected CssBuilder CompiledHelperClassList
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Type of the input element. It should be a valid HTML5 input type.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
[Parameter] public InputType InputType { get; set; } = InputType.Text;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private string ClearIcon = Icons.Material.Filled.Clear;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private string NumericUpIcon = Icons.Material.Filled.KeyboardArrowUp;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private string NumericDownIcon = Icons.Material.Filled.KeyboardArrowDown;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Hides the spin buttons"/>
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
[Parameter] public bool HideSpinButtons { get; set; } = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Show clear button.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
[Parameter] public bool Clearable { get; set; } = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#region Wrapper class
|
|
|
|
|
|
|
|
[Parameter]
|
|
|
|
|
|
|
|
public string WrapperClass { get; set; } = string.Empty;
|
|
|
|
|
|
|
|
protected CssBuilder CompiledWrapperClass
|
|
|
|
{
|
|
|
|
{
|
|
|
|
get
|
|
|
|
get
|
|
|
|
{
|
|
|
|
{
|
|
|
|
return new CssBuilder("input-helper-text")
|
|
|
|
return new CssBuilder("input")
|
|
|
|
.AddClass("input-helper-onfocus", HelperTextOnFocus)
|
|
|
|
.AddClass($"input-{Variant.ToDescription()}")
|
|
|
|
.AddClass(HelperClassList);
|
|
|
|
.AddClass($"input-adorned-{Adornment.ToDescription()}", Adornment != Adornment.None)
|
|
|
|
|
|
|
|
.AddClass($"input-margin-{Margin.ToDescription()}", when: () => Margin != Margin.None)
|
|
|
|
|
|
|
|
.AddClass("input-underline", when: () => DisableUnderLine == false && Variant != Variant.Outlined)
|
|
|
|
|
|
|
|
.AddClass("shrink", when: HasNativeHtmlPlaceholder() || !string.IsNullOrEmpty(Text) || Adornment == Adornment.Start || !string.IsNullOrWhiteSpace(Placeholder))
|
|
|
|
|
|
|
|
.AddClass("disabled", Disabled)
|
|
|
|
|
|
|
|
.AddClass("input-error", HasErrors)
|
|
|
|
|
|
|
|
.AddClass("ltr", GetInputType() == InputType.Email || GetInputType() == InputType.Telephone)
|
|
|
|
|
|
|
|
.AddClass(WrapperClass);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// A space separated list of class names, added on top of the default helper class list.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
[Parameter]
|
|
|
|
|
|
|
|
public string? HelperClassList { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
/*protected string HelperContainer =>
|
|
|
|
#region Input field class
|
|
|
|
new CssBuilder("input-control-helper-container")
|
|
|
|
|
|
|
|
.AddClass($"px-1", Variant == Variant.Filled)
|
|
|
|
|
|
|
|
.AddClass($"px-2", Variant == Variant.Outlined)
|
|
|
|
|
|
|
|
.Build();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[Parameter]
|
|
|
|
|
|
|
|
public string InputClass { get; set; } = string.Empty;
|
|
|
|
|
|
|
|
protected CssBuilder CompiledInputClass
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
get
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return new CssBuilder("input-slot")
|
|
|
|
|
|
|
|
.AddClass("input-root")
|
|
|
|
|
|
|
|
.AddClass($"input-root-{Variant.ToDescription()}")
|
|
|
|
|
|
|
|
.AddClass($"input-root-adorned-{Adornment.ToDescription()}", Adornment != Adornment.None)
|
|
|
|
|
|
|
|
.AddClass($"input-root-margin-{Margin.ToDescription()}", when: () => Margin != Margin.None)
|
|
|
|
|
|
|
|
.AddClass(InputClass);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
protected string HelperClass =>
|
|
|
|
#region Adornment class
|
|
|
|
new CssBuilder("input-helper-text")
|
|
|
|
[Parameter]
|
|
|
|
.AddClass("input-helper-onfocus", HelperTextOnFocus)
|
|
|
|
public string AdornmentClass { get; set; } = string.Empty;
|
|
|
|
.Build();*/
|
|
|
|
protected CssBuilder CompiledAdornmentClass
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
get
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return new CssBuilder("input-adornment")
|
|
|
|
|
|
|
|
.AddClass($"input-adornment-{Adornment.ToDescription()}", Adornment != Adornment.None)
|
|
|
|
|
|
|
|
.AddClass($"text", !string.IsNullOrEmpty(AdornmentText))
|
|
|
|
|
|
|
|
.AddClass($"input-root-filled-shrink", Variant == Variant.Filled)
|
|
|
|
|
|
|
|
.AddClass(AdornmentClass);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
private string GetCounterText()
|
|
|
|
#region Clear icon class
|
|
|
|
|
|
|
|
[Parameter]
|
|
|
|
|
|
|
|
public string ClearButtonClass { get; set; } = string.Empty;
|
|
|
|
|
|
|
|
protected CssBuilder CompiledClearButtonClassList
|
|
|
|
{
|
|
|
|
{
|
|
|
|
string result = Text.Length.ToString();
|
|
|
|
get
|
|
|
|
if (string.IsNullOrEmpty(Text)) result = "0";
|
|
|
|
{
|
|
|
|
return result;
|
|
|
|
return new CssBuilder()
|
|
|
|
|
|
|
|
.AddClass("me-n1", Adornment == Adornment.End && HideSpinButtons == false)
|
|
|
|
|
|
|
|
.AddClass("icon-button-edge-end", Adornment == Adornment.End && HideSpinButtons == true)
|
|
|
|
|
|
|
|
.AddClass("me-6", Adornment != Adornment.End && HideSpinButtons == false)
|
|
|
|
|
|
|
|
.AddClass("icon-button-edge-margin-end", Adornment != Adornment.End && HideSpinButtons == true)
|
|
|
|
|
|
|
|
.AddClass(ClearButtonClass);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
protected string Classname => InputCssHelper.GetClassname(this,
|
|
|
|
#region Helper container class
|
|
|
|
() => HasNativeHtmlPlaceholder() || !string.IsNullOrEmpty(Text) || Adornment == Adornment.Start || !string.IsNullOrWhiteSpace(Placeholder));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected CssBuilder CompiledClassList
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// A space separated list of class names, added on top of the default helper container class list.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
[Parameter]
|
|
|
|
|
|
|
|
public string? HelperContainerClass { get; set; }
|
|
|
|
|
|
|
|
protected CssBuilder CompiledHelperContainerClassList
|
|
|
|
{
|
|
|
|
{
|
|
|
|
get
|
|
|
|
get
|
|
|
|
{
|
|
|
|
{
|
|
|
@ -208,304 +293,149 @@ public partial class Input<T> : InputBase<T>
|
|
|
|
.AddClass($"px-1", Variant == Variant.Filled)
|
|
|
|
.AddClass($"px-1", Variant == Variant.Filled)
|
|
|
|
.AddClass($"px-2", Variant == Variant.Outlined)
|
|
|
|
.AddClass($"px-2", Variant == Variant.Outlined)
|
|
|
|
.AddClass($"px-1", Variant == Variant.Text)
|
|
|
|
.AddClass($"px-1", Variant == Variant.Text)
|
|
|
|
.AddClass(CompiledClearButtonClassList.Build())
|
|
|
|
.AddClass(HelperContainerClass);
|
|
|
|
.AddClass(CompiledHelperClassList.Build());
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected string InputClassname => InputCssHelper.GetInputClassname(this);
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
protected string AdornmentClassname => InputCssHelper.GetAdornmentClassname(this);
|
|
|
|
#region Error container class -- still needs implementation
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
#region Counter container class -- still needs implementation
|
|
|
|
/// Type of the input element. It should be a valid HTML5 input type.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
[Parameter] public InputType InputType { get; set; } = InputType.Text;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
internal override InputType GetInputType() => InputType;
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
protected string InputTypeString => InputType.ToDescription();
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private bool IsDecimalNumber(string type)
|
|
|
|
|
|
|
|
|
|
|
|
#region Content placeholders
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// The current character counter, displayed below the text field.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
[Parameter] public string CounterText { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private System.Timers.Timer _timer;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private double _textChangeInterval;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Interval to be awaited in MILLISECONDS before changing the Text value
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
[Parameter]
|
|
|
|
|
|
|
|
public double TextChangeDelay
|
|
|
|
{
|
|
|
|
{
|
|
|
|
switch (type.ToLower())
|
|
|
|
get => _textChangeInterval;
|
|
|
|
|
|
|
|
set
|
|
|
|
{
|
|
|
|
{
|
|
|
|
case "double":
|
|
|
|
if (NumericConverter<double>.AreEqual(_textChangeInterval, value))
|
|
|
|
case "float":
|
|
|
|
return;
|
|
|
|
return true;
|
|
|
|
_textChangeInterval = value;
|
|
|
|
default:
|
|
|
|
if (_textChangeInterval == 0)
|
|
|
|
return false;
|
|
|
|
{
|
|
|
|
|
|
|
|
// not debounced, dispose timer if any
|
|
|
|
|
|
|
|
ClearTimer(suppressTick: false);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
SetTimer();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private bool IsNumber(string s)
|
|
|
|
private void SetTimer()
|
|
|
|
{
|
|
|
|
{
|
|
|
|
bool result = false;
|
|
|
|
if (_timer == null)
|
|
|
|
try
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
double d;
|
|
|
|
|
|
|
|
result= double.TryParse(s, out d);
|
|
|
|
|
|
|
|
} catch
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
result = false;
|
|
|
|
_timer = new System.Timers.Timer();
|
|
|
|
|
|
|
|
_timer.Elapsed += OnTimerTick;
|
|
|
|
|
|
|
|
_timer.AutoReset = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
_timer.Interval = TextChangeDelay;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private string ValidateInput(string value)
|
|
|
|
private void ClearTimer(bool suppressTick = false)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
string result = value;
|
|
|
|
if (_timer == null)
|
|
|
|
if (value is not null)
|
|
|
|
return;
|
|
|
|
{
|
|
|
|
var wasEnabled = _timer.Enabled;
|
|
|
|
var expectedType = typeof(T).Name;
|
|
|
|
_timer.Stop();
|
|
|
|
if (IsNumericType(expectedType))
|
|
|
|
_timer.Elapsed -= OnTimerTick;
|
|
|
|
{
|
|
|
|
_timer.Dispose();
|
|
|
|
if (IsNumber(value.ToString()))
|
|
|
|
_timer = null;
|
|
|
|
{
|
|
|
|
if (wasEnabled && !suppressTick)
|
|
|
|
result = value.ToString();
|
|
|
|
OnTimerTickGuiThread().AndForget();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (IsDecimalNumber(value.ToString()))
|
|
|
|
|
|
|
|
result = Regex.Replace(value.ToString(), "[^0-9.]", "");
|
|
|
|
|
|
|
|
else result = Regex.Replace(value.ToString(), "[^0-9]", "");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
result = value;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
|
|
}*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected Task OnInput(ChangeEventArgs args)
|
|
|
|
/// <summary>
|
|
|
|
{
|
|
|
|
/// ChildContent of the MudInput will only be displayed if InputType.Hidden and if its not null.
|
|
|
|
if (!Immediate)
|
|
|
|
/// </summary>
|
|
|
|
return Task.CompletedTask;
|
|
|
|
[Parameter] public RenderFragment ChildContent { get; set; }
|
|
|
|
_isFocused = true;
|
|
|
|
|
|
|
|
return SetTextAsync(args?.Value as string);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private string TextCounter()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (string.IsNullOrEmpty(Text)) return "0";
|
|
|
|
|
|
|
|
return Text.Length.ToString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected async Task OnChange(ChangeEventArgs args)
|
|
|
|
[Parameter]
|
|
|
|
{
|
|
|
|
public double Step { get; set; } = 1;
|
|
|
|
if (TextChangeDelay > 0 && _timer != null)
|
|
|
|
internal override InputType GetInputType() => InputType;
|
|
|
|
|
|
|
|
protected string InputTypeString => InputType.ToDescription();
|
|
|
|
|
|
|
|
public ElementReference ElementReference { get; private set; }
|
|
|
|
|
|
|
|
private ElementReference _elementReference1;
|
|
|
|
|
|
|
|
private Size GetButtonSize() => Margin == Margin.Dense ? Size.Small : Size.Medium;
|
|
|
|
|
|
|
|
private bool _showClearable;
|
|
|
|
|
|
|
|
private string _internalText;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#region Lifecycle events
|
|
|
|
|
|
|
|
public override async Task SetParametersAsync(ParameterView parameters)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
await base.SetParametersAsync(parameters);
|
|
|
|
|
|
|
|
//if (!_isFocused || _forceTextUpdate)
|
|
|
|
|
|
|
|
// _internalText = Text;
|
|
|
|
|
|
|
|
if (RuntimeLocation.IsServerSide && TextUpdateSuppression)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
_timer.Stop();
|
|
|
|
// Text update suppression, only in BSS (not in WASM).
|
|
|
|
await UpdateValuePropertyAsync(false);
|
|
|
|
// This is a fix for #1012
|
|
|
|
|
|
|
|
if (!_isFocused || _forceTextUpdate)
|
|
|
|
|
|
|
|
_internalText = Text;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
else
|
|
|
|
{
|
|
|
|
{
|
|
|
|
_internalText = args?.Value as string;
|
|
|
|
// in WASM (or in BSS with TextUpdateSuppression==false) we always update
|
|
|
|
await OnInternalInputChanged.InvokeAsync(args);
|
|
|
|
_internalText = Text;
|
|
|
|
if (!Immediate)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
await SetTextAsync(args?.Value as string);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Paste hook for descendants.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected virtual async Task OnPaste(ClipboardEventArgs args)
|
|
|
|
if (Helper.IsNumericType(typeof(T).Name) && InputType != InputType.Number)
|
|
|
|
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// do nothing
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// ChildContent of the MudInput will only be displayed if InputType.Hidden and if its not null.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
[Parameter] public RenderFragment ChildContent { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public ElementReference ElementReference { get; private set; }
|
|
|
|
|
|
|
|
private ElementReference _elementReference1;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public override async ValueTask FocusAsync()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (InputType == InputType.Hidden && ChildContent != null)
|
|
|
|
|
|
|
|
await _elementReference1.FocusAsync();
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
await ElementReference.FocusAsync();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
Console.WriteLine("Input.FocusAsync: " + e.Message);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public override ValueTask BlurAsync()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return ElementReference.BlurAsync();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public override ValueTask SelectAsync()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return ElementReference.SelectAsync();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public override ValueTask SelectRangeAsync(int pos1, int pos2)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return ElementReference.SelectRangeAsync(pos1, pos2);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Invokes the callback when the Up arrow button is clicked when the input is set to <see cref="InputType.Number"/>.
|
|
|
|
|
|
|
|
/// Note: use the optimized control <see cref="NumericField{T}"/> if you need to deal with numbers.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
[Parameter] public EventCallback OnIncrement { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Invokes the callback when the Down arrow button is clicked when the input is set to <see cref="InputType.Number"/>.
|
|
|
|
|
|
|
|
/// Note: use the optimized control <see cref="MudNumericField{T}"/> if you need to deal with numbers.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
[Parameter] public EventCallback OnDecrement { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Hides the spin buttons for <see cref="MudNumericField{T}"/>
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
[Parameter] public bool HideSpinButtons { get; set; } = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <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; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Mouse wheel event for input.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
[Parameter] public EventCallback<WheelEventArgs> OnMouseWheel { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Custom clear icon.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
[Parameter] public string ClearIcon { get; set; } = Icons.Material.Filled.Clear;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Custom numeric up icon.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
[Parameter] public string NumericUpIcon { get; set; } = Icons.Material.Filled.KeyboardArrowUp;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Custom numeric down icon.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
[Parameter] public string NumericDownIcon { get; set; } = Icons.Material.Filled.KeyboardArrowDown;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private Size GetButtonSize() => Margin == Margin.Dense ? Size.Small : Size.Medium;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private bool _showClearable;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void UpdateClearable(object value)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var showClearable = Clearable && ((value is string stringValue && !string.IsNullOrWhiteSpace(stringValue)) || (value is not string && value is not null));
|
|
|
|
|
|
|
|
if (_showClearable != showClearable)
|
|
|
|
|
|
|
|
_showClearable = showClearable;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected override async Task UpdateTextPropertyAsync(bool updateValue)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
await base.UpdateTextPropertyAsync(updateValue);
|
|
|
|
|
|
|
|
if (Clearable)
|
|
|
|
|
|
|
|
UpdateClearable(Text);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*protected override async Task UpdateValuePropertyAsync(bool updateText)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
await base.UpdateValuePropertyAsync(updateText);
|
|
|
|
|
|
|
|
if (Clearable)
|
|
|
|
|
|
|
|
UpdateClearable(Value);
|
|
|
|
|
|
|
|
}*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected virtual async Task ClearButtonClickHandlerAsync(MouseEventArgs e)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
await SetTextAsync(string.Empty, updateValue: true);
|
|
|
|
|
|
|
|
await ElementReference.FocusAsync();
|
|
|
|
|
|
|
|
await OnClearButtonClick.InvokeAsync(e);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private string _internalText;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public override async Task SetParametersAsync(ParameterView parameters)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
await base.SetParametersAsync(parameters);
|
|
|
|
|
|
|
|
//if (!_isFocused || _forceTextUpdate)
|
|
|
|
|
|
|
|
// _internalText = Text;
|
|
|
|
|
|
|
|
if (RuntimeLocation.IsServerSide && TextUpdateSuppression)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// Text update suppression, only in BSS (not in WASM).
|
|
|
|
|
|
|
|
// This is a fix for #1012
|
|
|
|
|
|
|
|
if (!_isFocused || _forceTextUpdate)
|
|
|
|
|
|
|
|
_internalText = Text;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// in WASM (or in BSS with TextUpdateSuppression==false) we always update
|
|
|
|
|
|
|
|
_internalText = Text;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
string baseTypeName = typeof(T).Name;
|
|
|
|
|
|
|
|
if (IsNumericType(baseTypeName) && InputType !=InputType.Number)
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
InputType = InputType.Number;
|
|
|
|
InputType = InputType.Number;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private bool IsNumericType(string type)
|
|
|
|
protected override void OnParametersSet()
|
|
|
|
{
|
|
|
|
{
|
|
|
|
switch (type.ToLower())
|
|
|
|
base.OnParametersSet();
|
|
|
|
{
|
|
|
|
// if input is to be debounced, makes sense to bind the change of the text to oninput
|
|
|
|
case "uint16":
|
|
|
|
// so we set ChangeTextImmediately to true
|
|
|
|
case "uint32":
|
|
|
|
if (TextChangeDelay > 0)
|
|
|
|
case "uint64":
|
|
|
|
ChangeTextImmediately = true;
|
|
|
|
case "int16":
|
|
|
|
|
|
|
|
case "int32":
|
|
|
|
|
|
|
|
case "int64":
|
|
|
|
|
|
|
|
case "int":
|
|
|
|
|
|
|
|
case "double":
|
|
|
|
|
|
|
|
case "decimal":
|
|
|
|
|
|
|
|
case "float":
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Sets the input text from outside programmatically
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
/// <param name="text"></param>
|
|
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
|
|
public Task SetText(string text)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_internalText = text;
|
|
|
|
|
|
|
|
return SetTextAsync(text);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Certain HTML5 inputs (dates and color) have a native placeholder
|
|
|
|
|
|
|
|
private bool HasNativeHtmlPlaceholder()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return GetInputType() is InputType.Color or InputType.Date or InputType.DateTimeLocal or InputType.Month
|
|
|
|
|
|
|
|
or InputType.Time or InputType.Week;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public class InputString : Input<string> { }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|