[WIP] Refactor components to match converter refactor

pull/2/head
Matija Koželj 2 years ago
parent f3ae953772
commit 6f91dacb0c

@ -5,14 +5,14 @@
<CascadingValue Name="SubscribeToParentForm" Value="false" IsFixed="true"> <CascadingValue Name="SubscribeToParentForm" Value="false" IsFixed="true">
<div class="@AutocompleteClassList"> <div class="@AutocompleteClassList">
<InputControl Label="@Label" Variant="@Variant" HelperText="@HelperText" HelperTextOnFocus="@HelperTextOnFocus" FullWidth="@FullWidth" Margin="@Margin" Class="@ClassList()" Style="@Style" <InputControl Label="@Label" Variant="@Variant" HelperText="@HelperText" HelperTextOnFocus="@HelperTextOnFocus" FullWidth="@FullWidth" Margin="@Margin" Class="@ClassList()" Style="@Style"
Error="@Error" ErrorText="@ErrorText" Disabled="@Disabled" @onclick="@ToggleMenu" Required="@Required" ForId="@FieldId"> Error="@HasError" ErrorText="@ErrorText" Disabled="@Disabled" @onclick="@ToggleMenu" Required="@Required" ForId="@FieldId">
<InputContent> <InputContent>
<Input @ref="_elementReference" @key="_elementKey" InputType="InputType.Text" <Input @ref="_elementReference" @key="_elementKey" InputType="InputType.Text"
Class="select-input" Margin="@Margin" Class="select-input" Margin="@Margin"
Variant="@Variant" Variant="@Variant"
TextUpdateSuppression="@TextUpdateSuppression" TextUpdateSuppression="@TextUpdateSuppression"
Value="@Text" DisableUnderLine="@DisableUnderLine" Value="@Text" DisableUnderLine="@DisableUnderLine"
Disabled="@Disabled" ReadOnly="@ReadOnly" Error="@Error" Disabled="@Disabled" ReadOnly="@ReadOnly" Error="@HasError"
OnAdornmentClick="@OnAdornmentClick" AdornmentIcon="@CurrentIcon" Adornment="@Adornment" AdornmentColor="@AdornmentColor" IconSize="@IconSize" AdornmentText="@AdornmentText" OnAdornmentClick="@OnAdornmentClick" AdornmentIcon="@CurrentIcon" Adornment="@Adornment" AdornmentColor="@AdornmentColor" IconSize="@IconSize" AdornmentText="@AdornmentText"
Clearable="@Clearable" OnClearButtonClick="@OnClearButtonClick" Clearable="@Clearable" OnClearButtonClick="@OnClearButtonClick"
@attributes="UserAttributes" @attributes="UserAttributes"

@ -83,10 +83,7 @@ public partial class Autocomplete<T> : InputBase<T>, IDisposable
_toStringFunc = value; _toStringFunc = value;
Converter = new Converter<T> Converter = new LambdaConverter<T, string>(_toStringFunc ?? (x => x?.ToString()), null);
{
SetFunc = _toStringFunc ?? (x => x?.ToString()),
};
} }
} }
/// <summary> /// <summary>
@ -461,7 +458,7 @@ public partial class Autocomplete<T> : InputBase<T>, IDisposable
return string.Empty; return string.Empty;
try try
{ {
return Converter.Set(item); return Converter.Convert(item);
} }
catch (NullReferenceException) { } catch (NullReferenceException) { }
@ -628,7 +625,7 @@ public partial class Autocomplete<T> : InputBase<T>, IDisposable
_timer?.Dispose(); _timer?.Dispose();
var value = Converter.Get(Text); var value = Converter.ConvertBack(Text);
return SetValueAsync(value, updateText: false); return SetValueAsync(value, updateText: false);
} }

@ -44,17 +44,17 @@ public class BooleanInput<T> : FormComponent<T, bool?>
/// </summary> /// </summary>
[Parameter] public EventCallback<T> CheckedChanged { get; set; } [Parameter] public EventCallback<T> CheckedChanged { get; set; }
protected bool? BoolValue => Converter.Set(Checked); protected bool? BoolValue => Converter.Convert(Checked);
protected virtual Task OnChange(ChangeEventArgs args) protected virtual Task OnChange(ChangeEventArgs args)
{ {
Touched = true; Modified = true;
return SetBoolValueAsync((bool?)args.Value); return SetBoolValueAsync((bool?)args.Value);
} }
protected Task SetBoolValueAsync(bool? value) protected Task SetBoolValueAsync(bool? value)
{ {
return SetCheckedAsync(Converter.Get(value)); return SetCheckedAsync(Converter.ConvertBack(value));
} }
protected async Task SetCheckedAsync(T value) protected async Task SetCheckedAsync(T value)
@ -74,7 +74,7 @@ public class BooleanInput<T> : FormComponent<T, bool?>
{ {
var changed = base.SetConverter(value); var changed = base.SetConverter(value);
if (changed) if (changed)
SetBoolValueAsync(Converter.Set(Checked)).AndForget(); SetBoolValueAsync(Converter.Convert(Checked)).AndForget();
return changed; return changed;
} }

@ -139,7 +139,7 @@ public partial class CheckBox<T> : BooleanInput<T>
protected override Task OnChange(ChangeEventArgs args) protected override Task OnChange(ChangeEventArgs args)
{ {
Touched = true; Modified = true;
// Apply only when TriState parameter is set to true and T is bool? // Apply only when TriState parameter is set to true and T is bool?
if (TriState && typeof(T) == typeof(bool?)) if (TriState && typeof(T) == typeof(bool?))

@ -116,11 +116,7 @@ public partial class DataGrid<T> : UIComponent
internal bool isEditFormOpen; internal bool isEditFormOpen;
// converters // converters
private Converter<bool, bool?> _oppositeBoolConverter = new Converter<bool, bool?> private Converter<bool, bool?> _oppositeBoolConverter = new LambdaConverter<bool, bool?>(value => value ? false : true, value => value.HasValue ? !value.Value : true);
{
SetFunc = value => value ? false : true,
GetFunc = value => value.HasValue ? !value.Value : true,
};
#region Notify Children Delegates #region Notify Children Delegates

@ -30,7 +30,7 @@ public class DatePicker : DatePickerBase
{ {
if (_value != date) if (_value != date)
{ {
Touched = true; Modified = true;
if (date is not null && IsDateDisabledFunc(date.Value.Date)) if (date is not null && IsDateDisabledFunc(date.Value.Date))
{ {
@ -40,10 +40,8 @@ public class DatePicker : DatePickerBase
_value = date; _value = date;
if (updateValue) if (updateValue)
{ await SetTextAsync(Converter.Convert(_value), false);
Converter.GetError = false;
await SetTextAsync(Converter.Set(_value), false);
}
await DateChanged.InvokeAsync(_value); await DateChanged.InvokeAsync(_value);
BeginValidate(); BeginValidate();
FieldChanged(_value); FieldChanged(_value);
@ -52,15 +50,15 @@ public class DatePicker : DatePickerBase
protected override Task DateFormatChanged(string newFormat) protected override Task DateFormatChanged(string newFormat)
{ {
Touched = true; Modified = true;
return SetTextAsync(Converter.Set(_value), false); return SetTextAsync(Converter.Convert(_value), false);
} }
protected override Task StringValueChanged(string value) protected override Task StringValueChanged(string value)
{ {
Touched = true; Modified = true;
// Update the date property (without updating back the Value property) // Update the date property (without updating back the Value property)
return SetDateAsync(Converter.Get(value), false); return SetDateAsync(Converter.ConvertBack(value), false);
} }
protected override string GetDayClasses(int month, DateTime day) protected override string GetDayClasses(int month, DateTime day)

@ -230,7 +230,7 @@ public abstract partial class DatePickerBase : Picker<DateTime?>
base.OnPickerOpened(); base.OnPickerOpened();
if (Editable == true && Text != null) if (Editable == true && Text != null)
{ {
DateTime? a = Converter.Get(Text); DateTime? a = Converter.ConvertBack(Text);
if (a.HasValue) if (a.HasValue)
{ {
a = new DateTime(a.Value.Year, a.Value.Month, 1); a = new DateTime(a.Value.Year, a.Value.Month, 1);

@ -1,4 +1,6 @@
namespace Connected.Components; using Connected.Extensions;
namespace Connected.Components;
public class DateRange : Range<DateTime?>, IEquatable<DateRange> public class DateRange : Range<DateTime?>, IEquatable<DateRange>
{ {
@ -15,7 +17,7 @@ public class DateRange : Range<DateTime?>, IEquatable<DateRange>
if (Start == null || End == null) if (Start == null || End == null)
return string.Empty; return string.Empty;
return RangeConverter<DateTime>.Join(converter.Set(Start.Value), converter.Set(End.Value)); return RangeConverter<DateTime>.Join(converter.Convert(Start.Value), converter.Convert(End.Value));
} }
public string ToIsoDateString() public string ToIsoDateString()
@ -40,12 +42,14 @@ public class DateRange : Range<DateTime?>, IEquatable<DateRange>
{ {
date = null; date = null;
var endDate = converter.Get(end); var endDate = converter.ConvertBack(end);
if (converter.GetError)
if (endDate is null)
return false; return false;
var startDate = converter.Get(start); var startDate = converter.ConvertBack(start);
if (converter.GetError)
if (startDate is null)
return false; return false;
date = new DateRange(startDate, endDate); date = new DateRange(startDate, endDate);

@ -6,13 +6,13 @@
@code{ @code{
protected override RenderFragment InputContent=> protected override RenderFragment InputContent=>
@<InputControl Label="@Label" Variant="@Variant" HelperText="@HelperText" Error="@Error" @<InputControl Label="@Label" Variant="@Variant" HelperText="@HelperText" Error="@HasError"
ErrorText="@ErrorText" Disabled="@Disabled" Margin="@Margin" Required="@Required" ErrorText="@ErrorText" Disabled="@Disabled" Margin="@Margin" Required="@Required"
@onclick="() => { if (!Editable) ToggleState(); }" ForId="@FieldId"> @onclick="() => { if (!Editable) ToggleState(); }" ForId="@FieldId">
<InputContent> <InputContent>
<RangeInput @ref="_rangeInput" @attributes="UserAttributes" InputType="@InputType.Text" Class="@PickerInputClass" Style="@Style" Variant="@Variant" ReadOnly="@(!Editable)" <RangeInput @ref="_rangeInput" @attributes="UserAttributes" InputType="@InputType.Text" Class="@PickerInputClass" Style="@Style" Variant="@Variant" ReadOnly="@(!Editable)"
@bind-Value="@RangeText" Disabled="@Disabled" Adornment="@Adornment" AdornmentIcon="@AdornmentIcon" AdornmentColor="@AdornmentColor" IconSize="@IconSize" OnAdornmentClick="ToggleState" @bind-Value="@RangeText" Disabled="@Disabled" Adornment="@Adornment" AdornmentIcon="@AdornmentIcon" AdornmentColor="@AdornmentColor" IconSize="@IconSize" OnAdornmentClick="ToggleState"
Required="@Required" RequiredError="@RequiredError" Error="@Error" ErrorText="@ErrorText" Margin="@Margin" AdornmentAriaLabel="@AdornmentAriaLabel"/> Required="@Required" RequiredError="@RequiredError" Error="@HasError" ErrorText="@ErrorText" Margin="@Margin" AdornmentAriaLabel="@AdornmentAriaLabel"/>
</InputContent> </InputContent>
</InputControl>; </InputControl>;

@ -57,7 +57,6 @@ public partial class DateRangePicker : DatePickerBase
if (updateValue) if (updateValue)
{ {
Converter.GetError = false;
if (_dateRange == null) if (_dateRange == null)
{ {
_rangeText = null; _rangeText = null;
@ -66,8 +65,8 @@ public partial class DateRangePicker : DatePickerBase
else else
{ {
_rangeText = new Range<string>( _rangeText = new Range<string>(
Converter.Set(_dateRange.Start), Converter.Convert(_dateRange.Start),
Converter.Set(_dateRange.End)); Converter.Convert(_dateRange.End));
await SetTextAsync(_dateRange.ToString(Converter), false); await SetTextAsync(_dateRange.ToString(Converter), false);
} }
} }
@ -85,7 +84,7 @@ public partial class DateRangePicker : DatePickerBase
if (_rangeText?.Equals(value) ?? value == null) if (_rangeText?.Equals(value) ?? value == null)
return; return;
Touched = true; Modified = true;
_rangeText = value; _rangeText = value;
SetDateRangeAsync(ParseDateRangeValue(value?.Start, value?.End), false).AndForget(); SetDateRangeAsync(ParseDateRangeValue(value?.Start, value?.End), false).AndForget();
} }
@ -135,13 +134,13 @@ public partial class DateRangePicker : DatePickerBase
protected override Task DateFormatChanged(string newFormat) protected override Task DateFormatChanged(string newFormat)
{ {
Touched = true; Modified = true;
return SetTextAsync(_dateRange?.ToString(Converter), false); return SetTextAsync(_dateRange?.ToString(Converter), false);
} }
protected override Task StringValueChanged(string value) protected override Task StringValueChanged(string value)
{ {
Touched = true; Modified = true;
// Update the daterange property (without updating back the Value property) // Update the daterange property (without updating back the Value property)
return SetDateRangeAsync(ParseDateRangeValue(value), false); return SetDateRangeAsync(ParseDateRangeValue(value), false);
} }

@ -109,14 +109,14 @@ public partial class FileUpload<T> : FormComponent<T, string>
await FilesChanged.InvokeAsync(_value); await FilesChanged.InvokeAsync(_value);
BeginValidate(); BeginValidate();
FieldChanged(_value); FieldChanged(_value);
if (!Error || !SuppressOnChangeWhenInvalid) //only trigger FilesChanged if validation passes or SuppressOnChangeWhenInvalid is false if (!HasError || !SuppressOnChangeWhenInvalid) //only trigger FilesChanged if validation passes or SuppressOnChangeWhenInvalid is false
await OnFilesChanged.InvokeAsync(args); await OnFilesChanged.InvokeAsync(args);
} }
protected override void OnInitialized() protected override void OnInitialized()
{ {
if (!(typeof(T) == typeof(IReadOnlyList<IBrowserFile>) || typeof(T) == typeof(IBrowserFile))) //if (!(typeof(T) == typeof(IReadOnlyList<IBrowserFile>) || typeof(T) == typeof(IBrowserFile)))
Logger.LogWarning("T must be of type {type1} or {type2}", typeof(IReadOnlyList<IBrowserFile>), typeof(IBrowserFile)); // Logger.LogWarning("T must be of type {type1} or {type2}", typeof(IReadOnlyList<IBrowserFile>), typeof(IBrowserFile));
base.OnInitialized(); base.OnInitialized();
} }

@ -206,11 +206,11 @@ public partial class Form : UIComponent, IDisposable, IForm
// - none have an error // - none have an error
// - all required fields have been touched (and thus validated) // - all required fields have been touched (and thus validated)
var no_errors = _formControls.All(x => x.HasErrors == false); var no_errors = _formControls.All(x => x.HasErrors == false);
var required_all_touched = _formControls.Where(x => x.Required).All(x => x.Touched); var required_all_touched = _formControls.Where(x => x.Required).All(x => x.Modified);
var valid = no_errors && required_all_touched; var valid = no_errors && required_all_touched;
var old_touched = _touched; var old_touched = _touched;
_touched = _formControls.Any(x => x.Touched); _touched = _formControls.Any(x => x.Modified);
try try
{ {
_shouldRender = false; _shouldRender = false;

@ -14,13 +14,19 @@ public abstract class FormComponent<T, U> : UIComponent, IFormComponent, IDispos
{ {
private Converter<T, U> _converter; private Converter<T, U> _converter;
/// <summary>
/// Invoked whenever the string value cannot be converted
/// </summary>
public event EventHandler<string> ConversionErrorOccured;
protected FormComponent(Converter<T, U> converter) protected FormComponent(Converter<T, U> converter)
{ {
_converter = converter ?? throw new ArgumentNullException(nameof(converter)); _converter = converter ?? throw new ArgumentNullException(nameof(converter));
_converter.OnError = OnConversionError; _converter.ErrorOccured += (s, e) => OnConversionError(e);
} }
[CascadingParameter] internal IForm Form { get; set; } [CascadingParameter]
internal IForm? Form { get; set; }
/// <summary> /// <summary>
/// If true, this is a top-level form component. If false, this input is a sub-component of another input (i.e. TextField, Select, etc). /// If true, this is a top-level form component. If false, this input is a sub-component of another input (i.e. TextField, Select, etc).
@ -33,77 +39,77 @@ public abstract class FormComponent<T, U> : UIComponent, IFormComponent, IDispos
/// If true, this form input is required to be filled out. /// If true, this form input is required to be filled out.
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.FormComponent.Validation)]
public bool Required { get; set; } public bool Required { get; set; }
/// <summary> /// <summary>
/// The error text that will be displayed if the input is not filled out but required. /// The error text that will be displayed if the input is not filled out but required.
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.FormComponent.Validation)]
public string RequiredError { get; set; } = "Required"; public string RequiredError { get; set; } = "Required";
/// <summary> /// <summary>
/// The ErrorText that will be displayed if Error true. /// The ErrorText that will be displayed if <see cref="HasError"/> is set to true.
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.FormComponent.Validation)]
public string ErrorText { get; set; } public string ErrorText { get; set; }
/// <summary> /// <summary>
/// If true, the label will be displayed in an error state. /// If true, the label will be displayed in an error state.
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.FormComponent.Validation)] public bool HasError { get; set; }
public bool Error { get; set; }
/// <summary> /// <summary>
/// The ErrorId that will be used by aria-describedby if Error true /// The ErrorId that will be used by aria-describedby if <see cref="HasError"/> is true
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.FormComponent.Validation)]
public string ErrorId { get; set; } public string ErrorId { get; set; }
/// <summary> /// <summary>
/// The generic converter of the component. /// The generic converter of the component.
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.FormComponent.Behavior)]
public Converter<T, U> Converter public Converter<T, U> Converter
{ {
get => _converter; get => _converter;
set => SetConverter(value); set => SetConverter(value);
} }
protected virtual bool SetConverter(Converter<T, U> value)
{
var changed = (_converter != value);
if (changed)
{
_converter = value ?? throw new ArgumentNullException(nameof(value)); // converter is mandatory at all times
_converter.OnError = OnConversionError;
}
return changed;
}
/// <summary> /// <summary>
/// The culture of the component. /// The culture of the component. Also sets the culture of the <see cref="Converter"/> .
/// </summary> /// </summary>
[Parameter] [Parameter]
[Category(CategoryTypes.FormComponent.Behavior)]
public CultureInfo Culture public CultureInfo Culture
{ {
get => _converter.Culture; get => _converter.Culture;
set => SetCulture(value); set => SetCulture(value);
} }
protected virtual bool SetCulture(CultureInfo value) private string _conversionError { get; set; }
protected virtual bool SetConverter(Converter<T, U> value)
{ {
var changed = (_converter.Culture != value); var changed = _converter != value;
if (changed) if (changed)
{ {
_converter.Culture = value; /*
* Converter is mandatory at all times
*/
_converter = value ?? throw new ArgumentNullException(nameof(value));
_converter.ErrorOccured += (s, e) => OnConversionError(e);
} }
return changed;
}
protected virtual bool SetCulture(CultureInfo value)
{
var changed = _converter.Culture != value;
if (changed)
_converter.Culture = value;
return changed; return changed;
} }
@ -111,37 +117,38 @@ public abstract class FormComponent<T, U> : UIComponent, IFormComponent, IDispos
{ {
// note: we need to update the form here because the conversion error might lead to not updating the value // note: we need to update the form here because the conversion error might lead to not updating the value
// ... which leads to not updating the form // ... which leads to not updating the form
Touched = true;
//TODO Why does the form need to be updated?
Modified = true;
_conversionError = error;
Form?.Update(this); Form?.Update(this);
OnConversionErrorOccurred(error);
}
protected virtual void OnConversionErrorOccurred(string error) ConversionErrorOccured?.Invoke(this, error);
{
/* Descendants can override this method to catch conversion errors */
} }
/// <summary> /// <summary>
/// True if the conversion from string to T failed /// True if the conversion from string to T failed
/// </summary> /// </summary>
public bool ConversionError => _converter.GetError; public bool ConversionError => !string.IsNullOrWhiteSpace(_conversionError);
/// <summary> /// <summary>
/// The error message of the conversion error from string to T. Null otherwise /// The error message of the conversion error from string to T. Null otherwise
/// </summary> /// </summary>
public string ConversionErrorMessage => _converter.GetErrorMessage; public string ConversionErrorMessage => _conversionError;
/// <summary> /// <summary>
/// True if the input has any of the following errors: An error set from outside, a conversion error or /// True if the input has any of the following errors: An error set from outside, a conversion error or
/// one or more validation errors /// one or more validation errors
/// </summary> /// </summary>
public bool HasErrors => Error || ConversionError || ValidationErrors.Count > 0; public bool HasErrors => HasError || ConversionError || ValidationErrors.Count > 0;
/// <summary> /// <summary>
/// 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()
{ {
// 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))
@ -154,13 +161,13 @@ public abstract class FormComponent<T, U> : UIComponent, IFormComponent, IDispos
} }
/// <summary> /// <summary>
/// This manages the state of having been "touched" by the user. A form control always starts out untouched /// This manages the state of having been modified by the user. A form control always starts out unmodified
/// but becomes touched when the user performed input or the blur event was raised. /// but becomes modified when the user performed input or the blur event was raised.
/// ///
/// The touched state is only relevant for inputs that have no value (i.e. empty text fields). Being untouched will /// The modified state is only relevant for inputs that have no value (i.e. empty text fields). Being unmodified will
/// suppress RequiredError /// suppress the display of the <see cref="RequiredError"/>
/// </summary> /// </summary>
public bool Touched { get; protected set; } public bool Modified { get; protected set; }
#region MudForm Validation #region MudForm Validation
@ -182,10 +189,20 @@ public abstract class FormComponent<T, U> : UIComponent, IFormComponent, IDispos
[Category(CategoryTypes.FormComponent.Validation)] [Category(CategoryTypes.FormComponent.Validation)]
public object Validation { get; set; } public object Validation { get; set; }
private T __value;
/// <summary> /// <summary>
/// This is the form component's value. /// This is the form component's value.
/// </summary> /// </summary>
protected T _value; protected T _value
{
get => __value;
set
{
__value = value;
_conversionError = null;
}
}
// These are the fire-and-forget methods to launch an async validation process. // These are the fire-and-forget methods to launch an async validation process.
// After each async step, we make sure the current Value of the component has not changed while // After each async step, we make sure the current Value of the component has not changed while
@ -232,7 +249,7 @@ public abstract class FormComponent<T, U> : UIComponent, IFormComponent, IDispos
// when a validation is forced, we must set Touched to true, because for untouched fields with // when a validation is forced, we must set Touched to true, because for untouched fields with
// no value, validation does nothing due to the way forms are expected to work (display errors // no value, validation does nothing due to the way forms are expected to work (display errors
// only after fields have been touched). // only after fields have been touched).
Touched = true; Modified = true;
return ValidateValue(); return ValidateValue();
} }
@ -284,7 +301,7 @@ public abstract class FormComponent<T, U> : UIComponent, IFormComponent, IDispos
// required error (must be last, because it is least important!) // required error (must be last, because it is least important!)
if (Required) if (Required)
{ {
if (Touched && !HasValue(_value)) if (Modified && !HasValue(_value))
errors.Add(RequiredError); errors.Add(RequiredError);
} }
} }
@ -297,7 +314,7 @@ public abstract class FormComponent<T, U> : UIComponent, IFormComponent, IDispos
// if Error and ErrorText are set by the user, setting them here will have no effect. // if Error and ErrorText are set by the user, setting them here will have no effect.
// if Error, create an error id that can be used by aria-describedby on input control // if Error, create an error id that can be used by aria-describedby on input control
ValidationErrors = errors; ValidationErrors = errors;
Error = errors.Count > 0; HasError = errors.Count > 0;
ErrorText = errors.FirstOrDefault(); ErrorText = errors.FirstOrDefault();
ErrorId = HasErrors ? Guid.NewGuid().ToString() : null; ErrorId = HasErrors ? Guid.NewGuid().ToString() : null;
Form?.Update(this); Form?.Update(this);
@ -487,7 +504,8 @@ public abstract class FormComponent<T, U> : UIComponent, IFormComponent, IDispos
{ {
/* to be overridden */ /* to be overridden */
_value = default; _value = default;
Touched = false; _conversionError = null;
Modified = false;
StateHasChanged(); StateHasChanged();
} }
@ -496,7 +514,7 @@ public abstract class FormComponent<T, U> : UIComponent, IFormComponent, IDispos
/// </summary> /// </summary>
public void ResetValidation() public void ResetValidation()
{ {
Error = false; HasError = false;
ValidationErrors.Clear(); ValidationErrors.Clear();
ErrorText = null; ErrorText = null;
StateHasChanged(); StateHasChanged();
@ -527,28 +545,26 @@ public abstract class FormComponent<T, U> : UIComponent, IFormComponent, IDispos
/// <summary> /// <summary>
/// Specify an expression which returns the model's field for which validation messages should be displayed. /// Specify an expression which returns the model's field for which validation messages should be displayed.
/// </summary> /// </summary>
#nullable enable
[Parameter] [Parameter]
[Category(CategoryTypes.FormComponent.Validation)]
public Expression<Func<T>>? For { get; set; } public Expression<Func<T>>? For { get; set; }
#nullable disable
public bool IsForNull => For == null; public bool IsForNull => For == null;
/// <summary> /// <summary>
/// Stores the list of validation attributes attached to the property targeted by <seealso cref="For"/>. If <seealso cref="For"/> is null, this property is null too. /// Stores the list of validation attributes attached to the property targeted by <seealso cref="For"/>. If <seealso cref="For"/> is null, this property is null too.
/// </summary> /// </summary>
#nullable enable
private IEnumerable<ValidationAttribute>? _validationAttrsFor; private IEnumerable<ValidationAttribute>? _validationAttrsFor;
#nullable disable
private void OnValidationStateChanged(object sender, ValidationStateChangedEventArgs e) private void OnValidationStateChanged(object sender, ValidationStateChangedEventArgs e)
{ {
if (EditContext != null && !_fieldIdentifier.Equals(default(FieldIdentifier))) if (EditContext != null && !_fieldIdentifier.Equals(default(FieldIdentifier)))
{ {
var error_msgs = EditContext.GetValidationMessages(_fieldIdentifier).ToArray(); var error_msgs = EditContext.GetValidationMessages(_fieldIdentifier).ToArray();
Error = error_msgs.Length > 0; HasError = error_msgs.Length > 0;
ErrorText = (Error ? error_msgs[0] : null); ErrorText = (HasError ? error_msgs[0] : null);
StateHasChanged(); StateHasChanged();
} }
} }
@ -561,16 +577,13 @@ public abstract class FormComponent<T, U> : UIComponent, IFormComponent, IDispos
/// <summary> /// <summary>
/// To find out whether or not For parameter has changed we keep a separate reference /// To find out whether or not For parameter has changed we keep a separate reference
/// </summary> /// </summary>
#nullable enable
private Expression<Func<T>>? _currentFor; private Expression<Func<T>>? _currentFor;
#nullable disable
/// <summary> /// <summary>
/// To find out whether or not EditContext parameter has changed we keep a separate reference /// To find out whether or not EditContext parameter has changed we keep a separate reference
/// </summary> /// </summary>
#nullable enable
private EditContext? _currentEditContext; private EditContext? _currentEditContext;
#nullable disable
protected override void OnParametersSet() protected override void OnParametersSet()
{ {

@ -3,9 +3,9 @@
public interface IFormComponent public interface IFormComponent
{ {
public bool Required { get; set; } public bool Required { get; set; }
public bool Error { get; set; } public bool HasError { get; set; }
public bool HasErrors { get; } public bool HasErrors { get; }
public bool Touched { get; } public bool Modified { get; }
public object Validation { get; set; } public object Validation { get; set; }
public bool IsForNull { get; } public bool IsForNull { get; }
public List<string> ValidationErrors { get; set; } public List<string> ValidationErrors { get; set; }

@ -41,7 +41,7 @@
@onkeyup:preventDefault="@KeyUpPreventDefault" @onkeyup:preventDefault="@KeyUpPreventDefault"
@onmousewheel="@OnMouseWheel" @onmousewheel="@OnMouseWheel"
@onwheel="@OnMouseWheel" @onwheel="@OnMouseWheel"
aria-invalid="@Error.ToString().ToLower()" aria-invalid="@HasError.ToString().ToLower()"
aria-describedby="@ErrorId" aria-describedby="@ErrorId"
> >
@Text @Text
@ -73,7 +73,7 @@
@onkeyup:preventDefault="@KeyUpPreventDefault" @onkeyup:preventDefault="@KeyUpPreventDefault"
@onmousewheel="@OnMouseWheel" @onmousewheel="@OnMouseWheel"
@onwheel="@OnMouseWheel" @onwheel="@OnMouseWheel"
aria-invalid="@Error.ToString().ToLower()" aria-invalid="@HasError.ToString().ToLower()"
aria-describedby="@ErrorId" aria-describedby="@ErrorId"
/> />
@*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*@

@ -215,7 +215,7 @@ public abstract class InputBase<T> : FormComponent<T, string>
{ {
Text = text; Text = text;
if (!string.IsNullOrWhiteSpace(Text)) if (!string.IsNullOrWhiteSpace(Text))
Touched = true; Modified = true;
if (updateValue) if (updateValue)
await UpdateValuePropertyAsync(false); await UpdateValuePropertyAsync(false);
await TextChanged.InvokeAsync(Text); await TextChanged.InvokeAsync(Text);
@ -227,7 +227,7 @@ public abstract class InputBase<T> : FormComponent<T, string>
/// </summary> /// </summary>
protected virtual Task UpdateTextPropertyAsync(bool updateValue) protected virtual Task UpdateTextPropertyAsync(bool updateValue)
{ {
return SetTextAsync(Converter.Set(Value), updateValue); return SetTextAsync(Converter.Convert(Value), updateValue);
} }
/// <summary> /// <summary>
@ -266,7 +266,7 @@ public abstract class InputBase<T> : FormComponent<T, string>
if (!OnlyValidateIfDirty || _isDirty) if (!OnlyValidateIfDirty || _isDirty)
{ {
Touched = true; Modified = true;
BeginValidateAfter(OnBlur.InvokeAsync(obj)); BeginValidateAfter(OnBlur.InvokeAsync(obj));
} }
} }
@ -361,7 +361,7 @@ public abstract class InputBase<T> : FormComponent<T, string>
/// </summary> /// </summary>
protected virtual Task UpdateValuePropertyAsync(bool updateText) protected virtual Task UpdateValuePropertyAsync(bool updateText)
{ {
return SetValueAsync(Converter.Get(Text), updateText); return SetValueAsync(Converter.ConvertBack(Text), updateText);
} }
protected override bool SetConverter(Converter<T, string> value) protected override bool SetConverter(Converter<T, string> value)
@ -389,7 +389,7 @@ public abstract class InputBase<T> : FormComponent<T, string>
[Category(CategoryTypes.FormComponent.Behavior)] [Category(CategoryTypes.FormComponent.Behavior)]
public string Format public string Format
{ {
get => ((Converter<T>)Converter).Format; get => ((ToStringConverter<T>)Converter).Format;
set => SetFormat(value); set => SetFormat(value);
} }
@ -398,7 +398,7 @@ public abstract class InputBase<T> : FormComponent<T, string>
var changed = Format != value; var changed = Format != value;
if (changed) if (changed)
{ {
((Converter<T>)Converter).Format = value; ((ToStringConverter<T>)Converter).Format = value;
UpdateTextPropertyAsync(false).AndForget(); // refresh only Text property from current Value UpdateTextPropertyAsync(false).AndForget(); // refresh only Text property from current Value
} }
return changed; return changed;

@ -238,7 +238,7 @@ public partial class Mask : InputBase<string>, IDisposable
await base.SetTextAsync(text, updateValue: false); await base.SetTextAsync(text, updateValue: false);
if (Clearable) if (Clearable)
UpdateClearable(Text); UpdateClearable(Text);
var v = Converter.Get(cleanText); var v = Converter.ConvertBack(cleanText);
Value = v; Value = v;
await ValueChanged.InvokeAsync(v); await ValueChanged.InvokeAsync(v);
SetCaretPosition(caret, selection); SetCaretPosition(caret, selection);
@ -262,7 +262,7 @@ public partial class Mask : InputBase<string>, IDisposable
// allow this only via changes from the outside // allow this only via changes from the outside
if (_updating) if (_updating)
return; return;
var text = Converter.Set(Value); var text = Converter.Convert(Value);
var cleanText = MaskKind.GetCleanText(); var cleanText = MaskKind.GetCleanText();
if (cleanText == text || string.IsNullOrEmpty(cleanText) && string.IsNullOrEmpty(text)) if (cleanText == text || string.IsNullOrEmpty(cleanText) && string.IsNullOrEmpty(text))
return; return;

@ -39,7 +39,7 @@
AdornmentIcon="@AdornmentIcon" AdornmentIcon="@AdornmentIcon"
AdornmentColor="@AdornmentColor" AdornmentColor="@AdornmentColor"
IconSize="@IconSize" IconSize="@IconSize"
Error="@Error" Error="@HasError"
Immediate="@(Immediate)" Immediate="@(Immediate)"
Margin="@Margin" Margin="@Margin"
MaxLength="@MaxLength" MaxLength="@MaxLength"

@ -31,7 +31,7 @@
Margin="@Margin" Margin="@Margin"
Required="@Required" Required="@Required"
RequiredError="@RequiredError" RequiredError="@RequiredError"
Error="@Error" Error="@HasError"
ErrorText="@ErrorText" ErrorText="@ErrorText"
Clearable="@(ReadOnly ? false : Clearable)" Clearable="@(ReadOnly ? false : Clearable)"
OnClearButtonClick="@(() => Clear())" OnClearButtonClick="@(() => Clear())"

@ -81,7 +81,7 @@ public partial class RadioGroup<T> : FormComponent<T, T>, IRadioGroup
internal Task SetSelectedRadioAsync(Radio<T> radio) internal Task SetSelectedRadioAsync(Radio<T> radio)
{ {
Touched = true; Modified = true;
return SetSelectedRadioAsync(radio, true); return SetSelectedRadioAsync(radio, true);
} }

@ -5,14 +5,14 @@
<CascadingValue Name="SubscribeToParentForm" Value="false" IsFixed="true"> <CascadingValue Name="SubscribeToParentForm" Value="false" IsFixed="true">
<div class="mud-select" id="@_elementId"> <div class="mud-select" id="@_elementId">
<InputControl Label="@Label" Variant="@Variant" HelperText="@HelperText" HelperTextOnFocus="@HelperTextOnFocus" FullWidth="@FullWidth" Margin="@Margin" Class="@Classname" Style="@Style" <InputControl Label="@Label" Variant="@Variant" HelperText="@HelperText" HelperTextOnFocus="@HelperTextOnFocus" FullWidth="@FullWidth" Margin="@Margin" Class="@Classname" Style="@Style"
Error="@Error" ErrorText="@ErrorText" ErrorId="@ErrorId" Disabled="@Disabled" @onclick="@ToggleMenu" Required="@Required" ForId="@FieldId"> Error="@HasError" ErrorText="@ErrorText" ErrorId="@ErrorId" Disabled="@Disabled" @onclick="@ToggleMenu" Required="@Required" ForId="@FieldId">
<InputContent> <InputContent>
<Input @ref="_elementReference" InputType="@(CanRenderValue || (Strict && !IsValueInList) ? InputType.Hidden : InputType.Text)" <Input @ref="_elementReference" InputType="@(CanRenderValue || (Strict && !IsValueInList) ? InputType.Hidden : InputType.Text)"
Class="mud-select-input" Margin="@Margin" Placeholder="@Placeholder" Class="mud-select-input" Margin="@Margin" Placeholder="@Placeholder"
Variant="@Variant" Variant="@Variant"
TextUpdateSuppression="false" TextUpdateSuppression="false"
Value="@(Strict && !IsValueInList ? null : Text)" DisableUnderLine="@DisableUnderLine" Value="@(Strict && !IsValueInList ? null : Text)" DisableUnderLine="@DisableUnderLine"
Disabled="@Disabled" ReadOnly="true" Error="@Error" ErrorId="@ErrorId" Disabled="@Disabled" ReadOnly="true" Error="@HasError" ErrorId="@ErrorId"
OnAdornmentClick="@OnAdornmentClick" AdornmentIcon="@_currentIcon" Adornment="@Adornment" OnAdornmentClick="@OnAdornmentClick" AdornmentIcon="@_currentIcon" Adornment="@Adornment"
AdornmentColor="@AdornmentColor" IconSize="@IconSize" AdornmentText="@AdornmentText" AdornmentColor="@AdornmentColor" IconSize="@IconSize" AdornmentText="@AdornmentText"
Clearable="@Clearable" OnClearButtonClick="(async (e) => await SelectClearButtonClickHandlerAsync(e))" Clearable="@Clearable" OnClearButtonClick="(async (e) => await SelectClearButtonClickHandlerAsync(e))"

@ -86,12 +86,12 @@ public partial class Select<T> : InputBase<T>, ISelect, IShadowSelect
// find first item that starts with the letter // find first item that starts with the letter
var currentItem = items.FirstOrDefault(x => x.ItemId == (string)_activeItemId); var currentItem = items.FirstOrDefault(x => x.ItemId == (string)_activeItemId);
if (currentItem != null && if (currentItem != null &&
Converter.Set(currentItem.Value)?.ToLowerInvariant().StartsWith(startChar) == true) Converter.Convert(currentItem.Value)?.ToLowerInvariant().StartsWith(startChar) == true)
{ {
// this will step through all items that start with the same letter if pressed multiple times // this will step through all items that start with the same letter if pressed multiple times
items = items.SkipWhile(x => x != currentItem).Skip(1); items = items.SkipWhile(x => x != currentItem).Skip(1);
} }
items = items.Where(x => Converter.Set(x.Value)?.ToLowerInvariant().StartsWith(startChar) == true); items = items.Where(x => Converter.Convert(x.Value)?.ToLowerInvariant().StartsWith(startChar) == true);
} }
var item = items.FirstOrDefault(); var item = items.FirstOrDefault();
if (item == null) if (item == null)
@ -244,13 +244,13 @@ public partial class Select<T> : InputBase<T>, ISelect, IShadowSelect
//Warning. Here the Converter was not set yet //Warning. Here the Converter was not set yet
if (MultiSelectionTextFunc != null) if (MultiSelectionTextFunc != null)
{ {
SetCustomizedTextAsync(string.Join(Delimiter, SelectedValues.Select(x => Converter.Set(x))), SetCustomizedTextAsync(string.Join(Delimiter, SelectedValues.Select(x => Converter.Convert(x))),
selectedConvertedValues: SelectedValues.Select(x => Converter.Set(x)).ToList(), selectedConvertedValues: SelectedValues.Select(x => Converter.Convert(x)).ToList(),
multiSelectionTextFunc: MultiSelectionTextFunc).AndForget(); multiSelectionTextFunc: MultiSelectionTextFunc).AndForget();
} }
else else
{ {
SetTextAsync(string.Join(Delimiter, SelectedValues.Select(x => Converter.Set(x))), updateValue: false).AndForget(); SetTextAsync(string.Join(Delimiter, SelectedValues.Select(x => Converter.Convert(x))), updateValue: false).AndForget();
} }
} }
SelectedValuesChanged.InvokeAsync(new HashSet<T>(SelectedValues, _comparer)); SelectedValuesChanged.InvokeAsync(new HashSet<T>(SelectedValues, _comparer));
@ -293,11 +293,7 @@ public partial class Select<T> : InputBase<T>, ISelect, IShadowSelect
if (_toStringFunc == value) if (_toStringFunc == value)
return; return;
_toStringFunc = value; _toStringFunc = value;
Converter = new Converter<T> Converter = new LambdaConverter<T, string>(_toStringFunc ?? (x => x?.ToString()), null);
{
SetFunc = _toStringFunc ?? (x => x?.ToString()),
//GetFunc = LookupValue,
};
} }
} }
@ -393,15 +389,15 @@ public partial class Select<T> : InputBase<T>, ISelect, IShadowSelect
if (MultiSelectionTextFunc != null) if (MultiSelectionTextFunc != null)
{ {
return MultiSelection return MultiSelection
? SetCustomizedTextAsync(string.Join(Delimiter, SelectedValues.Select(x => Converter.Set(x))), ? SetCustomizedTextAsync(string.Join(Delimiter, SelectedValues.Select(x => Converter.Convert(x))),
selectedConvertedValues: SelectedValues.Select(x => Converter.Set(x)).ToList(), selectedConvertedValues: SelectedValues.Select(x => Converter.Convert(x)).ToList(),
multiSelectionTextFunc: MultiSelectionTextFunc) multiSelectionTextFunc: MultiSelectionTextFunc)
: base.UpdateTextPropertyAsync(updateValue); : base.UpdateTextPropertyAsync(updateValue);
} }
else else
{ {
return MultiSelection return MultiSelection
? SetTextAsync(string.Join(Delimiter, SelectedValues.Select(x => Converter.Set(x)))) ? SetTextAsync(string.Join(Delimiter, SelectedValues.Select(x => Converter.Convert(x))))
: base.UpdateTextPropertyAsync(updateValue); : base.UpdateTextPropertyAsync(updateValue);
} }
} }
@ -568,13 +564,13 @@ public partial class Select<T> : InputBase<T>, ISelect, IShadowSelect
if (MultiSelectionTextFunc != null) if (MultiSelectionTextFunc != null)
{ {
await SetCustomizedTextAsync(string.Join(Delimiter, SelectedValues.Select(x => Converter.Set(x))), await SetCustomizedTextAsync(string.Join(Delimiter, SelectedValues.Select(x => Converter.Convert(x))),
selectedConvertedValues: SelectedValues.Select(x => Converter.Set(x)).ToList(), selectedConvertedValues: SelectedValues.Select(x => Converter.Convert(x)).ToList(),
multiSelectionTextFunc: MultiSelectionTextFunc); multiSelectionTextFunc: MultiSelectionTextFunc);
} }
else else
{ {
await SetTextAsync(string.Join(Delimiter, SelectedValues.Select(x => Converter.Set(x))), updateValue: false); await SetTextAsync(string.Join(Delimiter, SelectedValues.Select(x => Converter.Convert(x))), updateValue: false);
} }
UpdateSelectAllChecked(); UpdateSelectAllChecked();
@ -811,7 +807,7 @@ public partial class Select<T> : InputBase<T>, ISelect, IShadowSelect
{ {
multiSelectionText = text; multiSelectionText = text;
if (!string.IsNullOrWhiteSpace(multiSelectionText)) if (!string.IsNullOrWhiteSpace(multiSelectionText))
Touched = true; Modified = true;
if (updateValue) if (updateValue)
await UpdateValuePropertyAsync(false); await UpdateValuePropertyAsync(false);
await TextChanged.InvokeAsync(multiSelectionText); await TextChanged.InvokeAsync(multiSelectionText);
@ -1004,13 +1000,13 @@ public partial class Select<T> : InputBase<T>, ISelect, IShadowSelect
_selectedValues = new HashSet<T>(selectedValues, _comparer); _selectedValues = new HashSet<T>(selectedValues, _comparer);
if (MultiSelectionTextFunc != null) if (MultiSelectionTextFunc != null)
{ {
await SetCustomizedTextAsync(string.Join(Delimiter, SelectedValues.Select(x => Converter.Set(x))), await SetCustomizedTextAsync(string.Join(Delimiter, SelectedValues.Select(x => Converter.Convert(x))),
selectedConvertedValues: SelectedValues.Select(x => Converter.Set(x)).ToList(), selectedConvertedValues: SelectedValues.Select(x => Converter.Convert(x)).ToList(),
multiSelectionTextFunc: MultiSelectionTextFunc); multiSelectionTextFunc: MultiSelectionTextFunc);
} }
else else
{ {
await SetTextAsync(string.Join(Delimiter, SelectedValues.Select(x => Converter.Set(x))), updateValue: false); await SetTextAsync(string.Join(Delimiter, SelectedValues.Select(x => Converter.Convert(x))), updateValue: false);
} }
UpdateSelectAllChecked(); UpdateSelectAllChecked();
_selectedValues = selectedValues; // need to force selected values because Blazor overwrites it under certain circumstances due to changes of Text or Value _selectedValues = selectedValues; // need to force selected values because Blazor overwrites it under certain circumstances due to changes of Text or Value

@ -129,7 +129,7 @@ public partial class SelectItem<T> : SelectItemBase, IDisposable
var converter = Select?.Converter; var converter = Select?.Converter;
if (converter == null) if (converter == null)
return $"{Value}"; return $"{Value}";
return converter.Set(Value); return converter.Convert(Value);
} }
} }

@ -28,8 +28,8 @@ public partial class Slider<T> : UIComponent
[Category(CategoryTypes.Slider.Validation)] [Category(CategoryTypes.Slider.Validation)]
public T Min public T Min
{ {
get => Converter.Get(_min); get => Converter.ConvertBack(_min);
set => _min = Converter.Set(value); set => _min = Converter.Convert(value);
} }
/// <summary> /// <summary>
@ -40,8 +40,8 @@ public partial class Slider<T> : UIComponent
[Category(CategoryTypes.Slider.Validation)] [Category(CategoryTypes.Slider.Validation)]
public T Max public T Max
{ {
get => Converter.Get(_max); get => Converter.ConvertBack(_max);
set => _max = Converter.Set(value); set => _max = Converter.Convert(value);
} }
/// <summary> /// <summary>
@ -52,8 +52,8 @@ public partial class Slider<T> : UIComponent
[Category(CategoryTypes.Slider.Validation)] [Category(CategoryTypes.Slider.Validation)]
public T Step public T Step
{ {
get => Converter.Get(_step); get => Converter.ConvertBack(_step);
set => _step = Converter.Set(value); set => _step = Converter.Convert(value);
} }
/// <summary> /// <summary>
@ -73,7 +73,7 @@ public partial class Slider<T> : UIComponent
[Parameter] [Parameter]
[Category(CategoryTypes.Slider.Behavior)] [Category(CategoryTypes.Slider.Behavior)]
public Converter<T> Converter { get; set; } = new DefaultConverter<T>() { Culture = CultureInfo.InvariantCulture }; public ToStringConverter<T> Converter { get; set; } = new DefaultConverter<T>() { Culture = CultureInfo.InvariantCulture };
[Parameter] public EventCallback<T> ValueChanged { get; set; } [Parameter] public EventCallback<T> ValueChanged { get; set; }
@ -81,10 +81,10 @@ public partial class Slider<T> : UIComponent
[Category(CategoryTypes.Slider.Data)] [Category(CategoryTypes.Slider.Data)]
public T Value public T Value
{ {
get => Converter.Get(_value); get => Converter.ConvertBack(_value);
set set
{ {
var d = Converter.Set(value); var d = Converter.Convert(value);
if (_value == d) if (_value == d)
return; return;
_value = d; _value = d;

@ -9,7 +9,7 @@
HelperTextOnFocus="@HelperTextOnFocus" HelperTextOnFocus="@HelperTextOnFocus"
CounterText="@GetCounterText()" CounterText="@GetCounterText()"
FullWidth="@FullWidth" FullWidth="@FullWidth"
Class="@Classname" Class="@ClassList"
Error="@HasErrors" Error="@HasErrors"
ErrorText="@GetErrorText()" ErrorText="@GetErrorText()"
ErrorId="@ErrorId" ErrorId="@ErrorId"
@ -43,7 +43,7 @@
AdornmentAriaLabel="@AdornmentAriaLabel" AdornmentAriaLabel="@AdornmentAriaLabel"
IconSize="@IconSize" IconSize="@IconSize"
OnAdornmentClick="@OnAdornmentClick" OnAdornmentClick="@OnAdornmentClick"
Error="@Error" Error="@HasError"
ErrorId="@ErrorId" ErrorId="@ErrorId"
Immediate="@Immediate" Immediate="@Immediate"
Margin="@Margin" Margin="@Margin"
@ -83,7 +83,7 @@
AdornmentColor="@AdornmentColor" AdornmentColor="@AdornmentColor"
IconSize="@IconSize" IconSize="@IconSize"
OnAdornmentClick="@OnAdornmentClick" OnAdornmentClick="@OnAdornmentClick"
Error="@Error" Error="@HasError"
Immediate="@Immediate" Immediate="@Immediate"
Margin="@Margin" OnBlur="@OnBlurred" Margin="@Margin" OnBlur="@OnBlurred"
Clearable="@Clearable" Clearable="@Clearable"

@ -7,13 +7,9 @@ namespace Connected.Components;
public partial class TextField<T> : DebouncedInput<T> public partial class TextField<T> : DebouncedInput<T>
{ {
protected string Classname => private Mask? _maskReference;
new CssBuilder("mud-input-input-control")
.AddClass(Class)
.Build();
public Input<string> InputReference { get; private set; } public Input<string> InputReference { get; private set; }
private Mask _maskReference;
/// <summary> /// <summary>
/// Type of the input element. It should be a valid HTML5 input type. /// Type of the input element. It should be a valid HTML5 input type.
@ -38,6 +34,11 @@ 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 =>
new CssBuilder("mud-input-input-control")
.AddClass(Class)
.Build();
public override ValueTask FocusAsync() public override ValueTask FocusAsync()
{ {
if (_mask == null) if (_mask == null)
@ -130,10 +131,10 @@ public partial class TextField<T> : DebouncedInput<T>
{ {
if (_mask != null) if (_mask != null)
{ {
var textValue = Converter.Set(value); var textValue = Converter.Convert(value);
_mask.SetText(textValue); _mask.SetText(textValue);
textValue = Mask.GetCleanText(); textValue = Mask.GetCleanText();
value = Converter.Get(textValue); value = Converter.ConvertBack(textValue);
} }
return base.SetValueAsync(value, updateText); return base.SetValueAsync(value, updateText);
} }

@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Dynamic;
using System.Globalization; using System.Globalization;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Connected.Annotations; using Connected.Annotations;
@ -14,11 +15,13 @@ public partial class TimePicker : Picker<TimeSpan?>
private const string format24Hours = "HH:mm"; private const string format24Hours = "HH:mm";
private const string format12Hours = "hh:mm tt"; private const string format12Hours = "hh:mm tt";
public TimePicker() : base(new DefaultConverter<TimeSpan?>())
public TimePicker() : base()
{ {
Converter.GetFunc = OnGet; _timeFormat = format24Hours;
Converter.SetFunc = OnSet;
((DefaultConverter<TimeSpan?>)Converter).Format = format24Hours; Converter = new LambdaConverter<TimeSpan?, string>((e) => OnSet(e), (e) => OnGet(e));
AdornmentIcon = Icons.Material.Filled.AccessTime; AdornmentIcon = Icons.Material.Filled.AccessTime;
AdornmentAriaLabel = "Open Time Picker"; AdornmentAriaLabel = "Open Time Picker";
} }
@ -30,7 +33,7 @@ public partial class TimePicker : Picker<TimeSpan?>
var time = DateTime.Today.Add(timespan.Value); var time = DateTime.Today.Add(timespan.Value);
return time.ToString(((DefaultConverter<TimeSpan?>)Converter).Format, Culture); return time.ToString(_timeFormat, Culture);
} }
private TimeSpan? OnGet(string value) private TimeSpan? OnGet(string value)
@ -38,7 +41,7 @@ public partial class TimePicker : Picker<TimeSpan?>
if (string.IsNullOrEmpty(value)) if (string.IsNullOrEmpty(value))
return null; return null;
if (DateTime.TryParseExact(value, ((DefaultConverter<TimeSpan?>)Converter).Format, Culture, DateTimeStyles.None, out var time)) if (DateTime.TryParseExact(value, _timeFormat, Culture, DateTimeStyles.None, out var time))
{ {
return time.TimeOfDay; return time.TimeOfDay;
} }
@ -105,13 +108,10 @@ public partial class TimePicker : Picker<TimeSpan?>
_amPm = value; _amPm = value;
if (Converter is DefaultConverter<TimeSpan?> defaultConverter && string.IsNullOrWhiteSpace(_timeFormat)) _timeFormat = AmPm ? format12Hours : format24Hours;
{
defaultConverter.Format = AmPm ? format12Hours : format24Hours;
}
Touched = true; Modified = true;
SetTextAsync(Converter.Set(_value), false).AndForget(); SetTextAsync(Converter.Convert(_value), false).AndForget();
} }
} }
@ -129,11 +129,9 @@ public partial class TimePicker : Picker<TimeSpan?>
return; return;
_timeFormat = value; _timeFormat = value;
if (Converter is DefaultConverter<TimeSpan?> defaultConverter)
defaultConverter.Format = _timeFormat;
Touched = true; Modified = true;
SetTextAsync(Converter.Set(_value), false).AndForget(); SetTextAsync(Converter.Convert(_value), false).AndForget();
} }
} }
@ -155,7 +153,7 @@ public partial class TimePicker : Picker<TimeSpan?>
TimeIntermediate = time; TimeIntermediate = time;
_value = time; _value = time;
if (updateValue) if (updateValue)
await SetTextAsync(Converter.Set(_value), false); await SetTextAsync(Converter.Convert(_value), false);
UpdateTimeSetFromTime(); UpdateTimeSetFromTime();
await TimeChanged.InvokeAsync(_value); await TimeChanged.InvokeAsync(_value);
BeginValidate(); BeginValidate();
@ -170,9 +168,9 @@ public partial class TimePicker : Picker<TimeSpan?>
protected override Task StringValueChanged(string value) protected override Task StringValueChanged(string value)
{ {
Touched = true; Modified = true;
// Update the time property (without updating back the Value property) // Update the time property (without updating back the Value property)
return SetTimeAsync(Converter.Get(value), false); return SetTimeAsync(Converter.ConvertBack(value), false);
} }
//The last line cannot be tested //The last line cannot be tested

@ -12,7 +12,7 @@ public partial class TreeViewItem<T> : UIComponent
private string _text; private string _text;
private bool _disabled; private bool _disabled;
private bool _isChecked, _isSelected, _isServerLoaded; private bool _isChecked, _isSelected, _isServerLoaded;
private Converter<T> _converter = new DefaultConverter<T>(); private ToStringConverter<T> _converter = new DefaultConverter<T>();
private readonly List<TreeViewItem<T>> _childItems = new(); private readonly List<TreeViewItem<T>> _childItems = new();
protected string Classname => protected string Classname =>
@ -68,7 +68,7 @@ public partial class TreeViewItem<T> : UIComponent
[Category(CategoryTypes.TreeView.Behavior)] [Category(CategoryTypes.TreeView.Behavior)]
public string Text public string Text
{ {
get => string.IsNullOrEmpty(_text) ? _converter.Set(Value) : _text; get => string.IsNullOrEmpty(_text) ? _converter.Convert(Value) : _text;
set => _text = value; set => _text = value;
} }

Loading…
Cancel
Save