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

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

@ -3,6 +3,23 @@
@inherits InputBase<T> @inherits InputBase<T>
<div class="@Classname"> <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) @if (Adornment == Adornment.Start)
{ {
<InputAdornment Class="@AdornmentClassname" <InputAdornment Class="@AdornmentClassname"
@ -15,7 +32,7 @@
AriaLabel="@AdornmentAriaLabel" AriaLabel="@AdornmentAriaLabel"
/> />
} }
<InputContent>
@if (Lines > 1) @if (Lines > 1)
{ {
<textarea class="@InputClassname" <textarea class="@InputClassname"
@ -46,6 +63,7 @@
> >
@Text @Text
</textarea> </textarea>
@*Note: double mouse wheel handlers needed for Firefox because it doesn't know onmousewheel*@ @*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*@ @*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) @if (_showClearable && !Disabled)
{ {
<IconButton Class="@ClearButtonClassname"
Color="@ThemeColor.Default"
Icon="@ClearIcon"
Size="@Size.Small"
OnClick="@ClearButtonClickHandlerAsync"
tabindex="-1"
/>
} }
@if (Adornment == Adornment.End) @if (Adornment == Adornment.End)
{ {
<InputAdornment Class="@AdornmentClassname" <InputAdornment Class="@AdornmentClassname"
@ -142,4 +155,12 @@
</Button> </Button>
</div> </div>
} }
</InputContent>
</CascadingValue>
</InputControl>
</div> </div>

@ -2,11 +2,191 @@
using Connected.Utilities; using Connected.Utilities;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.Web;
using System.Numerics;
using System.Text.RegularExpressions;
using System.Timers;
namespace Connected.Components; namespace Connected.Components;
public partial class Input<T> : InputBase<T> 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, protected string Classname => InputCssHelper.GetClassname(this,
() => HasNativeHtmlPlaceholder() || !string.IsNullOrEmpty(Text) || Adornment == Adornment.Start || !string.IsNullOrWhiteSpace(Placeholder)); () => 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(); 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) protected Task OnInput(ChangeEventArgs args)
{ {
if (!Immediate) if (!Immediate)
return Task.CompletedTask; return Task.CompletedTask;
_isFocused = true; _isFocused = true;
return SetTextAsync(args?.Value as string); return SetTextAsync(args?.Value as string);
} }
protected async Task OnChange(ChangeEventArgs args) protected async Task OnChange(ChangeEventArgs args)
{
if (TextChangeDelay > 0 && _timer != null)
{
_timer.Stop();
await UpdateValuePropertyAsync(false);
}
else
{ {
_internalText = args?.Value as string; _internalText = args?.Value as string;
await OnInternalInputChanged.InvokeAsync(args); await OnInternalInputChanged.InvokeAsync(args);
@ -49,6 +293,8 @@ public partial class Input<T> : InputBase<T>
} }
} }
}
/// <summary> /// <summary>
/// Paste hook for descendants. /// Paste hook for descendants.
/// </summary> /// </summary>
@ -164,12 +410,12 @@ public partial class Input<T> : InputBase<T>
UpdateClearable(Text); UpdateClearable(Text);
} }
protected override async Task UpdateValuePropertyAsync(bool updateText) /*protected override async Task UpdateValuePropertyAsync(bool updateText)
{ {
await base.UpdateValuePropertyAsync(updateText); await base.UpdateValuePropertyAsync(updateText);
if (Clearable) if (Clearable)
UpdateClearable(Value); UpdateClearable(Value);
} }*/
protected virtual async Task ClearButtonClickHandlerAsync(MouseEventArgs e) 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 // in WASM (or in BSS with TextUpdateSuppression==false) we always update
_internalText = Text; _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 UpdateValuePropertyAsync(false);
await TextChanged.InvokeAsync(Text); await TextChanged.InvokeAsync(Text);
} }
} }
/// <summary> /// <summary>
@ -329,7 +330,11 @@ public abstract class InputBase<T> : FormComponent<T, string>
/// Fired when the Value property changes. /// Fired when the Value property changes.
/// </summary> /// </summary>
[Parameter] [Parameter]
public EventCallback<T> ValueChanged { get; set; } public EventCallback<T> ValueChanged
{
get;
set;
}
/// <summary> /// <summary>
/// The value of this input element. /// The value of this input element.

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

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

@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Components.Web;
namespace Connected.Components; namespace Connected.Components;
public partial class TextField<T> : DebouncedInput<T> public partial class TextField<T> : InputBase<T>
{ {
private Mask? _maskReference; private Mask? _maskReference;
@ -19,7 +19,7 @@ public partial class TextField<T> : DebouncedInput<T>
internal override InputType GetInputType() => InputType; 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> /// <summary>
/// Show clear button. /// Show clear button.
@ -32,10 +32,25 @@ public partial class TextField<T> : DebouncedInput<T>
/// </summary> /// </summary>
[Parameter] public EventCallback<MouseEventArgs> OnClearButtonClick { get; set; } [Parameter] public EventCallback<MouseEventArgs> OnClearButtonClick { get; set; }
protected string ClassList => /*protected string ClassList =>
new CssBuilder("input-input-control") new CssBuilder("input-input-control")
.AddClass(base.AdditionalClassList) .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() public override ValueTask FocusAsync()
{ {
@ -128,10 +143,10 @@ public partial class TextField<T> : DebouncedInput<T>
{ {
if (_mask != null) if (_mask != null)
{ {
var textValue = Converter.Convert(value); var textValue = base.Converter.Convert(value);
_mask.SetText(textValue); _mask.SetText(textValue);
textValue = Mask.GetCleanText(); textValue = Mask.GetCleanText();
value = Converter.ConvertBack(textValue); value = base.Converter.ConvertBack(textValue);
} }
return base.SetValueAsync(value, updateText); return base.SetValueAsync(value, updateText);
} }

Loading…
Cancel
Save