features/nuget_autobuild
stm 2 years ago
parent 9f1214fd9f
commit a04a0f07e2

@ -143,7 +143,7 @@ public abstract class FormComponent<T, U> : UIComponent, IFormComponent, IDispos
/// Return the validation error text or the conversion error message.
/// </summary>
/// <returns>Error text/message</returns>
public string? GetErrorText()
public string? GetErrorText(bool test = false)
{
// ErrorText is either set from outside or the first validation error
if (!IsNullOrWhiteSpace(ErrorText))
@ -152,6 +152,8 @@ public abstract class FormComponent<T, U> : UIComponent, IFormComponent, IDispos
if (!IsNullOrWhiteSpace(ConversionErrorMessage))
return ConversionErrorMessage;
if (test) return "Error: test";
return null;
}

@ -1,6 +1,5 @@
using System.Timers;
using Connected.Annotations;
using Connected.Utilities.BindingConverters;
using Microsoft.AspNetCore.Components;
namespace Connected.Components;

@ -3,6 +3,23 @@
@inherits InputBase<T>
<div class="@Classname">
<InputControl Label="@Label"
Variant="@Variant"
HelperText="@HelperText"
HelperTextOnFocus="@HelperTextOnFocus"
CounterText="@GetCounterText()"
FullWidth="@FullWidth"
Class="@Classname"
Error="@HasErrors"
ErrorText="@ErrorText"
ErrorId="@ErrorId"
Disabled="@Disabled"
Margin="@Margin"
Required="@Required"
ForId="@FieldId">
<CascadingValue Name="SubscribeToParentForm" Value="@base.SubscribeToParentForm" IsFixed="true">
@if (Adornment == Adornment.Start)
{
<InputAdornment Class="@AdornmentClassname"
@ -15,7 +32,7 @@
AriaLabel="@AdornmentAriaLabel"
/>
}
<InputContent>
@if (Lines > 1)
{
<textarea class="@InputClassname"
@ -46,6 +63,7 @@
>
@Text
</textarea>
@*Note: double mouse wheel handlers needed for Firefox because it doesn't know onmousewheel*@
@*note: the value="@_internalText" is absolutely essential here. the inner html @Text is needed by tests checking it*@
}
@ -104,15 +122,10 @@
@if (_showClearable && !Disabled)
{
<IconButton Class="@ClearButtonClassname"
Color="@ThemeColor.Default"
Icon="@ClearIcon"
Size="@Size.Small"
OnClick="@ClearButtonClickHandlerAsync"
tabindex="-1"
/>
}
@if (Adornment == Adornment.End)
{
<InputAdornment Class="@AdornmentClassname"
@ -142,4 +155,12 @@
</Button>
</div>
}
</InputContent>
</CascadingValue>
</InputControl>
</div>

@ -2,11 +2,191 @@
using Connected.Utilities;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using System.Numerics;
using System.Text.RegularExpressions;
using System.Timers;
namespace Connected.Components;
public partial class Input<T> : InputBase<T>
{
/*
* Debounce
*
*/
/// <summary>
/// The current character counter, displayed below the text field.
/// </summary>
[Parameter] public string CounterText { get; set; }
private System.Timers.Timer _timer;
private double _debounceInterval;
/// <summary>
/// Interval to be awaited in MILLISECONDS before changing the Text value
/// </summary>
[Parameter]
public double TextChangeDelay
{
get => _debounceInterval;
set
{
if (NumericConverter<double>.AreEqual(_debounceInterval, value))
return;
_debounceInterval = value;
if (_debounceInterval == 0)
{
// not debounced, dispose timer if any
ClearTimer(suppressTick: false);
return;
}
SetTimer();
}
}
/// <summary>
/// callback to be called when the debounce interval has elapsed
/// receives the Text as a parameter
/// </summary>
[Parameter] public EventCallback<string> OnDebounceIntervalElapsed { get; set; }
protected Task OnDebounceChange()
{
if (TextChangeDelay > 0 && _timer != null)
{
_timer.Stop();
return base.UpdateValuePropertyAsync(false);
}
return Task.CompletedTask;
}
protected override Task UpdateValuePropertyAsync(bool updateText)
{
// This method is called when Value property needs to be refreshed from the current Text property, so typically because Text property has changed.
// We want to debounce only text-input, not a value being set, so the debouncing is only done when updateText==false (because that indicates the
// change came from a Text setter)
if (updateText)
{
// we have a change coming not from the Text setter, no debouncing is needed
return base.UpdateValuePropertyAsync(updateText);
}
// if debounce interval is 0 we update immediately
if (TextChangeDelay <= 0 || _timer == null)
return base.UpdateValuePropertyAsync(updateText);
// If a debounce interval is defined, we want to delay the update of Value property.
_timer.Stop();
// restart the timer while user is typing
_timer.Start();
return Task.CompletedTask;
}
protected override void OnParametersSet()
{
base.OnParametersSet();
// 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()
{
if (_timer == null)
{
_timer = new System.Timers.Timer();
_timer.Elapsed += OnTimerTick;
_timer.AutoReset = false;
}
_timer.Interval = TextChangeDelay;
}
private void OnTimerTick(object sender, ElapsedEventArgs e)
{
InvokeAsync(OnTimerTickGuiThread).AndForget();
}
private async Task OnTimerTickGuiThread()
{
await base.UpdateValuePropertyAsync(false);
await OnDebounceIntervalElapsed.InvokeAsync(Text);
}
private void ClearTimer(bool suppressTick = false)
{
if (_timer == null)
return;
var wasEnabled = _timer.Enabled;
_timer.Stop();
_timer.Elapsed -= OnTimerTick;
_timer.Dispose();
_timer = null;
if (wasEnabled && !suppressTick)
OnTimerTickGuiThread().AndForget();
}
/*
* Debounce end
*/
protected CssBuilder CompiledHelperContainerClassList
{
get
{
return new CssBuilder("input-control-helper-container")
.AddClass($"px-1", Variant == Variant.Filled)
.AddClass($"px-2", Variant == Variant.Outlined)
.AddClass($"px-1", Variant == Variant.Text)
.AddClass(HelperContainerClassList);
}
}
/// <summary>
/// A space separated list of class names, added on top of the default helper container class list.
/// </summary>
[Parameter]
public string? HelperContainerClassList { get; set; }
protected CssBuilder CompiledHelperClassList
{
get
{
return new CssBuilder("input-helper-text")
.AddClass("input-helper-onfocus", HelperTextOnFocus)
.AddClass(HelperClassList);
}
}
/// <summary>
/// A space separated list of class names, added on top of the default helper class list.
/// </summary>
[Parameter]
public string? HelperClassList { get; set; }
/*protected string HelperContainer =>
new CssBuilder("input-control-helper-container")
.AddClass($"px-1", Variant == Variant.Filled)
.AddClass($"px-2", Variant == Variant.Outlined)
.Build();
protected string HelperClass =>
new CssBuilder("input-helper-text")
.AddClass("input-helper-onfocus", HelperTextOnFocus)
.Build();*/
private string GetCounterText()
{
string result = Text.Length.ToString();
if (string.IsNullOrEmpty(Text)) result = "0";
return result;
}
protected string Classname => InputCssHelper.GetClassname(this,
() => HasNativeHtmlPlaceholder() || !string.IsNullOrEmpty(Text) || Adornment == Adornment.Start || !string.IsNullOrWhiteSpace(Placeholder));
@ -31,15 +211,79 @@ public partial class Input<T> : InputBase<T>
protected string InputTypeString => InputType.ToDescription();
/*
private bool IsDecimalNumber(string type)
{
switch (type.ToLower())
{
case "double":
case "float":
return true;
default:
return false;
}
}
private bool IsNumber(string s)
{
bool result = false;
try
{
double d;
result= double.TryParse(s, out d);
} catch
{
result = false;
}
return result;
}
private string ValidateInput(string value)
{
string result = value;
if (value is not null)
{
var expectedType = typeof(T).Name;
if (IsNumericType(expectedType))
{
if (IsNumber(value.ToString()))
{
result = value.ToString();
}
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)
{
if (!Immediate)
return Task.CompletedTask;
_isFocused = true;
return SetTextAsync(args?.Value as string);
}
protected async Task OnChange(ChangeEventArgs args)
{
if (TextChangeDelay > 0 && _timer != null)
{
_timer.Stop();
await UpdateValuePropertyAsync(false);
}
else
{
_internalText = args?.Value as string;
await OnInternalInputChanged.InvokeAsync(args);
@ -49,6 +293,8 @@ public partial class Input<T> : InputBase<T>
}
}
}
/// <summary>
/// Paste hook for descendants.
/// </summary>
@ -164,12 +410,12 @@ public partial class Input<T> : InputBase<T>
UpdateClearable(Text);
}
protected override async Task UpdateValuePropertyAsync(bool updateText)
/*protected override async Task UpdateValuePropertyAsync(bool updateText)
{
await base.UpdateValuePropertyAsync(updateText);
if (Clearable)
UpdateClearable(Value);
}
}*/
protected virtual async Task ClearButtonClickHandlerAsync(MouseEventArgs e)
{
@ -196,6 +442,33 @@ public partial class Input<T> : InputBase<T>
{
// in WASM (or in BSS with TextUpdateSuppression==false) we always update
_internalText = Text;
}
string baseTypeName = typeof(T).Name;
if (IsNumericType(baseTypeName))
{
InputType = InputType.Number;
}
}
private bool IsNumericType(string type)
{
switch (type.ToLower())
{
case "uint16":
case "uint32":
case "uint64":
case "int16":
case "int32":
case "int64":
case "int":
case "double":
case "decimal":
case "float":
return true;
default:
return false;
}
}

@ -220,6 +220,7 @@ public abstract class InputBase<T> : FormComponent<T, string>
await UpdateValuePropertyAsync(false);
await TextChanged.InvokeAsync(Text);
}
}
/// <summary>
@ -329,7 +330,11 @@ public abstract class InputBase<T> : FormComponent<T, string>
/// Fired when the Value property changes.
/// </summary>
[Parameter]
public EventCallback<T> ValueChanged { get; set; }
public EventCallback<T> ValueChanged
{
get;
set;
}
/// <summary>
/// The value of this input element.

@ -387,7 +387,7 @@ public partial class Picker<T> : FormComponent<T, string>
protected override void ResetValue()
{
_inputReference?.Reset();
_inputReference?.InputReference.Reset();
base.ResetValue();
}

@ -1,15 +1,15 @@
@namespace Connected.Components
@typeparam T
@inherits DebouncedInput<T>
@inherits InputBase<T>
<CascadingValue Name="SubscribeToParentForm" Value="@SubscribeToParentForm" IsFixed="true">
<CascadingValue Name="SubscribeToParentForm" Value="@base.SubscribeToParentForm" IsFixed="true">
<InputControl Label="@Label"
Variant="@Variant"
HelperText="@HelperText"
HelperTextOnFocus="@HelperTextOnFocus"
CounterText="@GetCounterText()"
FullWidth="@FullWidth"
Class="@ClassList"
class="@ClassList"
Error="@HasErrors"
ErrorText="@GetErrorText()"
ErrorId="@ErrorId"
@ -48,7 +48,6 @@
Margin="@Margin"
OnBlur="@OnBlurred"
OnKeyDown="@InvokeKeyDown"
OnInternalInputChanged="OnChange"
OnKeyPress="@InvokeKeyPress"
OnKeyUp="@InvokeKeyUp"
KeyDownPreventDefault="KeyDownPreventDefault"
@ -57,6 +56,7 @@
HideSpinButtons="true"
Clearable="@Clearable"
OnClearButtonClick="@OnClearButtonClick"
Class="@CompiledClassList.Build()"
Pattern="@Pattern"/>
}
else
@ -83,8 +83,10 @@
OnAdornmentClick="@OnAdornmentClick"
Error="@HasError"
Immediate="@Immediate"
Margin="@Margin" OnBlur="@OnBlurred"
Margin="@Margin"
OnBlur="@OnBlurred"
Clearable="@Clearable"
Class="@CompiledClassList.Build()"
OnClearButtonClick="@OnClearButtonClick"/>
}
</CascadingValue>

@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Components.Web;
namespace Connected.Components;
public partial class TextField<T> : DebouncedInput<T>
public partial class TextField<T> : InputBase<T>
{
private Mask? _maskReference;
@ -19,7 +19,7 @@ public partial class TextField<T> : DebouncedInput<T>
internal override InputType GetInputType() => InputType;
private string GetCounterText() => Counter == null ? string.Empty : (Counter == 0 ? (string.IsNullOrEmpty(Text) ? "0" : $"{Text.Length}") : ((string.IsNullOrEmpty(Text) ? "0" : $"{Text.Length}") + $" / {Counter}"));
private string GetCounterText() => base.Counter == null ? string.Empty : (base.Counter == 0 ? (string.IsNullOrEmpty(base.Text) ? "0" : $"{base.Text.Length}") : ((string.IsNullOrEmpty(base.Text) ? "0" : $"{base.Text.Length}") + $" / {base.Counter}"));
/// <summary>
/// Show clear button.
@ -32,10 +32,25 @@ public partial class TextField<T> : DebouncedInput<T>
/// </summary>
[Parameter] public EventCallback<MouseEventArgs> OnClearButtonClick { get; set; }
protected string ClassList =>
/*protected string ClassList =>
new CssBuilder("input-input-control")
.AddClass(base.AdditionalClassList)
.Build();
.Build();*/
protected virtual CssBuilder CompiledClassList
{
get
{
return new CssBuilder("input-input-control")
.AddClass(ClassList);
}
}
/// <summary>
/// A space separated list of class names, added on top of the default class list.
/// </summary>
[Parameter]
public string? ClassList { get; set; }
public override ValueTask FocusAsync()
{
@ -128,10 +143,10 @@ public partial class TextField<T> : DebouncedInput<T>
{
if (_mask != null)
{
var textValue = Converter.Convert(value);
var textValue = base.Converter.Convert(value);
_mask.SetText(textValue);
textValue = Mask.GetCleanText();
value = Converter.ConvertBack(textValue);
value = base.Converter.ConvertBack(textValue);
}
return base.SetValueAsync(value, updateText);
}

Loading…
Cancel
Save