You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
489 lines
14 KiB
489 lines
14 KiB
using System.Globalization;
|
|
using Connected.Annotations;
|
|
using Connected.Utilities;
|
|
using Microsoft.AspNetCore.Components;
|
|
using Microsoft.AspNetCore.Components.Web;
|
|
|
|
namespace Connected.Components;
|
|
|
|
public abstract class InputBase<T> : FormComponent<T, string>
|
|
{
|
|
private bool _isDirty;
|
|
|
|
protected InputBase() : base(new DefaultConverter<T>()) { }
|
|
|
|
/// <summary>
|
|
/// If true, the input element will be disabled.
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Behavior)]
|
|
public bool Disabled { get; set; }
|
|
|
|
/// <summary>
|
|
/// If true, the input will be read-only.
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Behavior)]
|
|
public bool ReadOnly { get; set; }
|
|
|
|
/// <summary>
|
|
/// If true, the input will take up the full width of its container.
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Appearance)]
|
|
public bool FullWidth { get; set; }
|
|
|
|
/// <summary>
|
|
/// If true, the input will update the Value immediately on typing.
|
|
/// If false, the Value is updated only on Enter.
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Behavior)]
|
|
public bool Immediate { get; set; }
|
|
|
|
/// <summary>
|
|
/// If true, the input will not have an underline.
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Appearance)]
|
|
public bool DisableUnderLine { get; set; }
|
|
|
|
/// <summary>
|
|
/// The HelperText will be displayed below the text field.
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Behavior)]
|
|
public string HelperText { get; set; }
|
|
|
|
/// <summary>
|
|
/// If true, the helper text will only be visible on focus.
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Behavior)]
|
|
public bool HelperTextOnFocus { get; set; }
|
|
|
|
/// <summary>
|
|
/// Icon that will be used if Adornment is set to Start or End.
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Behavior)]
|
|
public string AdornmentIcon { get; set; }
|
|
|
|
/// <summary>
|
|
/// Text that will be used if Adornment is set to Start or End, the Text overrides Icon.
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Behavior)]
|
|
public string AdornmentText { get; set; }
|
|
|
|
/// <summary>
|
|
/// The Adornment if used. By default, it is set to None.
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Behavior)]
|
|
public Adornment Adornment { get; set; } = Adornment.None;
|
|
|
|
/// <summary>
|
|
/// The validation is only triggered if the user has changed the input value at least once. By default, it is false
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Behavior)]
|
|
public bool OnlyValidateIfDirty { get; set; } = false;
|
|
|
|
/// <summary>
|
|
/// The color of the adornment if used. It supports the theme colors.
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Appearance)]
|
|
public ThemeColor AdornmentColor { get; set; } = ThemeColor.Default;
|
|
|
|
/// <summary>
|
|
/// The aria-label of the adornment.
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Appearance)]
|
|
public string AdornmentAriaLabel { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// The Icon Size.
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Appearance)]
|
|
public Size IconSize { get; set; } = Size.Medium;
|
|
|
|
/// <summary>
|
|
/// Button click event if set and Adornment used.
|
|
/// </summary>
|
|
[Parameter] public EventCallback<MouseEventArgs> OnAdornmentClick { get; set; }
|
|
|
|
/// <summary>
|
|
/// Variant to use.
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Appearance)]
|
|
public Variant Variant { get; set; } = Variant.Text;
|
|
|
|
/// <summary>
|
|
/// Will adjust vertical spacing.
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Appearance)]
|
|
public Margin Margin { get; set; } = Margin.None;
|
|
|
|
/// <summary>
|
|
/// The short hint displayed in the input before the user enters a value.
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Behavior)]
|
|
public string Placeholder { get; set; }
|
|
|
|
/// <summary>
|
|
/// If set, will display the counter, value 0 will display current count but no stop count.
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Validation)]
|
|
public int? Counter { get; set; }
|
|
|
|
/// <summary>
|
|
/// Maximum number of characters that the input will accept
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Validation)]
|
|
public int MaxLength { get; set; } = 524288;
|
|
|
|
/// <summary>
|
|
/// If string has value the label text will be displayed in the input, and scaled down at the top if the input has value.
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Behavior)]
|
|
public string Label { get; set; }
|
|
|
|
/// <summary>
|
|
/// If true the input will focus automatically.
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Behavior)]
|
|
public bool AutoFocus { get; set; }
|
|
|
|
/// <summary>
|
|
/// A multiline input (textarea) will be shown, if set to more than one line.
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Behavior)]
|
|
public int Lines { get; set; } = 1;
|
|
|
|
/// <summary>
|
|
/// The text to be displayed.
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Data)]
|
|
public string Text { get; set; }
|
|
|
|
/// <summary>
|
|
/// When TextUpdateSuppression is true (which is default) the text can not be updated by bindings while the component is focused in BSS (not WASM).
|
|
/// This solves issue #1012: Textfield swallowing chars when typing rapidly
|
|
/// If you need to update the input's text while it is focused you can set this parameter to false.
|
|
/// Note: on WASM text update suppression is not active, so this parameter has no effect.
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Behavior)]
|
|
public bool TextUpdateSuppression { get; set; } = true;
|
|
|
|
/// <summary>
|
|
/// Hints at the type of data that might be entered by the user while editing the input
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Behavior)]
|
|
public virtual InputMode InputMode { get; set; } = InputMode.text;
|
|
|
|
/// <summary>
|
|
/// The pattern attribute, when specified, is a regular expression which the input's value must match in order for the value to pass constraint validation. It must be a valid JavaScript regular expression
|
|
/// Not Supported in multline input
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Validation)]
|
|
public virtual string Pattern { get; set; }
|
|
|
|
/// <summary>
|
|
/// Derived classes need to override this if they can be something other than text
|
|
/// </summary>
|
|
internal virtual InputType GetInputType() { return InputType.Text; }
|
|
|
|
protected virtual async Task SetTextAsync(string text, bool updateValue = true)
|
|
{
|
|
if (Text != text)
|
|
{
|
|
Text = text;
|
|
if (!string.IsNullOrWhiteSpace(Text))
|
|
Modified = true;
|
|
if (updateValue)
|
|
await UpdateValuePropertyAsync(false);
|
|
await TextChanged.InvokeAsync(Text);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Text change hook for descendants. Called when Text needs to be refreshed from current Value property.
|
|
/// </summary>
|
|
protected virtual Task UpdateTextPropertyAsync(bool updateValue)
|
|
{
|
|
return SetTextAsync(Converter.Convert(Value), updateValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Focuses the element
|
|
/// </summary>
|
|
/// <returns>The ValueTask</returns>
|
|
public virtual ValueTask FocusAsync() { return new ValueTask(); }
|
|
|
|
public virtual ValueTask BlurAsync() { return new ValueTask(); }
|
|
|
|
public virtual ValueTask SelectAsync() { return new ValueTask(); }
|
|
|
|
public virtual ValueTask SelectRangeAsync(int pos1, int pos2) { return new ValueTask(); }
|
|
|
|
/// <summary>
|
|
/// Fired when the text value changes.
|
|
/// </summary>
|
|
[Parameter] public EventCallback<string> TextChanged { get; set; }
|
|
|
|
/// <summary>
|
|
/// Fired when the element loses focus.
|
|
/// </summary>
|
|
[Parameter] public EventCallback<FocusEventArgs> OnBlur { get; set; }
|
|
|
|
/// <summary>
|
|
/// Fired when the element changes internally its text value.
|
|
/// </summary>
|
|
[Parameter]
|
|
public EventCallback<ChangeEventArgs> OnInternalInputChanged { get; set; }
|
|
|
|
protected bool _isFocused;
|
|
|
|
protected internal virtual void OnBlurred(FocusEventArgs obj)
|
|
{
|
|
_isFocused = false;
|
|
|
|
if (!OnlyValidateIfDirty || _isDirty)
|
|
{
|
|
Modified = true;
|
|
BeginValidateAfter(OnBlur.InvokeAsync(obj));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fired on the KeyDown event.
|
|
/// </summary>
|
|
[Parameter] public EventCallback<KeyboardEventArgs> OnKeyDown { get; set; }
|
|
|
|
protected virtual void InvokeKeyDown(KeyboardEventArgs obj)
|
|
{
|
|
_isFocused = true;
|
|
OnKeyDown.InvokeAsync(obj).AndForget();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prevent the default action for the KeyDown event.
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Behavior)]
|
|
public bool KeyDownPreventDefault { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
/// Fired on the KeyPress event.
|
|
/// </summary>
|
|
[Parameter] public EventCallback<KeyboardEventArgs> OnKeyPress { get; set; }
|
|
|
|
protected virtual void InvokeKeyPress(KeyboardEventArgs obj)
|
|
{
|
|
OnKeyPress.InvokeAsync(obj).AndForget();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prevent the default action for the KeyPress event.
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Behavior)]
|
|
public bool KeyPressPreventDefault { get; set; }
|
|
|
|
/// <summary>
|
|
/// Fired on the KeyUp event.
|
|
/// </summary>
|
|
[Parameter] public EventCallback<KeyboardEventArgs> OnKeyUp { get; set; }
|
|
|
|
protected virtual void InvokeKeyUp(KeyboardEventArgs obj)
|
|
{
|
|
_isFocused = true;
|
|
OnKeyUp.InvokeAsync(obj).AndForget();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prevent the default action for the KeyUp event.
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Behavior)]
|
|
public bool KeyUpPreventDefault { get; set; }
|
|
|
|
/// <summary>
|
|
/// Fired when the Value property changes.
|
|
/// </summary>
|
|
[Parameter]
|
|
public EventCallback<T> ValueChanged { get; set; }
|
|
|
|
/// <summary>
|
|
/// The value of this input element.
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Data)]
|
|
public T Value
|
|
{
|
|
get => _value;
|
|
set => _value = value;
|
|
}
|
|
|
|
protected virtual async Task SetValueAsync(T value, bool updateText = true)
|
|
{
|
|
if (!EqualityComparer<T>.Default.Equals(Value, value))
|
|
{
|
|
_isDirty = true;
|
|
Value = value;
|
|
if (updateText)
|
|
await UpdateTextPropertyAsync(false);
|
|
await ValueChanged.InvokeAsync(Value);
|
|
BeginValidate();
|
|
FieldChanged(Value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Value change hook for descendants. Called when Value needs to be refreshed from current Text property.
|
|
/// </summary>
|
|
protected virtual Task UpdateValuePropertyAsync(bool updateText)
|
|
{
|
|
return SetValueAsync(Converter.ConvertBack(Text), updateText);
|
|
}
|
|
|
|
protected override bool SetConverter(Converter<T, string> value)
|
|
{
|
|
var changed = base.SetConverter(value);
|
|
if (changed)
|
|
UpdateTextPropertyAsync(false).AndForget(); // refresh only Text property from current Value
|
|
|
|
return changed;
|
|
}
|
|
|
|
protected override bool SetCulture(CultureInfo value)
|
|
{
|
|
var changed = base.SetCulture(value);
|
|
if (changed)
|
|
UpdateTextPropertyAsync(false).AndForget(); // refresh only Text property from current Value
|
|
|
|
return changed;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Conversion format parameter for ToString(), can be used for formatting primitive types, DateTimes and TimeSpans
|
|
/// </summary>
|
|
[Parameter]
|
|
[Category(CategoryTypes.FormComponent.Behavior)]
|
|
public string Format
|
|
{
|
|
get => ((ToStringConverter<T>)Converter).Format;
|
|
set => SetFormat(value);
|
|
}
|
|
|
|
protected virtual bool SetFormat(string value)
|
|
{
|
|
var changed = Format != value;
|
|
if (changed)
|
|
{
|
|
((ToStringConverter<T>)Converter).Format = value;
|
|
UpdateTextPropertyAsync(false).AndForget(); // refresh only Text property from current Value
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
protected override Task ValidateValue()
|
|
{
|
|
if (SubscribeToParentForm)
|
|
return base.ValidateValue();
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
await base.OnInitializedAsync();
|
|
|
|
// Because the way the Value setter is built, it won't cause an update if the incoming Value is
|
|
// equal to the initial value. This is why we force an update to the Text property here.
|
|
if (typeof(T) != typeof(string))
|
|
await UpdateTextPropertyAsync(false);
|
|
|
|
if (Label == null && For != null)
|
|
Label = For.GetLabelString();
|
|
}
|
|
|
|
public virtual void ForceRender(bool forceTextUpdate)
|
|
{
|
|
_forceTextUpdate = true;
|
|
UpdateTextPropertyAsync(false).AndForget();
|
|
StateHasChanged();
|
|
}
|
|
|
|
protected bool _forceTextUpdate;
|
|
|
|
public override async Task SetParametersAsync(ParameterView parameters)
|
|
{
|
|
await base.SetParametersAsync(parameters);
|
|
|
|
var hasText = parameters.Contains<string>(nameof(Text));
|
|
var hasValue = parameters.Contains<T>(nameof(Value));
|
|
|
|
// Refresh Value from Text
|
|
if (hasText && !hasValue)
|
|
await UpdateValuePropertyAsync(false);
|
|
|
|
// Refresh Text from Value
|
|
if (hasValue && !hasText)
|
|
{
|
|
var updateText = true;
|
|
if (_isFocused && !_forceTextUpdate)
|
|
{
|
|
// Text update suppression, only in BSS (not in WASM).
|
|
// This is a fix for #1012
|
|
if (RuntimeLocation.IsServerSide && TextUpdateSuppression)
|
|
updateText = false;
|
|
}
|
|
if (updateText)
|
|
{
|
|
_forceTextUpdate = false;
|
|
await UpdateTextPropertyAsync(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
//Only focus automatically after the first render cycle!
|
|
if (firstRender && AutoFocus)
|
|
{
|
|
await FocusAsync();
|
|
}
|
|
}
|
|
|
|
protected override void OnParametersSet()
|
|
{
|
|
if (SubscribeToParentForm)
|
|
base.OnParametersSet();
|
|
}
|
|
|
|
protected override void ResetValue()
|
|
{
|
|
SetTextAsync(null, updateValue: true).AndForget();
|
|
_isDirty = false;
|
|
base.ResetValue();
|
|
}
|
|
}
|