Compare commits
	
		
			3 Commits
		
	
	
		
			25606b926b
			...
			edcc8661e3
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					edcc8661e3 | ||
| 
						 | 
					b7db83fe15 | ||
| 
						 | 
					a04a0f07e2 | 
@ -139,11 +139,11 @@ public abstract class FormComponent<T, U> : UIComponent, IFormComponent, IDispos
 | 
			
		||||
	 /// </summary>
 | 
			
		||||
	 public bool HasErrors => HasError || ConversionError || ValidationErrors.Count > 0;
 | 
			
		||||
 | 
			
		||||
	 /// <summary>
 | 
			
		||||
	 /// Return the validation error text or the conversion error message.
 | 
			
		||||
	 /// </summary>
 | 
			
		||||
	 /// <returns>Error text/message</returns>
 | 
			
		||||
	 public string? GetErrorText()
 | 
			
		||||
	/// <summary>
 | 
			
		||||
	/// Return the validation error text or the conversion error message.
 | 
			
		||||
	/// </summary>
 | 
			
		||||
	/// <returns>Error text/message</returns>
 | 
			
		||||
	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,131 +32,135 @@
 | 
			
		||||
                AriaLabel="@AdornmentAriaLabel"
 | 
			
		||||
        />
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@if (Lines > 1)
 | 
			
		||||
	{
 | 
			
		||||
	    <textarea class="@InputClassname"
 | 
			
		||||
                @ref="ElementReference" 
 | 
			
		||||
                rows="@Lines" 
 | 
			
		||||
                @attributes="CustomAttributes"
 | 
			
		||||
                type="@InputTypeString"                  
 | 
			
		||||
                placeholder="@Placeholder" 
 | 
			
		||||
                disabled=@Disabled 
 | 
			
		||||
                readonly="@ReadOnly" 
 | 
			
		||||
                inputmode="@InputMode.ToString()"
 | 
			
		||||
			    @oninput="OnInput" 
 | 
			
		||||
                @onchange="OnChange" 
 | 
			
		||||
                @onblur="@OnBlurred" 
 | 
			
		||||
                @onkeydown="@InvokeKeyDown" 
 | 
			
		||||
                @onkeypress="@InvokeKeyPress" 
 | 
			
		||||
                @onkeyup="@InvokeKeyUp" 
 | 
			
		||||
                @onpaste="@OnPaste" 
 | 
			
		||||
                value="@_internalText" 
 | 
			
		||||
                maxlength="@MaxLength"
 | 
			
		||||
			    @onkeydown:preventDefault="@KeyDownPreventDefault" 
 | 
			
		||||
                @onkeypress:preventDefault="@KeyPressPreventDefault" 
 | 
			
		||||
                @onkeyup:preventDefault="@KeyUpPreventDefault"
 | 
			
		||||
                @onmousewheel="@OnMouseWheel" 
 | 
			
		||||
                @onwheel="@OnMouseWheel"
 | 
			
		||||
                aria-invalid="@HasError.ToString().ToLower()"
 | 
			
		||||
                aria-describedby="@ErrorId"
 | 
			
		||||
        > 
 | 
			
		||||
            @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*@
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
	    <input class="@InputClassname"
 | 
			
		||||
                @ref="ElementReference" 
 | 
			
		||||
                @attributes="CustomAttributes" 
 | 
			
		||||
                type="@InputTypeString"                  
 | 
			
		||||
                value="@_internalText" 
 | 
			
		||||
                @oninput="OnInput" 
 | 
			
		||||
                @onchange="OnChange"
 | 
			
		||||
                placeholder="@Placeholder" 
 | 
			
		||||
                disabled=@Disabled 
 | 
			
		||||
                readonly="@ReadOnly" 
 | 
			
		||||
                @onblur="@OnBlurred" 
 | 
			
		||||
                inputmode="@InputMode.ToString()" 
 | 
			
		||||
                pattern="@Pattern"
 | 
			
		||||
                @onkeydown="@InvokeKeyDown" 
 | 
			
		||||
                @onkeypress="@InvokeKeyPress" 
 | 
			
		||||
                @onkeyup="@InvokeKeyUp" 
 | 
			
		||||
                maxlength="@MaxLength"
 | 
			
		||||
                @onkeydown:preventDefault="KeyDownPreventDefault" 
 | 
			
		||||
                @onkeypress:preventDefault="@KeyPressPreventDefault" 
 | 
			
		||||
                @onkeyup:preventDefault="@KeyUpPreventDefault"
 | 
			
		||||
                @onmousewheel="@OnMouseWheel" 
 | 
			
		||||
                @onwheel="@OnMouseWheel"
 | 
			
		||||
                aria-invalid="@HasError.ToString().ToLower()"
 | 
			
		||||
                aria-describedby="@ErrorId"
 | 
			
		||||
         />
 | 
			
		||||
	     @*Note: double mouse wheel handlers needed for Firefox because it doesn't know onmousewheel*@
 | 
			
		||||
    <InputContent>
 | 
			
		||||
	    @if (Lines > 1)
 | 
			
		||||
	    {
 | 
			
		||||
	        <textarea class="@InputClassname"
 | 
			
		||||
                    @ref="ElementReference" 
 | 
			
		||||
                    rows="@Lines" 
 | 
			
		||||
                    @attributes="CustomAttributes"
 | 
			
		||||
                    type="@InputTypeString"                  
 | 
			
		||||
                    placeholder="@Placeholder" 
 | 
			
		||||
                    disabled=@Disabled 
 | 
			
		||||
                    readonly="@ReadOnly" 
 | 
			
		||||
                    inputmode="@InputMode.ToString()"
 | 
			
		||||
			        @oninput="OnInput" 
 | 
			
		||||
                    @onchange="OnChange" 
 | 
			
		||||
                    @onblur="@OnBlurred" 
 | 
			
		||||
                    @onkeydown="@InvokeKeyDown" 
 | 
			
		||||
                    @onkeypress="@InvokeKeyPress" 
 | 
			
		||||
                    @onkeyup="@InvokeKeyUp" 
 | 
			
		||||
                    @onpaste="@OnPaste" 
 | 
			
		||||
                    value="@_internalText" 
 | 
			
		||||
                    maxlength="@MaxLength"
 | 
			
		||||
			        @onkeydown:preventDefault="@KeyDownPreventDefault" 
 | 
			
		||||
                    @onkeypress:preventDefault="@KeyPressPreventDefault" 
 | 
			
		||||
                    @onkeyup:preventDefault="@KeyUpPreventDefault"
 | 
			
		||||
                    @onmousewheel="@OnMouseWheel" 
 | 
			
		||||
                    @onwheel="@OnMouseWheel"
 | 
			
		||||
                    aria-invalid="@HasError.ToString().ToLower()"
 | 
			
		||||
                    aria-describedby="@ErrorId"
 | 
			
		||||
            > 
 | 
			
		||||
                @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*@
 | 
			
		||||
	    }
 | 
			
		||||
	    else
 | 
			
		||||
	    {
 | 
			
		||||
	        <input class="@InputClassname"
 | 
			
		||||
                    @ref="ElementReference" 
 | 
			
		||||
                    @attributes="CustomAttributes" 
 | 
			
		||||
                    type="@InputTypeString"                  
 | 
			
		||||
                    value="@_internalText" 
 | 
			
		||||
                    @oninput="OnInput" 
 | 
			
		||||
                    @onchange="OnChange"
 | 
			
		||||
                    placeholder="@Placeholder" 
 | 
			
		||||
                    disabled=@Disabled 
 | 
			
		||||
                    readonly="@ReadOnly" 
 | 
			
		||||
                    @onblur="@OnBlurred" 
 | 
			
		||||
                    inputmode="@InputMode.ToString()" 
 | 
			
		||||
                    pattern="@Pattern"
 | 
			
		||||
                    @onkeydown="@InvokeKeyDown" 
 | 
			
		||||
                    @onkeypress="@InvokeKeyPress" 
 | 
			
		||||
                    @onkeyup="@InvokeKeyUp" 
 | 
			
		||||
                    maxlength="@MaxLength"
 | 
			
		||||
                    @onkeydown:preventDefault="KeyDownPreventDefault" 
 | 
			
		||||
                    @onkeypress:preventDefault="@KeyPressPreventDefault" 
 | 
			
		||||
                    @onkeyup:preventDefault="@KeyUpPreventDefault"
 | 
			
		||||
                    @onmousewheel="@OnMouseWheel" 
 | 
			
		||||
                    @onwheel="@OnMouseWheel"
 | 
			
		||||
                    aria-invalid="@HasError.ToString().ToLower()"
 | 
			
		||||
                    aria-describedby="@ErrorId"
 | 
			
		||||
             />
 | 
			
		||||
	         @*Note: double mouse wheel handlers needed for Firefox because it doesn't know onmousewheel*@
 | 
			
		||||
      
 | 
			
		||||
         @if (Disabled) {
 | 
			
		||||
             @*Note: this div must always be there to avoid crashes in WASM, but it is hidden most of the time except if ChildContent should be shown.
 | 
			
		||||
                           In Disabled state the tabindex attribute must NOT be set at all or else it will get focus on click
 | 
			
		||||
             *@
 | 
			
		||||
	        <div class="@InputClassname"
 | 
			
		||||
                style="@("display:"+(InputType == InputType.Hidden && ChildContent != null ? "inline" : "none"))"                  
 | 
			
		||||
                @onblur="@OnBlurred" @ref="@_elementReference1" 
 | 
			
		||||
            >
 | 
			
		||||
                @ChildContent
 | 
			
		||||
            </div>
 | 
			
		||||
        } 
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
             @*Note: this div must always be there to avoid crashes in WASM, but it is hidden most of the time except if ChildContent should be shown.*@
 | 
			
		||||
            <div class="@InputClassname"
 | 
			
		||||
                 style="@("display:"+(InputType == InputType.Hidden && ChildContent != null ? "inline" : "none"))"  
 | 
			
		||||
                 tabindex="@(InputType == InputType.Hidden && ChildContent != null ? 0 : -1)" 
 | 
			
		||||
                 @onblur="@OnBlurred" @ref="@_elementReference1" 
 | 
			
		||||
            >
 | 
			
		||||
                @ChildContent
 | 
			
		||||
            </div>            
 | 
			
		||||
             @if (Disabled) {
 | 
			
		||||
                 @*Note: this div must always be there to avoid crashes in WASM, but it is hidden most of the time except if ChildContent should be shown.
 | 
			
		||||
                               In Disabled state the tabindex attribute must NOT be set at all or else it will get focus on click
 | 
			
		||||
                 *@
 | 
			
		||||
	            <div class="@InputClassname"
 | 
			
		||||
                    style="@("display:"+(InputType == InputType.Hidden && ChildContent != null ? "inline" : "none"))"                  
 | 
			
		||||
                    @onblur="@OnBlurred" @ref="@_elementReference1" 
 | 
			
		||||
                >
 | 
			
		||||
                    @ChildContent
 | 
			
		||||
                </div>
 | 
			
		||||
            } 
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                 @*Note: this div must always be there to avoid crashes in WASM, but it is hidden most of the time except if ChildContent should be shown.*@
 | 
			
		||||
                <div class="@InputClassname"
 | 
			
		||||
                     style="@("display:"+(InputType == InputType.Hidden && ChildContent != null ? "inline" : "none"))"  
 | 
			
		||||
                     tabindex="@(InputType == InputType.Hidden && ChildContent != null ? 0 : -1)" 
 | 
			
		||||
                     @onblur="@OnBlurred" @ref="@_elementReference1" 
 | 
			
		||||
                >
 | 
			
		||||
                    @ChildContent
 | 
			
		||||
                </div>            
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	@if (_showClearable && !Disabled)
 | 
			
		||||
    {
 | 
			
		||||
        <IconButton Class="@ClearButtonClassname"
 | 
			
		||||
                    Color="@ThemeColor.Default" 
 | 
			
		||||
                    Icon="@ClearIcon" 
 | 
			
		||||
                    Size="@Size.Small" 
 | 
			
		||||
                    OnClick="@ClearButtonClickHandlerAsync"
 | 
			
		||||
                    tabindex="-1"
 | 
			
		||||
                />
 | 
			
		||||
	}
 | 
			
		||||
	    @if (_showClearable && !Disabled)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
	@if (Adornment == Adornment.End)
 | 
			
		||||
	{
 | 
			
		||||
	    <InputAdornment Class="@AdornmentClassname" 
 | 
			
		||||
                Icon="@AdornmentIcon" 
 | 
			
		||||
                Color="@AdornmentColor" 
 | 
			
		||||
                Size="@IconSize" 
 | 
			
		||||
                Text="@AdornmentText" 
 | 
			
		||||
                Edge="@Edge.End" 
 | 
			
		||||
                AdornmentClick="@OnAdornmentClick" 
 | 
			
		||||
                AriaLabel="@AdornmentAriaLabel"
 | 
			
		||||
        />
 | 
			
		||||
	}
 | 
			
		||||
        }
 | 
			
		||||
    
 | 
			
		||||
	
 | 
			
		||||
	    @if (Adornment == Adornment.End)
 | 
			
		||||
	    {
 | 
			
		||||
	        <InputAdornment Class="@AdornmentClassname" 
 | 
			
		||||
                    Icon="@AdornmentIcon" 
 | 
			
		||||
                    Color="@AdornmentColor" 
 | 
			
		||||
                    Size="@IconSize" 
 | 
			
		||||
                    Text="@AdornmentText" 
 | 
			
		||||
                    Edge="@Edge.End" 
 | 
			
		||||
                    AdornmentClick="@OnAdornmentClick" 
 | 
			
		||||
                    AriaLabel="@AdornmentAriaLabel"
 | 
			
		||||
            />
 | 
			
		||||
	    }
 | 
			
		||||
 | 
			
		||||
	    @if (Variant == Variant.Outlined)
 | 
			
		||||
	    {
 | 
			
		||||
	        <div class="input-outlined-border"></div>
 | 
			
		||||
	    }
 | 
			
		||||
 | 
			
		||||
	    @if (!HideSpinButtons)
 | 
			
		||||
	    {
 | 
			
		||||
	        <div class="input-numeric-spin">
 | 
			
		||||
                <Button Variant="Variant.Text" @onclick="OnIncrement" Disabled="@(Disabled || ReadOnly)" tabindex="-1">
 | 
			
		||||
                    <Icon Icon="@NumericUpIcon" Size="@GetButtonSize()" />
 | 
			
		||||
                </Button>
 | 
			
		||||
                <Button Variant="Variant.Text" @onclick="OnDecrement" Disabled="@(Disabled || ReadOnly)" tabindex="-1">
 | 
			
		||||
                    <Icon Icon="@NumericDownIcon" Size="@GetButtonSize()" />
 | 
			
		||||
                </Button>
 | 
			
		||||
	        </div>
 | 
			
		||||
	    }
 | 
			
		||||
 | 
			
		||||
        </InputContent>
 | 
			
		||||
        
 | 
			
		||||
    </CascadingValue>  
 | 
			
		||||
   
 | 
			
		||||
    </InputControl>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
	@if (Variant == Variant.Outlined)
 | 
			
		||||
	{
 | 
			
		||||
	    <div class="input-outlined-border"></div>
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@if (!HideSpinButtons)
 | 
			
		||||
	{
 | 
			
		||||
	    <div class="input-numeric-spin">
 | 
			
		||||
            <Button Variant="Variant.Text" @onclick="OnIncrement" Disabled="@(Disabled || ReadOnly)" tabindex="-1">
 | 
			
		||||
                <Icon Icon="@NumericUpIcon" Size="@GetButtonSize()" />
 | 
			
		||||
            </Button>
 | 
			
		||||
            <Button Variant="Variant.Text" @onclick="OnDecrement" Disabled="@(Disabled || ReadOnly)" tabindex="-1">
 | 
			
		||||
                <Icon Icon="@NumericDownIcon" Size="@GetButtonSize()" />
 | 
			
		||||
            </Button>
 | 
			
		||||
	    </div>
 | 
			
		||||
	}
 | 
			
		||||
</div>
 | 
			
		||||
@ -2,12 +2,192 @@
 | 
			
		||||
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>
 | 
			
		||||
{
 | 
			
		||||
   protected string Classname => InputCssHelper.GetClassname(this,
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * 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));
 | 
			
		||||
 | 
			
		||||
   protected string InputClassname => InputCssHelper.GetInputClassname(this);
 | 
			
		||||
@ -31,23 +211,89 @@ public partial class Input<T> : InputBase<T>
 | 
			
		||||
 | 
			
		||||
   protected string InputTypeString => InputType.ToDescription();
 | 
			
		||||
 | 
			
		||||
   protected Task OnInput(ChangeEventArgs args)
 | 
			
		||||
	/*
 | 
			
		||||
 | 
			
		||||
	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);
 | 
			
		||||
   }
 | 
			
		||||
		return SetTextAsync(args?.Value as string);
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
   protected async Task OnChange(ChangeEventArgs args)
 | 
			
		||||
   {
 | 
			
		||||
      _internalText = args?.Value as string;
 | 
			
		||||
      await OnInternalInputChanged.InvokeAsync(args);
 | 
			
		||||
      if (!Immediate)
 | 
			
		||||
      {
 | 
			
		||||
         await SetTextAsync(args?.Value as string);
 | 
			
		||||
      }
 | 
			
		||||
   }
 | 
			
		||||
		if (TextChangeDelay > 0 && _timer != null)
 | 
			
		||||
		{
 | 
			
		||||
			_timer.Stop();
 | 
			
		||||
			await UpdateValuePropertyAsync(false);
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			_internalText = args?.Value as string;
 | 
			
		||||
			await OnInternalInputChanged.InvokeAsync(args);
 | 
			
		||||
			if (!Immediate)
 | 
			
		||||
			{
 | 
			
		||||
				await SetTextAsync(args?.Value as string);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
   /// <summary>
 | 
			
		||||
   /// Paste hook for descendants.
 | 
			
		||||
@ -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)
 | 
			
		||||
   {
 | 
			
		||||
@ -197,14 +443,41 @@ 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)
 | 
			
		||||
		{
 | 
			
		||||
			InputType = InputType.Number;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
   /// <summary>
 | 
			
		||||
   /// Sets the input text from outside programmatically
 | 
			
		||||
   /// </summary>
 | 
			
		||||
   /// <param name="text"></param>
 | 
			
		||||
   /// <returns></returns>
 | 
			
		||||
   public Task SetText(string text)
 | 
			
		||||
	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;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// <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);
 | 
			
		||||
 | 
			
		||||
@ -211,7 +211,7 @@ public abstract class InputBase<T> : FormComponent<T, string>
 | 
			
		||||
 | 
			
		||||
   protected virtual async Task SetTextAsync(string text, bool updateValue = true)
 | 
			
		||||
   {
 | 
			
		||||
      if (Text != text)
 | 
			
		||||
		if (Text != text)
 | 
			
		||||
      {
 | 
			
		||||
         Text = text;
 | 
			
		||||
         if (!string.IsNullOrWhiteSpace(Text))
 | 
			
		||||
@ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user