From 168aa5a0ac78b096e56238a8f5f0c0222e83b87d Mon Sep 17 00:00:00 2001 From: Florian Gilde Date: Tue, 13 Jan 2026 14:08:18 +0100 Subject: [PATCH 01/11] begin with mudblazor 9 support --- .../MudBlazor.Extensions.Tests.csproj | 12 +++--- .../Components/Base/MudExBaseFormComponent.cs | 4 -- .../Components/Base/MudExPickerBase.razor.cs | 3 ++ .../Components/MudExBaseInput.cs | 6 --- .../Components/MudExColorEdit.razor.cs | 6 +-- .../Components/MudExColorPicker.razor.cs | 7 +--- .../Components/MudExEnumSelect.razor | 4 +- .../Components/MudExIconPicker.razor.cs | 6 +-- .../Components/MudExInput.razor.cs | 10 ++--- .../Components/MudExList.razor.cs | 16 ++++--- .../Components/MudExPopover.razor.cs | 2 +- .../Components/MudExSelect.razor | 2 +- .../Components/MudExSelect.razor.cs | 22 ++++------ .../Components/MudExTagField.razor | 2 +- .../Components/MudExTagField.razor.cs | 2 +- .../Components/MudExTextField.razor | 2 - .../Components/MudExTextField.razor.cs | 15 ++++--- .../Components/MudExUploadEdit.razor.cs | 4 +- .../Components/MudExValidationWrapper.razor | 3 +- .../ObjectEdit/MudExObjectEdit.razor.cs | 2 +- .../ObjectEdit/MudExObjectEditPicker.razor.cs | 12 ++++-- .../ObjectEdit/Options/ObjectEditMeta.base.cs | 2 +- MudBlazor.Extensions/DialogService.cs | 4 +- .../Helper/StringMudColorConverter.cs | 42 +++++++++++++++++++ .../MudBlazor.Extensions.csproj | 10 ++--- .../wwwroot/docs/MudBlazor.Extensions.xml | 24 +++++------ .../MainSample.WebAssembly.csproj | 8 ++-- .../MainSample.WebAssembly/wwwroot/index.html | 24 +++++------ TryMudEx/Try.Core/Try.Core.csproj | 10 ++--- TryMudEx/Try.Tests/Try.Tests.csproj | 4 +- .../TryMudEx.Client/TryMudEx.Client.csproj | 8 ++-- .../TryMudEx.Server/TryMudEx.Server.csproj | 8 ++-- .../UserComponents/Try.UserComponents.csproj | 2 +- 33 files changed, 153 insertions(+), 135 deletions(-) create mode 100644 MudBlazor.Extensions/Helper/StringMudColorConverter.cs diff --git a/MudBlazor.Extensions.Tests/MudBlazor.Extensions.Tests.csproj b/MudBlazor.Extensions.Tests/MudBlazor.Extensions.Tests.csproj index 292a3800..bd84b308 100644 --- a/MudBlazor.Extensions.Tests/MudBlazor.Extensions.Tests.csproj +++ b/MudBlazor.Extensions.Tests/MudBlazor.Extensions.Tests.csproj @@ -10,16 +10,16 @@ - - - + + + - + - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/MudBlazor.Extensions/Components/Base/MudExBaseFormComponent.cs b/MudBlazor.Extensions/Components/Base/MudExBaseFormComponent.cs index 27ca77a2..18d47067 100644 --- a/MudBlazor.Extensions/Components/Base/MudExBaseFormComponent.cs +++ b/MudBlazor.Extensions/Components/Base/MudExBaseFormComponent.cs @@ -251,8 +251,4 @@ public virtual async ValueTask DisposeAsync() else _renderFinishTimer?.Dispose(); } - - /// - protected MudExBaseFormComponent(Converter converter = null) : base(converter ?? new Converter()) - {} } \ No newline at end of file diff --git a/MudBlazor.Extensions/Components/Base/MudExPickerBase.razor.cs b/MudBlazor.Extensions/Components/Base/MudExPickerBase.razor.cs index 5a102a01..82661928 100644 --- a/MudBlazor.Extensions/Components/Base/MudExPickerBase.razor.cs +++ b/MudBlazor.Extensions/Components/Base/MudExPickerBase.razor.cs @@ -42,6 +42,9 @@ public partial class MudExPickerBase [Parameter] public AnimationType PopverAnimation { get; set; } = AnimationType.Pulse; + [Parameter] + public OverflowBehavior? OverflowBehavior { get; set; } + /// /// Contains all parameters before init /// diff --git a/MudBlazor.Extensions/Components/MudExBaseInput.cs b/MudBlazor.Extensions/Components/MudExBaseInput.cs index 1965ec67..54268b43 100644 --- a/MudBlazor.Extensions/Components/MudExBaseInput.cs +++ b/MudBlazor.Extensions/Components/MudExBaseInput.cs @@ -105,12 +105,6 @@ public abstract class MudExBaseInput : MudBaseInput [SafeCategory(CategoryTypes.FormComponent.Validation)] [Parameter] public EventCallback ErrorStateChanged { get; set; } - /// - /// Callback when the error changes - /// - [SafeCategory(CategoryTypes.FormComponent.Validation)] - [Parameter] public EventCallback ErrorChanged { get; set; } - /// /// Callback when the validation errors change diff --git a/MudBlazor.Extensions/Components/MudExColorEdit.razor.cs b/MudBlazor.Extensions/Components/MudExColorEdit.razor.cs index 6052ae4f..033dffea 100644 --- a/MudBlazor.Extensions/Components/MudExColorEdit.razor.cs +++ b/MudBlazor.Extensions/Components/MudExColorEdit.razor.cs @@ -164,9 +164,7 @@ protected override void OnInitialized() { AdornmentIcon = Icons.Material.Filled.ColorLens; Editable = true; - Converter = new DefaultConverter(); - Converter.GetFunc = OnGet; - Converter.SetFunc = OnSet; + Converter = new MudExDefaultConverter(OnSet, OnGet); Class = string.IsNullOrEmpty(Class) || !Class.Contains("mud-ex-color-edit") ? $"{Class} mud-ex-color-edit" : Class; base.OnInitialized(); } @@ -210,7 +208,7 @@ protected override async Task StringValueChangedAsync(string value) if (!Rendered) return; Touched = true; - Value = Converter.Get(value); + Value = ConvertGet(value); if (ForceSelectOfMudColor) await SetTextAsync(ValueString, false); diff --git a/MudBlazor.Extensions/Components/MudExColorPicker.razor.cs b/MudBlazor.Extensions/Components/MudExColorPicker.razor.cs index eeea2c48..2f848456 100644 --- a/MudBlazor.Extensions/Components/MudExColorPicker.razor.cs +++ b/MudBlazor.Extensions/Components/MudExColorPicker.razor.cs @@ -1,6 +1,7 @@ using System.Reflection; using Microsoft.AspNetCore.Components; using MudBlazor.Extensions.Attribute; +using MudBlazor.Extensions.Helper; using MudBlazor.Utilities; namespace MudBlazor.Extensions.Components @@ -53,11 +54,7 @@ public partial class MudExColorPicker /// /// Converter for string and MudColor /// - public MudBlazor.Converter ColorConverter { get; set; } = new() - { - GetFunc = s => new MudColor(s), - SetFunc = c => c.ToString(MudColorOutputFormats.Hex) - }; + public IReversibleConverter ColorConverter { get; set; } = StringMudColorConverter.Instance; /// protected override void OnInitialized() diff --git a/MudBlazor.Extensions/Components/MudExEnumSelect.razor b/MudBlazor.Extensions/Components/MudExEnumSelect.razor index ddc2d45c..8f9ea3fc 100644 --- a/MudBlazor.Extensions/Components/MudExEnumSelect.razor +++ b/MudBlazor.Extensions/Components/MudExEnumSelect.razor @@ -1,6 +1,4 @@ -@using System.Reflection -@using Nextended.Core.Extensions -@typeparam TEnum +@typeparam TEnum @inherits MudExSelect @Inherited() diff --git a/MudBlazor.Extensions/Components/MudExIconPicker.razor.cs b/MudBlazor.Extensions/Components/MudExIconPicker.razor.cs index d1ee8512..09881260 100644 --- a/MudBlazor.Extensions/Components/MudExIconPicker.razor.cs +++ b/MudBlazor.Extensions/Components/MudExIconPicker.razor.cs @@ -132,9 +132,7 @@ private string OnSet(string value) protected override void OnInitialized() { Editable = true; - Converter = new DefaultConverter(); - Converter.GetFunc = OnGetValueFromName; - Converter.SetFunc = OnSet; + Converter = new MudExDefaultConverter(OnSet, OnGetValueFromName); Class = string.IsNullOrEmpty(Class) || !Class.Contains("mud-ex-icon-picker") ? $"{Class} mud-ex-icon-picker" : Class; base.OnInitialized(); } @@ -237,7 +235,7 @@ protected override async Task StringValueChangedAsync(string value) if (!_rendered) return; Touched = true; - Value = IsValueName(value) ? Converter.Get(value) : value; + Value = IsValueName(value) ? ConvertGet(value) : value; await SetTextAsync(AlwaysShowValue ? Value : PropertyName, false); diff --git a/MudBlazor.Extensions/Components/MudExInput.razor.cs b/MudBlazor.Extensions/Components/MudExInput.razor.cs index 14f35a19..0034ed80 100644 --- a/MudBlazor.Extensions/Components/MudExInput.razor.cs +++ b/MudBlazor.Extensions/Components/MudExInput.razor.cs @@ -16,7 +16,7 @@ public partial class MudExInput : MudExBaseInput /// Classname for the component. /// protected string Classname => MudExCss.GetClassname(this, - () => HasNativeHtmlPlaceholder() || ForceShrink || !string.IsNullOrEmpty(Text) || AdornmentStart != null || !string.IsNullOrWhiteSpace(Placeholder) || !string.IsNullOrEmpty(Converter.Set(Value))); + () => HasNativeHtmlPlaceholder() || ForceShrink || !string.IsNullOrEmpty(Text) || AdornmentStart != null || !string.IsNullOrWhiteSpace(Placeholder) || !string.IsNullOrEmpty(ConvertSet(Value))); /// /// Classname for the input element. @@ -135,8 +135,8 @@ protected Task OnInputHandler(ChangeEventArgs args) protected async Task OnChangeHandler(ChangeEventArgs args) { _internalText = args?.Value as string; - await OnInternalInputChanged.InvokeAsync(args); - await Validate(); + await OnInternalInputChanged.InvokeAsync(_internalText); + await ValidateAsync(); if (!Immediate) { @@ -317,7 +317,7 @@ protected override async Task UpdateValuePropertyAsync(bool updateText) /// protected virtual async Task ClearButtonClickHandlerAsync(MouseEventArgs e) { - await SetTextAsync(string.Empty, updateValue: true); + await SetTextAndUpdateValueAsync(string.Empty, updateValue: true); await ElementReference.FocusAsync(); await OnClearButtonClick.InvokeAsync(e); } @@ -330,7 +330,7 @@ public override async Task SetParametersAsync(ParameterView parameters) await base.SetParametersAsync(parameters); //if (!_isFocused || _forceTextUpdate) // _internalText = Text; - if (RuntimeLocation.IsServerSide && TextUpdateSuppression) + if (RuntimeLocation.IsServerSide /*&& TextUpdateSuppression*/) { // Text update suppression, only in BSS (not in WASM). // This is a fix for #1012 diff --git a/MudBlazor.Extensions/Components/MudExList.razor.cs b/MudBlazor.Extensions/Components/MudExList.razor.cs index ab7ff125..ce0f0413 100644 --- a/MudBlazor.Extensions/Components/MudExList.razor.cs +++ b/MudBlazor.Extensions/Components/MudExList.razor.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Components; using MudBlazor.Extensions.Attribute; using MudBlazor.Extensions.Core; +using MudBlazor.Extensions.Helper; using MudBlazor.Extensions.Options; using MudBlazor.Services; using MudBlazor.Utilities; @@ -27,7 +28,7 @@ public partial class MudExList : IDisposable /// public virtual string ItemNameRender(T item) { - var res = ToStringFunc != null ? ToStringFunc(item) : Converter.Set(item); + var res = ToStringFunc != null ? ToStringFunc(item) : Converter.Convert(item); if (!string.IsNullOrWhiteSpace(res) && !string.IsNullOrWhiteSpace(LocalizerPattern)) { res = LocalizerToUse != null ? LocalizerToUse[string.Format(LocalizerPattern, res)] : string.Format(LocalizerPattern, res); @@ -171,7 +172,7 @@ public virtual string ItemNameRender(T item) /// [Parameter, IgnoreOnObjectEdit] [SafeCategory(CategoryTypes.List.Behavior)] - public DefaultConverter Converter { get; set; } = new(); + public IReversibleConverter Converter { get; set; } = new DefaultConverter(); private IEqualityComparer _comparer; @@ -212,10 +213,7 @@ public Func ToStringFunc if (_toStringFunc == value) return; _toStringFunc = value; - Converter = new DefaultConverter - { - SetFunc = _toStringFunc ?? (x => x?.ToString()), - }; + Converter = new MudExDefaultConverter(_toStringFunc ?? (x => x?.ToString()), null); } } @@ -575,7 +573,7 @@ public T SelectedValue get => _selectedValue; set { - if (Converter.Set(_selectedValue) != Converter.Set(default(T)) && !_firstRendered) + if (Converter.Convert(_selectedValue) != Converter.Convert(default(T)) && !_firstRendered) { return; } @@ -1384,7 +1382,7 @@ public async Task ActiveFirstItem(string startChar = null) } // find first item that starts with the letter - var possibleItems = items.Where(x => (x.Text ?? Converter.Set(x.Value) ?? "").StartsWith(startChar, StringComparison.CurrentCultureIgnoreCase)).ToList(); + var possibleItems = items.Where(x => (x.Text ?? Converter.Convert(x.Value) ?? "").StartsWith(startChar, StringComparison.CurrentCultureIgnoreCase)).ToList(); if (!possibleItems.Any()) { if (LastActivatedItem == null) @@ -1568,7 +1566,7 @@ protected ICollection GetSearchedItems() => !SearchBox || ItemCollection == null || SearchString == null ? ItemCollection : SearchFunc != null ? ItemCollection.Where(x => SearchFunc.Invoke(x, SearchString)).ToList() : ItemCollection - .Where(x => Converter.Set(x).Contains(SearchString, StringComparison.InvariantCultureIgnoreCase)) + .Where(x => Converter.Convert(x).Contains(SearchString, StringComparison.InvariantCultureIgnoreCase)) .ToList(); /// diff --git a/MudBlazor.Extensions/Components/MudExPopover.razor.cs b/MudBlazor.Extensions/Components/MudExPopover.razor.cs index b09af22e..db3798a8 100644 --- a/MudBlazor.Extensions/Components/MudExPopover.razor.cs +++ b/MudBlazor.Extensions/Components/MudExPopover.razor.cs @@ -74,7 +74,7 @@ public partial class MudExPopover : IMudExComponent, IAsyncDisposable, IDisposab private string _classId = $"mud-ex-popover-{Guid.NewGuid()}"; private string AnimationStyle() => Open && Animation != AnimationType.Default - ? MudExStyleBuilder.Default.WithAnimation(Animation, TimeSpan.FromMilliseconds(Duration), AnimationDirection.In, AnimationTimingFunction, AnimationPosition).Build() + ? MudExStyleBuilder.Default.WithAnimation(Animation, TimeSpan.FromMilliseconds(Duration ?? 0), AnimationDirection.In, AnimationTimingFunction, AnimationPosition).Build() : string.Empty; diff --git a/MudBlazor.Extensions/Components/MudExSelect.razor b/MudBlazor.Extensions/Components/MudExSelect.razor index 691b8915..26f9e1ae 100644 --- a/MudBlazor.Extensions/Components/MudExSelect.razor +++ b/MudBlazor.Extensions/Components/MudExSelect.razor @@ -175,7 +175,7 @@ { await base.ValidateValue(); if (_validationComponent is not null) { - await _validationComponent.Validate(); + await _validationComponent.ValidateAsync(); } } diff --git a/MudBlazor.Extensions/Components/MudExSelect.razor.cs b/MudBlazor.Extensions/Components/MudExSelect.razor.cs index e74cabcb..d5eaf73c 100644 --- a/MudBlazor.Extensions/Components/MudExSelect.razor.cs +++ b/MudBlazor.Extensions/Components/MudExSelect.razor.cs @@ -40,7 +40,7 @@ public partial class MudExSelect : IMudExSelect, IMudExShadowSelect, IMudExCo /// public virtual string ItemNameRender(T item) { - var res = ToStringFunc != null && item != null ? ToStringFunc(item) : Converter.Set(item); + var res = ToStringFunc != null && item != null ? ToStringFunc(item) : ConvertSet(item); if (!string.IsNullOrWhiteSpace(res) && !string.IsNullOrWhiteSpace(LocalizerPattern)) { return LocalizerToUse != null ? LocalizerToUse[string.Format(LocalizerPattern, item)] : string.Format(LocalizerPattern, res); @@ -685,10 +685,7 @@ public virtual Func ToStringFunc if (_toStringFunc == value) return; _toStringFunc = value; - Converter = new Converter - { - SetFunc = _toStringFunc ?? (x => x?.ToString()), - }; + Converter = new MudExDefaultConverter(_toStringFunc ?? (x => x?.ToString()), null); } } @@ -871,7 +868,7 @@ protected override Task UpdateTextPropertyAsync(bool updateValue) multiSelectionTextFunc: MultiSelectionTextFunc, updateValue: updateValue); } - return SetTextAsync(string.Join(Delimiter, textList), updateValue: updateValue); + return SetTextAndUpdateValueAsync(string.Join(Delimiter, textList), updateValue: updateValue); } @@ -882,7 +879,7 @@ protected override Task UpdateTextPropertyAsync(bool updateValue) _initialSet = true; _selectedValues = new HashSet(_comparer) { Value }; } - return resultItem == null ? SetTextAsync(ItemNameRender(Value), false) : SetTextAsync((!string.IsNullOrEmpty(resultItem.Text) && resultItem.Value is null ? resultItem.Text : ItemNameRender(resultItem.Value)), updateValue: updateValue); + return resultItem == null ? SetTextAndUpdateValueAsync(ItemNameRender(Value), false) : SetTextAndUpdateValueAsync((!string.IsNullOrEmpty(resultItem.Text) && resultItem.Value is null ? resultItem.Text : ItemNameRender(resultItem.Value)), updateValue: updateValue); } bool _initialSet = false; @@ -1207,8 +1204,8 @@ public async Task SelectOption(object obj, bool force = true) // CloseMenu(true) doesn't close popover in BSS await CloseMenu(); - await SetValueAsync(value, force: force); - + await SetValueAndUpdateTextAsync(value, force: force); + _ = _elementReference.SetText(Text); //_selectedValues.Clear(); //_selectedValues.Add(value); @@ -1310,8 +1307,7 @@ public void UnregisterShadowItem(MudExSelectItem item) /// protected async ValueTask SelectClearButtonClickHandlerAsync(MouseEventArgs e) { - await SetValueAsync(default, false); - await SetTextAsync(default, false); + await SetValueAndUpdateTextAsync(default, false, false); _selectedValues.Clear(); SelectedListItem = null; SelectedListItems = null; @@ -1334,8 +1330,8 @@ protected async ValueTask SelectClearButtonClickHandlerAsync(MouseEventArgs e) /// public async Task Clear() { - await SetValueAsync(default, false); - await SetTextAsync(default, false); + await SetValueAndUpdateTextAsync(default, false, false); + _selectedValues.Clear(); await BeginValidateAsync(); StateHasChanged(); diff --git a/MudBlazor.Extensions/Components/MudExTagField.razor b/MudBlazor.Extensions/Components/MudExTagField.razor index e90981a2..59828cc3 100644 --- a/MudBlazor.Extensions/Components/MudExTagField.razor +++ b/MudBlazor.Extensions/Components/MudExTagField.razor @@ -36,7 +36,7 @@ @onmouseout="@(args =>HandleOnChipMouseOut(args, value))" Color="@(ChipColor.IsColor ? ChipColor.AsColor : MudBlazor.Color.Default)" Ripple="false" - Text="@Converter.Set(value)" + Text="@ConvertSet(value)" Value="@value" Variant="@ChipVariant" Size="@ChipSize" diff --git a/MudBlazor.Extensions/Components/MudExTagField.razor.cs b/MudBlazor.Extensions/Components/MudExTagField.razor.cs index ba3e49f2..1b7b0d37 100644 --- a/MudBlazor.Extensions/Components/MudExTagField.razor.cs +++ b/MudBlazor.Extensions/Components/MudExTagField.razor.cs @@ -218,7 +218,7 @@ protected override async Task InvokeKeyDownAsync(KeyboardEventArgs args) if (((Delimiters?.Contains(args.Key[0]) == true && args.Key.Length == 1) || (SetChipsOnEnter && args.Key == "Enter")) && Value != null) await ApplyChips(); - if (args.Key == "Backspace" && string.IsNullOrEmpty(Converter.Set(Value)) && Values?.Any() == true) + if (args.Key == "Backspace" && string.IsNullOrEmpty(ConvertSet(Value)) && Values?.Any() == true) { Values.RemoveAt(Values.Count - 1); await InvokeValuesChanged(); diff --git a/MudBlazor.Extensions/Components/MudExTextField.razor b/MudBlazor.Extensions/Components/MudExTextField.razor index 963ba8f1..0c41bd70 100644 --- a/MudBlazor.Extensions/Components/MudExTextField.razor +++ b/MudBlazor.Extensions/Components/MudExTextField.razor @@ -33,7 +33,6 @@ Adornment="@Adornment" Style="@Style" Variant="@Variant" - TextUpdateSuppression="@TextUpdateSuppression" Value="@Text" ValueChanged="(s) => SetTextAsync(s)" Placeholder="@Placeholder" @@ -79,7 +78,6 @@ Lines="@Lines" Style="@Style" Variant="@Variant" - TextUpdateSuppression="@TextUpdateSuppression" Value="@Text" ValueChanged="OnMaskedValueChanged" Placeholder="@Placeholder" diff --git a/MudBlazor.Extensions/Components/MudExTextField.razor.cs b/MudBlazor.Extensions/Components/MudExTextField.razor.cs index f1c0ac1f..1e79e552 100644 --- a/MudBlazor.Extensions/Components/MudExTextField.razor.cs +++ b/MudBlazor.Extensions/Components/MudExTextField.razor.cs @@ -153,30 +153,29 @@ protected Task OnChange() return base.UpdateValuePropertyAsync(false); } - /// - protected override Task SetValueAsync(T value, bool updateText = true, bool force = false) + protected override Task SetValueAndUpdateTextAsync(T value, bool updateText = true, bool force = false) { if (_mask != null) { - var textValue = Converter.Set(value); + var textValue = ConvertSet(value); _mask.SetText(textValue); textValue = Mask.GetCleanText(); - value = Converter.Get(textValue); + value = ConvertGet(textValue); } - return base.SetValueAsync(value, updateText, force); + return base.SetValueAndUpdateTextAsync(value, updateText, force); } - /// - protected override Task SetTextAsync(string text, bool updateValue = true) + protected override Task SetTextAndUpdateValueAsync(string text, bool updateValue = true) { if (_mask != null) { _mask.SetText(text); text = _mask.Text; } - return base.SetTextAsync(text, updateValue); + return base.SetTextAndUpdateValueAsync(text, updateValue); } + private async Task OnMaskedValueChanged(string s) => await SetTextAsync(s); private string DataVisualiserStyleStr() diff --git a/MudBlazor.Extensions/Components/MudExUploadEdit.razor.cs b/MudBlazor.Extensions/Components/MudExUploadEdit.razor.cs index da1a9ff9..03053e65 100644 --- a/MudBlazor.Extensions/Components/MudExUploadEdit.razor.cs +++ b/MudBlazor.Extensions/Components/MudExUploadEdit.razor.cs @@ -1134,13 +1134,13 @@ private bool SetError(string message = default) private async Task RaiseDataLoadedAsync(T request) { await UploadRequestDataLoaded.InvokeAsync(request); - await Validate(); + await ValidateAsync(); } private async Task RaiseChangedAsync() { await (AllowMultiple ? UploadRequestsChanged.InvokeAsync(UploadRequests) : UploadRequestChanged.InvokeAsync(UploadRequest)); - await Validate(); + await ValidateAsync(); } /// diff --git a/MudBlazor.Extensions/Components/MudExValidationWrapper.razor b/MudBlazor.Extensions/Components/MudExValidationWrapper.razor index 256e6502..61e0c595 100644 --- a/MudBlazor.Extensions/Components/MudExValidationWrapper.razor +++ b/MudBlazor.Extensions/Components/MudExValidationWrapper.razor @@ -22,8 +22,7 @@ _value = value; } } - public MudExValidationWrapper() : base(new DefaultConverter()) { } - + [Parameter] public RenderFragment? ChildContent { get; set; } } \ No newline at end of file diff --git a/MudBlazor.Extensions/Components/ObjectEdit/MudExObjectEdit.razor.cs b/MudBlazor.Extensions/Components/ObjectEdit/MudExObjectEdit.razor.cs index da1f1584..7a982c1d 100644 --- a/MudBlazor.Extensions/Components/ObjectEdit/MudExObjectEdit.razor.cs +++ b/MudBlazor.Extensions/Components/ObjectEdit/MudExObjectEdit.razor.cs @@ -968,7 +968,7 @@ private async Task ShowConfirmationBox() }; return JsRuntime != null ? await DialogService.ShowConfirmationDialogAsync(ResetConfirmationMessageBoxOptions, ResetConfirmationDialogOptions) - : (await DialogService.ShowMessageBox(ResetConfirmationMessageBoxOptions, ResetConfirmationDialogOptions) ?? false); + : (await DialogService.ShowMessageBoxAsync(ResetConfirmationMessageBoxOptions, ResetConfirmationDialogOptions) ?? false); } private string GetStyle() diff --git a/MudBlazor.Extensions/Components/ObjectEdit/MudExObjectEditPicker.razor.cs b/MudBlazor.Extensions/Components/ObjectEdit/MudExObjectEditPicker.razor.cs index c5aaf84a..b8b69b04 100644 --- a/MudBlazor.Extensions/Components/ObjectEdit/MudExObjectEditPicker.razor.cs +++ b/MudBlazor.Extensions/Components/ObjectEdit/MudExObjectEditPicker.razor.cs @@ -2,6 +2,7 @@ using MudBlazor.Extensions.Components.ObjectEdit.Options; using MudBlazor.Extensions.Core; using MudBlazor.Extensions.Options; +using YamlDotNet.Core.Tokens; namespace MudBlazor.Extensions.Components.ObjectEdit; @@ -403,10 +404,15 @@ protected override Task OnPickerClosedAsync() return base.OnPickerClosedAsync(); } - protected override Task WriteValueAsync(T value) + protected override Task WriteTextAsync(string value) { - Text = ToStringFunc(value); - return base.WriteValueAsync(value); + value = ToStringFunc(Value); + return base.WriteTextAsync(value); + } + + protected override string ConvertSet(T input) + { + return ToStringFunc(Value); } /// diff --git a/MudBlazor.Extensions/Components/ObjectEdit/Options/ObjectEditMeta.base.cs b/MudBlazor.Extensions/Components/ObjectEdit/Options/ObjectEditMeta.base.cs index 6e816bca..5ea9a208 100644 --- a/MudBlazor.Extensions/Components/ObjectEdit/Options/ObjectEditMeta.base.cs +++ b/MudBlazor.Extensions/Components/ObjectEdit/Options/ObjectEditMeta.base.cs @@ -94,7 +94,7 @@ internal static bool IsAllowedAsPropertyToEditOnAComponent(PropertyInfo p) if (typeof(T) == typeof(MudChip<>) && p.Name == nameof(MudChip.Value)) return false; // TODO: find out why its so hard crashing without this - var forbiddenTypes = new[] { typeof(EventCallback), typeof(EventCallback<>), typeof(Expression<>), typeof(Func<>), typeof(Converter<>), typeof(Converter<,>), typeof(CultureInfo), typeof(RenderFragment), typeof(RenderFragment<>), typeof(IStringLocalizer<>), typeof(IStringLocalizer) }; + var forbiddenTypes = new[] { typeof(EventCallback), typeof(EventCallback<>), typeof(Expression<>), typeof(Func<>), typeof(Converter<,>), typeof(Converter<,>), typeof(CultureInfo), typeof(RenderFragment), typeof(RenderFragment<>), typeof(IStringLocalizer<>), typeof(IStringLocalizer) }; var isIComponent = typeof(ComponentBase).IsAssignableFrom(p.DeclaringType); return (!isIComponent || p.GetCustomAttribute() != null || p.GetCustomAttribute() != null) && !p.PropertyType.IsFunc() && !p.PropertyType.IsExpression() //&& !p.PropertyType.IsAction() diff --git a/MudBlazor.Extensions/DialogService.cs b/MudBlazor.Extensions/DialogService.cs index d4ef636b..8767afd8 100644 --- a/MudBlazor.Extensions/DialogService.cs +++ b/MudBlazor.Extensions/DialogService.cs @@ -126,7 +126,7 @@ public static IMudExDialogReference Show(this IDialogService d /// The options. /// The interface . public static IMudExDialogReference Show(this IDialogService dialogService, string title, TDialog dialogParameters, DialogOptions options = null) where TDialog : ComponentBase, new() - => dialogService.Show(title, dialogParameters.ConvertToDialogParameters(), options ?? DefaultOptions()).AsMudExDialogReference(); + => dialogService.Show(title, dialogParameters, options ?? DefaultOptions()).AsMudExDialogReference(); /// /// Shows the dialog and injects dependencies immediately. @@ -138,7 +138,7 @@ public static IMudExDialogReference Show(this IDialogService d /// The options. /// The interface . public static IMudExDialogReference Show(this IDialogService dialogService, string title, Action dialogParameters, DialogOptions options = null) where TDialog : ComponentBase, new() - => dialogService.Show(title, dialogParameters.ConvertToDialogParameters(), options ?? DefaultOptions()).AsMudExDialogReference(); + => dialogService.Show(title, dialogParameters, options ?? DefaultOptions()).AsMudExDialogReference(); /// /// Shows the dialog and injects dependencies asynchronously. diff --git a/MudBlazor.Extensions/Helper/StringMudColorConverter.cs b/MudBlazor.Extensions/Helper/StringMudColorConverter.cs new file mode 100644 index 00000000..97067547 --- /dev/null +++ b/MudBlazor.Extensions/Helper/StringMudColorConverter.cs @@ -0,0 +1,42 @@ +using MudBlazor.Utilities; + +namespace MudBlazor.Extensions.Helper; + +public sealed class StringMudColorConverter : IReversibleConverter, IReversibleConverter +{ + public MudColor Convert(string input) + => string.IsNullOrWhiteSpace(input) ? new MudColor("#000000") : new MudColor(input); + + public string ConvertBack(MudColor output) + => output.ToString(MudColorOutputFormats.Hex); + + public static StringMudColorConverter Instance { get; } = new(); + public string Convert(MudColor input) + { + return ConvertBack(input); + } + + public MudColor ConvertBack(string input) + { + return Convert(input); + } +} + +public class MudExDefaultConverter(Func convertFn, Func convertBackFn) : MudExDefaultConverter(convertFn, convertBackFn) +{ + +} + +public class MudExDefaultConverter(Func convertFn, Func convertBackFn) + : IReversibleConverter +{ + public T2 Convert(T input) + { + return convertFn(input); + } + + public T ConvertBack(T2 input) + { + return convertBackFn(input); + } +} \ No newline at end of file diff --git a/MudBlazor.Extensions/MudBlazor.Extensions.csproj b/MudBlazor.Extensions/MudBlazor.Extensions.csproj index a6cdeb1f..fbe55995 100644 --- a/MudBlazor.Extensions/MudBlazor.Extensions.csproj +++ b/MudBlazor.Extensions/MudBlazor.Extensions.csproj @@ -24,9 +24,9 @@ - 8 - 15 - 2 + 9 + 0 + 0 @@ -69,7 +69,7 @@ - + @@ -82,7 +82,7 @@ - + diff --git a/MudBlazor.Extensions/wwwroot/docs/MudBlazor.Extensions.xml b/MudBlazor.Extensions/wwwroot/docs/MudBlazor.Extensions.xml index e9394d83..2ee1a01f 100644 --- a/MudBlazor.Extensions/wwwroot/docs/MudBlazor.Extensions.xml +++ b/MudBlazor.Extensions/wwwroot/docs/MudBlazor.Extensions.xml @@ -504,9 +504,6 @@ - - - Base class for components that require a JS module to be imported and a JS object to be created. @@ -1198,11 +1195,6 @@ Callback when the error state changes - - - Callback when the error changes - - Callback when the validation errors change @@ -6489,6 +6481,16 @@ Gets or sets the aria-labelledby attribute to improve accessibility. + + + Calculates the maximum allowed start position given the current end position and MinLength constraint. + + + + + Calculates the minimum allowed end position given the current start position and MinLength constraint. + + Gets the JavaScript interop arguments required by the component. @@ -8062,12 +8064,6 @@ On change event on the input element - - - - - - Theme preset is just a wrapper class for a name and a theme diff --git a/Samples/MainSample.WebAssembly/MainSample.WebAssembly.csproj b/Samples/MainSample.WebAssembly/MainSample.WebAssembly.csproj index bd7658ce..07aa17c5 100644 --- a/Samples/MainSample.WebAssembly/MainSample.WebAssembly.csproj +++ b/Samples/MainSample.WebAssembly/MainSample.WebAssembly.csproj @@ -48,10 +48,10 @@ - - - - + + + + diff --git a/Samples/MainSample.WebAssembly/wwwroot/index.html b/Samples/MainSample.WebAssembly/wwwroot/index.html index c81f1807..2fa88a75 100644 --- a/Samples/MainSample.WebAssembly/wwwroot/index.html +++ b/Samples/MainSample.WebAssembly/wwwroot/index.html @@ -21,17 +21,17 @@ - + - - - - + + + + - - - + + + @@ -48,8 +48,8 @@ -

MudBlazor.Extensions v8.15.1-prev-2512121913

-

for MudBlazor 8.15.0

+

MudBlazor.Extensions v9.0.0-prev-260113145

+

for MudBlazor 9.0.0-preview.1

@@ -83,8 +83,8 @@

MudBlazor.Extensions v8.15.1-prev-2512121913

- - + + diff --git a/TryMudEx/Try.Core/Try.Core.csproj b/TryMudEx/Try.Core/Try.Core.csproj index 0104d312..16bbb901 100644 --- a/TryMudEx/Try.Core/Try.Core.csproj +++ b/TryMudEx/Try.Core/Try.Core.csproj @@ -6,15 +6,15 @@ - - + + - + - - + + diff --git a/TryMudEx/Try.Tests/Try.Tests.csproj b/TryMudEx/Try.Tests/Try.Tests.csproj index efb63864..ede1b4e0 100644 --- a/TryMudEx/Try.Tests/Try.Tests.csproj +++ b/TryMudEx/Try.Tests/Try.Tests.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/TryMudEx/TryMudEx.Client/TryMudEx.Client.csproj b/TryMudEx/TryMudEx.Client/TryMudEx.Client.csproj index ab982b22..906b0d84 100644 --- a/TryMudEx/TryMudEx.Client/TryMudEx.Client.csproj +++ b/TryMudEx/TryMudEx.Client/TryMudEx.Client.csproj @@ -10,10 +10,10 @@ - - - - + + + + diff --git a/TryMudEx/TryMudEx.Server/TryMudEx.Server.csproj b/TryMudEx/TryMudEx.Server/TryMudEx.Server.csproj index 2199d05e..55ab1580 100644 --- a/TryMudEx/TryMudEx.Server/TryMudEx.Server.csproj +++ b/TryMudEx/TryMudEx.Server/TryMudEx.Server.csproj @@ -7,10 +7,10 @@ - - - - + + + + diff --git a/TryMudEx/UserComponents/Try.UserComponents.csproj b/TryMudEx/UserComponents/Try.UserComponents.csproj index 590d9c2a..84944d7e 100644 --- a/TryMudEx/UserComponents/Try.UserComponents.csproj +++ b/TryMudEx/UserComponents/Try.UserComponents.csproj @@ -4,7 +4,7 @@ net10.0 - + From b6d562a5f316e2d2841c184798b5077a093aa81f Mon Sep 17 00:00:00 2001 From: Aaron Glover Date: Sun, 1 Mar 2026 03:26:43 +1100 Subject: [PATCH 02/11] Mudblazor9 Upgrade (#201) * Initial plan * Update MudBlazor to 9.0.0 and fix breaking API changes - Update MudBlazor from 9.0.0-preview.1 to 9.0.0 - Update MudBlazor.Markdown from 8.11.0 to 9.0.0 - Add IDialogReference.Options and InjectOptions to MudExDialogRef - Add GetDefaultConverter() to MudExBaseFormComponent, MudExPickerBase, MudExValidationWrapper - Replace ForceUpdate() override with ForceRender() in MudExSelect - Replace SetTextAsync with SetTextAndUpdateValueAsync in MudExInput/MudExTextField - Replace SetValueAsync with SetValueAndUpdateTextAsync in MudExSelect - Add Nextended.Core.Helper using to _Imports.razor for ToDescriptionString Co-authored-by: aarondglover <8821892+aarondglover@users.noreply.github.com> * Add net10.0 target to CodeGator Adapter project Co-authored-by: aarondglover <8821892+aarondglover@users.noreply.github.com> * Fix remaining MudBlazor v9 breaking changes and bUnit test API updates - Fix MudThemeProvider.GetSystemPreference -> GetSystemDarkModeAsync in TryMudEx - Fix bUnit RenderComponent -> Render and ToMarkup -> OuterHtml in tests Co-authored-by: aarondglover <8821892+aarondglover@users.noreply.github.com> * Initial plan * Align MudBlazor.Extensions API with MudBlazor v9 async conventions - Rename Show extension methods to ShowAsync (return Task) - Add CloseAnimatedAsync/CancelAnimatedAsync/CloseAnimatedIfAsync/CancelAnimatedIfAsync methods that return Task instead of void - Mark old method names as [Obsolete] pointing to new async versions - Update all internal callers to use new async method names - Update sample code to use new async method names - Create BREAKING_CHANGES.md documenting all changes Co-authored-by: aarondglover <8821892+aarondglover@users.noreply.github.com> * Address code review: properly await async methods instead of discarding Tasks Co-authored-by: aarondglover <8821892+aarondglover@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: aarondglover <8821892+aarondglover@users.noreply.github.com> --- BREAKING_CHANGES.md | 125 ++++++++++++++++++ ...Blazor.Extensions.CodeGator.Adapter.csproj | 2 +- .../Tests/Components/MudExIconPicker.cs | 4 +- .../Tests/Components/MudExRangeSliderTests.cs | 6 +- .../Components/Base/MudExBaseFormComponent.cs | 3 + .../Components/Base/MudExPickerBase.razor.cs | 3 + .../Components/MudExDialog.razor.cs | 8 +- .../Components/MudExInput.razor.cs | 6 +- .../Components/MudExMessageDialog.razor.cs | 2 +- .../Components/MudExSelect.razor.cs | 8 +- .../Components/MudExTextField.razor | 2 +- .../Components/MudExTextField.razor.cs | 2 +- .../Components/MudExValidationWrapper.razor | 3 + .../MudExComponentEditDialog.razor.cs | 4 +- .../ObjectEdit/MudExObjectEditDialog.razor.cs | 4 +- .../Core/IMudExDialogReference.cs | 3 + MudBlazor.Extensions/DialogService.cs | 32 ++--- .../DialogService.obsolete.cs | 18 +++ .../Helper/MudDialogInstanceExtensions.cs | 107 ++++++++++++--- .../MudBlazor.Extensions.csproj | 4 +- .../Services/MudExDialogService.cs | 2 +- MudBlazor.Extensions/_Imports.razor | 1 + .../Pages/SampleDialog.razor | 4 +- .../Pages/SampleTabDialog.razor | 4 +- .../Shared/MainLayout.razor.cs | 2 +- .../TryMudEx.Client/TryMudEx.Client.csproj | 2 +- 26 files changed, 295 insertions(+), 66 deletions(-) create mode 100644 BREAKING_CHANGES.md diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md new file mode 100644 index 00000000..6af1d859 --- /dev/null +++ b/BREAKING_CHANGES.md @@ -0,0 +1,125 @@ +# Breaking Changes - MudBlazor.Extensions v9 + +This document outlines the breaking changes introduced in MudBlazor.Extensions v9 to align with [MudBlazor v9 API conventions](https://github.com/MudBlazor/MudBlazor/issues/12666). + +## Overview + +MudBlazor v9 introduced consistent async naming across its APIs, renaming methods like `Show` to `ShowAsync` and `ShowMessageBox` to `ShowMessageBoxAsync`. MudBlazor.Extensions v9 aligns its own public API with these conventions. + +All renamed methods retain their previous versions marked with `[Obsolete]` to ease migration. The obsolete methods delegate to the new async implementations and will be removed in a future major version. + +--- + +## Dialog Service Extension Methods + +### `Show` → `ShowAsync` + +The `DialogServiceExt.Show` extension methods on `IDialogService` have been renamed to `ShowAsync` and now return `Task>` instead of `IMudExDialogReference`. + +**Reason:** Aligns with MudBlazor v9's rename of `IDialogService.Show` to `IDialogService.ShowAsync`. The new methods also properly call MudBlazor's `ShowAsync` internally, fixing a potential issue with the previous synchronous wrappers. + +**Before:** +```csharp +var dialog = dialogService.Show("Title", dialogParameters, options); +``` + +**After:** +```csharp +var dialog = await dialogService.ShowAsync("Title", dialogParameters, options); +``` + +**Affected overloads:** + +| Old Method | New Method | +|---|---| +| `Show(title, Action, Action)` | `ShowAsync(title, Action, Action)` | +| `Show(title, TDialog, Action)` | `ShowAsync(title, TDialog, Action)` | +| `Show(title, TDialog, DialogOptions)` | `ShowAsync(title, TDialog, DialogOptions)` | +| `Show(title, Action, DialogOptions)` | `ShowAsync(title, Action, DialogOptions)` | + +--- + +## Dialog Close/Cancel Extension Methods + +### `CloseAnimated` → `CloseAnimatedAsync` + +### `CancelAnimated` → `CancelAnimatedAsync` + +### `CloseAnimatedIf` → `CloseAnimatedIfAsync` + +### `CancelAnimatedIf` → `CancelAnimatedIfAsync` + +The `MudDialogInstanceExtensions` close/cancel methods have been renamed with an `Async` suffix and now return `Task` instead of `void`. + +**Reason:** Aligns with .NET and MudBlazor v9 conventions where asynchronous methods use the `Async` suffix. The previous `void` methods discarded the underlying async operation's result, which could mask exceptions. The new methods allow callers to properly `await` the close animation. + +**Before:** +```csharp +MudDialog.CloseAnimatedIf(DialogResult.Ok(true), JsRuntime); +MudDialog.CancelAnimatedIf(JsRuntime); +``` + +**After:** +```csharp +await MudDialog.CloseAnimatedIfAsync(DialogResult.Ok(true), JsRuntime); +await MudDialog.CancelAnimatedIfAsync(JsRuntime); +``` + +**Affected methods on `IMudDialogInstance`:** + +| Old Method | New Method | +|---|---| +| `CloseAnimated(jsRuntime)` | `CloseAnimatedAsync(jsRuntime)` | +| `CancelAnimated(jsRuntime)` | `CancelAnimatedAsync(jsRuntime)` | +| `CloseAnimated(result, jsRuntime)` | `CloseAnimatedAsync(result, jsRuntime)` | +| `CloseAnimated(result, jsRuntime)` | `CloseAnimatedAsync(result, jsRuntime)` | +| `CloseAnimatedIf(jsRuntime)` | `CloseAnimatedIfAsync(jsRuntime)` | +| `CancelAnimatedIf(jsRuntime)` | `CancelAnimatedIfAsync(jsRuntime)` | +| `CloseAnimatedIf(result, jsRuntime)` | `CloseAnimatedIfAsync(result, jsRuntime)` | +| `CloseAnimatedIf(result, jsRuntime)` | `CloseAnimatedIfAsync(result, jsRuntime)` | + +**Affected methods on `IDialogReference`:** + +| Old Method | New Method | +|---|---| +| `CloseAnimated(jsRuntime)` | `CloseAnimatedAsync(jsRuntime)` | +| `CancelAnimated(jsRuntime)` | `CancelAnimatedAsync(jsRuntime)` | +| `CloseAnimated(result, jsRuntime)` | `CloseAnimatedAsync(result, jsRuntime)` | +| `CloseAnimated(result, jsRuntime)` | `CloseAnimatedAsync(result, jsRuntime)` | +| `CloseAnimatedIf(jsRuntime)` | `CloseAnimatedIfAsync(jsRuntime)` | +| `CancelAnimatedIf(jsRuntime)` | `CancelAnimatedIfAsync(jsRuntime)` | +| `CloseAnimatedIf(result, jsRuntime)` | `CloseAnimatedIfAsync(result, jsRuntime)` | +| `CloseAnimatedIf(result, jsRuntime)` | `CloseAnimatedIfAsync(result, jsRuntime)` | + +--- + +## Previously Deprecated Methods (Reminder) + +The following methods were already deprecated in earlier versions and continue to point to their async replacements: + +| Old Method | Replacement | +|---|---| +| `ShowEx(...)` | `ShowExAsync(...)` | +| `ShowMessageBoxEx(...)` | `ShowMessageBoxExAsync(...)` | +| `ShowFileDisplayDialog(...)` | `ShowFileDisplayDialogAsync(...)` | +| `ShowObject(...)` | `ShowObjectAsync(...)` | +| `EditObject(...)` | `EditObjectAsync(...)` | +| `ShowStructuredDataString(...)` | `ShowStructuredDataStringAsync(...)` | +| `EditStructuredDataString(...)` | `EditStructuredDataStringAsync(...)` | + +--- + +## Migration Guide + +1. **Find and replace** method calls in your code: + - `CloseAnimated(` → `CloseAnimatedAsync(` + - `CancelAnimated(` → `CancelAnimatedAsync(` + - `CloseAnimatedIf(` → `CloseAnimatedIfAsync(` + - `CancelAnimatedIf(` → `CancelAnimatedIfAsync(` + - `.Show<` (on `IDialogService`) → `.ShowAsync<` + +2. **Add `await`** to all renamed method calls, since they now return `Task`. + +3. **Update method signatures** if the calling method was `void` — change it to `async Task` or `async void` (for event handlers only). + +4. The old method names still work but will produce compiler warnings. They will be removed in a future major version. diff --git a/MudBlazor.Extensions.CodeGator.Adapter/MudBlazor.Extensions.CodeGator.Adapter.csproj b/MudBlazor.Extensions.CodeGator.Adapter/MudBlazor.Extensions.CodeGator.Adapter.csproj index 308f9a9d..98ce407b 100644 --- a/MudBlazor.Extensions.CodeGator.Adapter/MudBlazor.Extensions.CodeGator.Adapter.csproj +++ b/MudBlazor.Extensions.CodeGator.Adapter/MudBlazor.Extensions.CodeGator.Adapter.csproj @@ -1,7 +1,7 @@  - net8;net9 + net8.0;net9.0;net10.0 enable enable true diff --git a/MudBlazor.Extensions.Tests/UnitTests/Tests/Components/MudExIconPicker.cs b/MudBlazor.Extensions.Tests/UnitTests/Tests/Components/MudExIconPicker.cs index 184fdb65..01501510 100644 --- a/MudBlazor.Extensions.Tests/UnitTests/Tests/Components/MudExIconPicker.cs +++ b/MudBlazor.Extensions.Tests/UnitTests/Tests/Components/MudExIconPicker.cs @@ -39,9 +39,9 @@ public void ShouldDisplaysIcons() context.JSInterop.SetupVoid("mudPopover.initialize", _ => true); context.JSInterop.SetupVoid("mudKeyInterceptor.connect", _ => true); - var cut = context.RenderComponent(); + var cut = context.Render(); cut.Find("button.mud-button-root").Click(); - Assert.NotEmpty(cut.Find(".mud-icon-root")?.ToMarkup() ?? string.Empty); + Assert.NotEmpty(cut.Find(".mud-icon-root")?.OuterHtml ?? string.Empty); } } \ No newline at end of file diff --git a/MudBlazor.Extensions.Tests/UnitTests/Tests/Components/MudExRangeSliderTests.cs b/MudBlazor.Extensions.Tests/UnitTests/Tests/Components/MudExRangeSliderTests.cs index 767a0d85..b5e19477 100644 --- a/MudBlazor.Extensions.Tests/UnitTests/Tests/Components/MudExRangeSliderTests.cs +++ b/MudBlazor.Extensions.Tests/UnitTests/Tests/Components/MudExRangeSliderTests.cs @@ -40,7 +40,7 @@ public void StartThumb_ShouldRespectMinLength_WhenDraggingTowardsEnd() var minLength = new RangeLength(minSpan); var initialValue = new MudExRange(initialStart, initialEnd); - var cut = context.RenderComponent>(parameters => parameters + var cut = context.Render>(parameters => parameters .Add(p => p.SizeRange, sizeRange) .Add(p => p.StepLength, stepLength) .Add(p => p.MinLength, minLength) @@ -93,7 +93,7 @@ public void EndThumb_ShouldRespectMinLength_WhenDraggingTowardsStart() var minLength = new RangeLength(minSpan); var initialValue = new MudExRange(initialStart, initialEnd); - var cut = context.RenderComponent>(parameters => parameters + var cut = context.Render>(parameters => parameters .Add(p => p.SizeRange, sizeRange) .Add(p => p.StepLength, stepLength) .Add(p => p.MinLength, minLength) @@ -142,7 +142,7 @@ public void WithoutMinLength_ThumbsShouldMoveFreelyWithinSizeRange() var stepLength = new RangeLength(stepSize); var initialValue = new MudExRange(initialStart, initialEnd); - var cut = context.RenderComponent>(parameters => parameters + var cut = context.Render>(parameters => parameters .Add(p => p.SizeRange, sizeRange) .Add(p => p.StepLength, stepLength) .Add(p => p.Value, initialValue) diff --git a/MudBlazor.Extensions/Components/Base/MudExBaseFormComponent.cs b/MudBlazor.Extensions/Components/Base/MudExBaseFormComponent.cs index 18d47067..15275864 100644 --- a/MudBlazor.Extensions/Components/Base/MudExBaseFormComponent.cs +++ b/MudBlazor.Extensions/Components/Base/MudExBaseFormComponent.cs @@ -229,6 +229,9 @@ private void OnRenderFinishTimerElapsed(object sender, ElapsedEventArgs e) }); } + /// + protected override IConverter GetDefaultConverter() => new DefaultConverter() as IConverter; + /// /// Refreshes this component and forces render /// diff --git a/MudBlazor.Extensions/Components/Base/MudExPickerBase.razor.cs b/MudBlazor.Extensions/Components/Base/MudExPickerBase.razor.cs index 82661928..2c7ec0d1 100644 --- a/MudBlazor.Extensions/Components/Base/MudExPickerBase.razor.cs +++ b/MudBlazor.Extensions/Components/Base/MudExPickerBase.razor.cs @@ -321,6 +321,9 @@ protected virtual void RaiseChangedIf() RaiseChanged(); } + /// + protected override IConverter GetDefaultConverter() => new DefaultConverter(); + private string GetPopOverStyle() { return MudExStyleBuilder.Default diff --git a/MudBlazor.Extensions/Components/MudExDialog.razor.cs b/MudBlazor.Extensions/Components/MudExDialog.razor.cs index 5c932d93..03c1966f 100644 --- a/MudBlazor.Extensions/Components/MudExDialog.razor.cs +++ b/MudBlazor.Extensions/Components/MudExDialog.razor.cs @@ -161,18 +161,16 @@ private async Task ShowDialogAsync() return result; } - public new Task CloseAsync(DialogResult? result = null) + public new async Task CloseAsync(DialogResult? result = null) { if (_reference is null) { Visible = false; - return Task.CompletedTask; + return; } - _reference.CloseAnimatedIf(result); + await _reference.CloseAnimatedIfAsync(result); _reference = null; - - return Task.CompletedTask; } private string EnsureInitialClass() diff --git a/MudBlazor.Extensions/Components/MudExInput.razor.cs b/MudBlazor.Extensions/Components/MudExInput.razor.cs index 0034ed80..de7c80bf 100644 --- a/MudBlazor.Extensions/Components/MudExInput.razor.cs +++ b/MudBlazor.Extensions/Components/MudExInput.razor.cs @@ -126,7 +126,7 @@ protected Task OnInputHandler(ChangeEventArgs args) { JsRuntime.InvokeVoidAsync("auto_size", ElementReference); } - return SetTextAsync(args?.Value as string); + return SetTextAndUpdateValueAsync(args?.Value as string, true); } /// @@ -140,7 +140,7 @@ protected async Task OnChangeHandler(ChangeEventArgs args) if (!Immediate) { - await SetTextAsync(args?.Value as string); + await SetTextAndUpdateValueAsync(args?.Value as string, true); if (AutoSize) { await JsRuntime.InvokeVoidAsync("auto_size", ElementReference); @@ -352,7 +352,7 @@ public override async Task SetParametersAsync(ParameterView parameters) public Task SetText(string text) { _internalText = text; - return SetTextAsync(text); + return SetTextAndUpdateValueAsync(text, true); } diff --git a/MudBlazor.Extensions/Components/MudExMessageDialog.razor.cs b/MudBlazor.Extensions/Components/MudExMessageDialog.razor.cs index d818da77..23931c76 100644 --- a/MudBlazor.Extensions/Components/MudExMessageDialog.razor.cs +++ b/MudBlazor.Extensions/Components/MudExMessageDialog.razor.cs @@ -220,7 +220,7 @@ void Submit(DialogResult result) /// /// Cancels the dialog /// - void Cancel() => MudDialog.CloseAnimatedIf(JsRuntime); + async Task Cancel() => await MudDialog.CloseAnimatedIfAsync(JsRuntime); public override ValueTask DisposeAsync() { diff --git a/MudBlazor.Extensions/Components/MudExSelect.razor.cs b/MudBlazor.Extensions/Components/MudExSelect.razor.cs index d5eaf73c..cc390e56 100644 --- a/MudBlazor.Extensions/Components/MudExSelect.razor.cs +++ b/MudBlazor.Extensions/Components/MudExSelect.razor.cs @@ -721,7 +721,7 @@ public IEnumerable SelectedValues if (NeedsValueUpdateForNonMultiSelection()) // No binding so we need to update the value manually { - _ = SetValueAsync(_selectedValues.LastOrDefault()); + _ = SetValueAndUpdateTextAsync(_selectedValues.LastOrDefault(), true, false); //Value = _selectedValues.LastOrDefault(); } @@ -914,7 +914,7 @@ protected override void OnInitialized() else if (MultiSelection && SelectedValues != null) { // TODO: Check this line again - _ = SetValueAsync(SelectedValues.FirstOrDefault()); + _ = SetValueAndUpdateTextAsync(SelectedValues.FirstOrDefault(), true, false); } } @@ -1222,9 +1222,9 @@ public async Task SelectOption(object obj, bool force = true) /// Force the component to update. /// /// - public override async Task ForceUpdate() + public async Task ForceUpdate() { - await base.ForceUpdate(); + ForceRender(forceTextUpdate: true); if (!MultiSelection) { SelectedValues = new HashSet(_comparer) { Value }; diff --git a/MudBlazor.Extensions/Components/MudExTextField.razor b/MudBlazor.Extensions/Components/MudExTextField.razor index 0c41bd70..90d8d0cb 100644 --- a/MudBlazor.Extensions/Components/MudExTextField.razor +++ b/MudBlazor.Extensions/Components/MudExTextField.razor @@ -34,7 +34,7 @@ Style="@Style" Variant="@Variant" Value="@Text" - ValueChanged="(s) => SetTextAsync(s)" + ValueChanged="(s) => SetTextAndUpdateValueAsync(s, true)" Placeholder="@Placeholder" Disabled=@Disabled Underline="@Underline" diff --git a/MudBlazor.Extensions/Components/MudExTextField.razor.cs b/MudBlazor.Extensions/Components/MudExTextField.razor.cs index 1e79e552..c56d1621 100644 --- a/MudBlazor.Extensions/Components/MudExTextField.razor.cs +++ b/MudBlazor.Extensions/Components/MudExTextField.razor.cs @@ -176,7 +176,7 @@ protected override Task SetTextAndUpdateValueAsync(string text, bool updateValue } - private async Task OnMaskedValueChanged(string s) => await SetTextAsync(s); + private async Task OnMaskedValueChanged(string s) => await SetTextAndUpdateValueAsync(s, true); private string DataVisualiserStyleStr() { diff --git a/MudBlazor.Extensions/Components/MudExValidationWrapper.razor b/MudBlazor.Extensions/Components/MudExValidationWrapper.razor index 61e0c595..9e9fca35 100644 --- a/MudBlazor.Extensions/Components/MudExValidationWrapper.razor +++ b/MudBlazor.Extensions/Components/MudExValidationWrapper.razor @@ -8,6 +8,9 @@ @code { + /// + protected override IConverter GetDefaultConverter() => new DefaultConverter(); + /// /// The value of the ValidationWrapper component. This will be passed to the set validation function. /// diff --git a/MudBlazor.Extensions/Components/ObjectEdit/MudExComponentEditDialog.razor.cs b/MudBlazor.Extensions/Components/ObjectEdit/MudExComponentEditDialog.razor.cs index 3227d5d8..1adf1547 100644 --- a/MudBlazor.Extensions/Components/ObjectEdit/MudExComponentEditDialog.razor.cs +++ b/MudBlazor.Extensions/Components/ObjectEdit/MudExComponentEditDialog.razor.cs @@ -129,7 +129,7 @@ protected override async Task OnSubmit(EditContext ctx) await base.OnSubmit(ctx); if (CustomSubmit == null || string.IsNullOrWhiteSpace(_errorMessage = await CustomSubmit.Invoke(Value, this))) { - MudDialog.CloseAnimatedIf(DialogResult.Ok(Value), JsRuntime); + await MudDialog.CloseAnimatedIfAsync(DialogResult.Ok(Value), JsRuntime); } } finally @@ -145,6 +145,6 @@ protected override async Task OnSubmit(EditContext ctx) protected override async Task Cancel() { await base.Cancel(); - MudDialog.CancelAnimatedIf(JsRuntime); + await MudDialog.CancelAnimatedIfAsync(JsRuntime); } } \ No newline at end of file diff --git a/MudBlazor.Extensions/Components/ObjectEdit/MudExObjectEditDialog.razor.cs b/MudBlazor.Extensions/Components/ObjectEdit/MudExObjectEditDialog.razor.cs index 71a95e90..44c4e7e3 100644 --- a/MudBlazor.Extensions/Components/ObjectEdit/MudExObjectEditDialog.razor.cs +++ b/MudBlazor.Extensions/Components/ObjectEdit/MudExObjectEditDialog.razor.cs @@ -130,7 +130,7 @@ protected override async Task OnSubmit(EditContext ctx) await base.OnSubmit(ctx); if (CustomSubmit == null || string.IsNullOrWhiteSpace(_errorMessage = await CustomSubmit.Invoke(Value, this))) { - MudDialog.CloseAnimatedIf(DialogResult.Ok(Value), JsRuntime); + await MudDialog.CloseAnimatedIfAsync(DialogResult.Ok(Value), JsRuntime); } } finally @@ -146,6 +146,6 @@ protected override async Task OnSubmit(EditContext ctx) protected override async Task Cancel() { await base.Cancel(); - MudDialog.CancelAnimatedIf(JsRuntime); + await MudDialog.CancelAnimatedIfAsync(JsRuntime); } } \ No newline at end of file diff --git a/MudBlazor.Extensions/Core/IMudExDialogReference.cs b/MudBlazor.Extensions/Core/IMudExDialogReference.cs index d1cdbdb6..28c7df4c 100644 --- a/MudBlazor.Extensions/Core/IMudExDialogReference.cs +++ b/MudBlazor.Extensions/Core/IMudExDialogReference.cs @@ -124,6 +124,9 @@ public RenderFragment RenderFragment public TaskCompletionSource RenderCompleteTaskCompletionSource => DialogReference.RenderCompleteTaskCompletionSource; + public DialogOptions Options => DialogReference.Options; + public void InjectOptions(DialogOptions options) => DialogReference.InjectOptions(options); + public object Dialog => DialogReference.Dialog; public T DialogComponent => Dialog as T; diff --git a/MudBlazor.Extensions/DialogService.cs b/MudBlazor.Extensions/DialogService.cs index 8767afd8..b675c1e9 100644 --- a/MudBlazor.Extensions/DialogService.cs +++ b/MudBlazor.Extensions/DialogService.cs @@ -83,62 +83,62 @@ public static Task> ShowExAsync(this IDi => dialogService.ShowExAsync(title, dialogParameters.ConvertToDialogParameters(), optionsEx ?? DefaultOptions()); /// - /// Shows the dialog and injects dependencies immediately. + /// Shows the dialog asynchronously. /// /// The dialog type. /// The dialog service. /// The title. /// The dialog parameters. /// The options. - /// The interface . - public static IMudExDialogReference Show(this IDialogService dialogService, string title, Action dialogParameters, Action options) + /// A . + public static async Task> ShowAsync(this IDialogService dialogService, string title, Action dialogParameters, Action options) where TDialog : ComponentBase, new() { var dlgOptions = DefaultOptions(); options(dlgOptions); - return Show(dialogService, title, dialogParameters, dlgOptions); + return await ShowAsync(dialogService, title, dialogParameters, dlgOptions); } /// - /// Shows the dialog and injects dependencies immediately. + /// Shows the dialog asynchronously. /// /// The dialog type. /// The dialog service. /// The title. /// The dialog parameters. /// The options. - /// The interface . - public static IMudExDialogReference Show(this IDialogService dialogService, string title, TDialog dialogParameters, Action options) + /// A . + public static async Task> ShowAsync(this IDialogService dialogService, string title, TDialog dialogParameters, Action options) where TDialog : ComponentBase, new() { var dlgOptions = DefaultOptions(); options(dlgOptions); - return dialogService.Show(title, dialogParameters, dlgOptions).AsMudExDialogReference(); + return (await dialogService.ShowAsync(title, dialogParameters.ConvertToDialogParameters(), dlgOptions)).AsMudExDialogReference(); } /// - /// Shows the dialog and injects dependencies immediately. + /// Shows the dialog asynchronously. /// /// The dialog type. /// The dialog service. /// The title. /// The dialog parameters. /// The options. - /// The interface . - public static IMudExDialogReference Show(this IDialogService dialogService, string title, TDialog dialogParameters, DialogOptions options = null) where TDialog : ComponentBase, new() - => dialogService.Show(title, dialogParameters, options ?? DefaultOptions()).AsMudExDialogReference(); + /// A . + public static async Task> ShowAsync(this IDialogService dialogService, string title, TDialog dialogParameters, DialogOptions options = null) where TDialog : ComponentBase, new() + => (await dialogService.ShowAsync(title, dialogParameters.ConvertToDialogParameters(), options ?? DefaultOptions())).AsMudExDialogReference(); /// - /// Shows the dialog and injects dependencies immediately. + /// Shows the dialog asynchronously. /// /// The dialog type. /// The dialog service. /// The title. /// The dialog parameters. /// The options. - /// The interface . - public static IMudExDialogReference Show(this IDialogService dialogService, string title, Action dialogParameters, DialogOptions options = null) where TDialog : ComponentBase, new() - => dialogService.Show(title, dialogParameters, options ?? DefaultOptions()).AsMudExDialogReference(); + /// A . + public static async Task> ShowAsync(this IDialogService dialogService, string title, Action dialogParameters, DialogOptions options = null) where TDialog : ComponentBase, new() + => (await dialogService.ShowAsync(title, dialogParameters.ConvertToDialogParameters(), options ?? DefaultOptions())).AsMudExDialogReference(); /// /// Shows the dialog and injects dependencies asynchronously. diff --git a/MudBlazor.Extensions/DialogService.obsolete.cs b/MudBlazor.Extensions/DialogService.obsolete.cs index 725b667d..4f7f8a9c 100644 --- a/MudBlazor.Extensions/DialogService.obsolete.cs +++ b/MudBlazor.Extensions/DialogService.obsolete.cs @@ -54,7 +54,25 @@ public static Task ShowEx(this IDialogService dialogService, T public static Task ShowEx(this IDialogService dialogService, Type type, string title, DialogOptionsEx options = null) => ShowExAsync(dialogService, type, title, options); + [Obsolete("Use ShowAsync instead.")] + public static Task> Show(this IDialogService dialogService, string title, Action dialogParameters, Action options) + where TDialog : ComponentBase, new() + => ShowAsync(dialogService, title, dialogParameters, options); + + [Obsolete("Use ShowAsync instead.")] + public static Task> Show(this IDialogService dialogService, string title, TDialog dialogParameters, Action options) + where TDialog : ComponentBase, new() + => ShowAsync(dialogService, title, dialogParameters, options); + [Obsolete("Use ShowAsync instead.")] + public static Task> Show(this IDialogService dialogService, string title, TDialog dialogParameters, DialogOptions options = null) + where TDialog : ComponentBase, new() + => ShowAsync(dialogService, title, dialogParameters, options); + + [Obsolete("Use ShowAsync instead.")] + public static Task> Show(this IDialogService dialogService, string title, Action dialogParameters, DialogOptions options = null) + where TDialog : ComponentBase, new() + => ShowAsync(dialogService, title, dialogParameters, options); [Obsolete("Use ShowFileDisplayDialogAsync instead.")] public static Task> ShowFileDisplayDialog(this IDialogService dialogService, string url, DialogOptionsEx options = null, DialogParameters dialogParameters = null) diff --git a/MudBlazor.Extensions/Helper/MudDialogInstanceExtensions.cs b/MudBlazor.Extensions/Helper/MudDialogInstanceExtensions.cs index d8c0bb1c..ae2505ee 100644 --- a/MudBlazor.Extensions/Helper/MudDialogInstanceExtensions.cs +++ b/MudBlazor.Extensions/Helper/MudDialogInstanceExtensions.cs @@ -4,52 +4,127 @@ namespace MudBlazor.Extensions.Helper; public static class MudDialogInstanceExtensions { + // --- IMudDialogInstance async methods --- + + public static Task CloseAnimatedAsync(this IMudDialogInstance instance, IJSRuntime jsRuntime = null) + => AnimateClose(instance, (i) => { i.Close(); return Task.CompletedTask; }, jsRuntime); + + public static Task CancelAnimatedAsync(this IMudDialogInstance instance, IJSRuntime jsRuntime = null) + => AnimateClose(instance, (i) => { i.Cancel(); return Task.CompletedTask; }, jsRuntime); + + public static Task CloseAnimatedAsync(this IMudDialogInstance instance, DialogResult result, IJSRuntime jsRuntime = null) + => AnimateClose(instance, (i) => { i.Close(result); return Task.CompletedTask; }, jsRuntime); + + public static Task CloseAnimatedAsync(this IMudDialogInstance instance, T result, IJSRuntime jsRuntime = null) + => AnimateClose(instance, (i) => { i.Close(result); return Task.CompletedTask; }, jsRuntime); + + public static Task CloseAnimatedIfAsync(this IMudDialogInstance instance, IJSRuntime jsRuntime = null) + => AnimateClose(instance, (i) => { i.Close(); return Task.CompletedTask; }, jsRuntime, true); + + public static Task CancelAnimatedIfAsync(this IMudDialogInstance instance, IJSRuntime jsRuntime = null) + => AnimateClose(instance, (i) => { i.Cancel(); return Task.CompletedTask; }, jsRuntime, true); + + public static Task CloseAnimatedIfAsync(this IMudDialogInstance instance, DialogResult result, IJSRuntime jsRuntime = null) + => AnimateClose(instance, (i) => { i.Close(result); return Task.CompletedTask; }, jsRuntime, true); + + public static Task CloseAnimatedIfAsync(this IMudDialogInstance instance, T result, IJSRuntime jsRuntime = null) + => AnimateClose(instance, (i) => { i.Close(result); return Task.CompletedTask; }, jsRuntime, true); + + // --- IDialogReference async methods --- + + public static Task CloseAnimatedAsync(this IDialogReference instance, IJSRuntime jsRuntime = null) + => AnimateClose(instance, (i) => { i.Close(); return Task.CompletedTask; }, jsRuntime); + + public static Task CancelAnimatedAsync(this IDialogReference instance, IJSRuntime jsRuntime = null) + => AnimateClose(instance, (i) => { i.Close(DialogResult.Cancel()); return Task.CompletedTask; }, jsRuntime); + + public static Task CloseAnimatedAsync(this IDialogReference instance, DialogResult result, IJSRuntime jsRuntime = null) + => AnimateClose(instance, (i) => { i.Close(result); return Task.CompletedTask; }, jsRuntime); + + public static Task CloseAnimatedAsync(this IDialogReference instance, T result, IJSRuntime jsRuntime = null) + => AnimateClose(instance, (i) => { i.Close(DialogResult.Ok(result)); return Task.CompletedTask; }, jsRuntime); + + public static Task CloseAnimatedIfAsync(this IDialogReference instance, IJSRuntime jsRuntime = null) + => AnimateClose(instance, (i) => { i.Close(); return Task.CompletedTask; }, jsRuntime, true); + + public static Task CancelAnimatedIfAsync(this IDialogReference instance, IJSRuntime jsRuntime = null) + => AnimateClose(instance, (i) => { i.Close(DialogResult.Cancel()); return Task.CompletedTask; }, jsRuntime, true); + + public static Task CloseAnimatedIfAsync(this IDialogReference instance, DialogResult result, IJSRuntime jsRuntime = null) + => AnimateClose(instance, (i) => { i.Close(result); return Task.CompletedTask; }, jsRuntime, true); + + public static Task CloseAnimatedIfAsync(this IDialogReference instance, T result, IJSRuntime jsRuntime = null) + => AnimateClose(instance, (i) => { i.Close(DialogResult.Ok(result)); return Task.CompletedTask; }, jsRuntime, true); + + // --- IMudDialogInstance obsolete methods --- + + [Obsolete("Use CloseAnimatedAsync instead.")] public static void CloseAnimated(this IMudDialogInstance instance, IJSRuntime jsRuntime = null) - => _ = AnimateClose(instance, (i) => { i.Close(); return Task.CompletedTask; }, jsRuntime); + => _ = CloseAnimatedAsync(instance, jsRuntime); + [Obsolete("Use CancelAnimatedAsync instead.")] public static void CancelAnimated(this IMudDialogInstance instance, IJSRuntime jsRuntime = null) - => _ = AnimateClose(instance, (i) => { i.Cancel(); return Task.CompletedTask; }, jsRuntime); + => _ = CancelAnimatedAsync(instance, jsRuntime); + [Obsolete("Use CloseAnimatedAsync instead.")] public static void CloseAnimated(this IMudDialogInstance instance, DialogResult result, IJSRuntime jsRuntime = null) - => _ = AnimateClose(instance, (i) => { i.Close(result); return Task.CompletedTask; }, jsRuntime); + => _ = CloseAnimatedAsync(instance, result, jsRuntime); + + [Obsolete("Use CloseAnimatedAsync instead.")] public static void CloseAnimated(this IMudDialogInstance instance, T result, IJSRuntime jsRuntime = null) - => _ = AnimateClose(instance, (i) => { i.Close(result); return Task.CompletedTask; }, jsRuntime); + => _ = CloseAnimatedAsync(instance, result, jsRuntime); + [Obsolete("Use CloseAnimatedIfAsync instead.")] public static void CloseAnimatedIf(this IMudDialogInstance instance, IJSRuntime jsRuntime = null) - => _ = AnimateClose(instance, (i) => { i.Close(); return Task.CompletedTask; }, jsRuntime, true); + => _ = CloseAnimatedIfAsync(instance, jsRuntime); + [Obsolete("Use CancelAnimatedIfAsync instead.")] public static void CancelAnimatedIf(this IMudDialogInstance instance, IJSRuntime jsRuntime = null) - => _ = AnimateClose(instance, (i) => { i.Cancel(); return Task.CompletedTask; }, jsRuntime, true); + => _ = CancelAnimatedIfAsync(instance, jsRuntime); + [Obsolete("Use CloseAnimatedIfAsync instead.")] public static void CloseAnimatedIf(this IMudDialogInstance instance, DialogResult result, IJSRuntime jsRuntime = null) - => _ = AnimateClose(instance, (i) => { i.Close(result); return Task.CompletedTask; }, jsRuntime, true); + => _ = CloseAnimatedIfAsync(instance, result, jsRuntime); + [Obsolete("Use CloseAnimatedIfAsync instead.")] public static void CloseAnimatedIf(this IMudDialogInstance instance, T result, IJSRuntime jsRuntime = null) - => _ = AnimateClose(instance, (i) => { i.Close(result); return Task.CompletedTask; }, jsRuntime, true); + => _ = CloseAnimatedIfAsync(instance, result, jsRuntime); + // --- IDialogReference obsolete methods --- + [Obsolete("Use CloseAnimatedAsync instead.")] public static void CloseAnimated(this IDialogReference instance, IJSRuntime jsRuntime = null) - => _ = AnimateClose(instance, (i) => { i.Close(); return Task.CompletedTask; }, jsRuntime); + => _ = CloseAnimatedAsync(instance, jsRuntime); + [Obsolete("Use CancelAnimatedAsync instead.")] public static void CancelAnimated(this IDialogReference instance, IJSRuntime jsRuntime = null) - => _ = AnimateClose(instance, (i) => { i.Close(DialogResult.Cancel()); return Task.CompletedTask; }, jsRuntime); + => _ = CancelAnimatedAsync(instance, jsRuntime); + [Obsolete("Use CloseAnimatedAsync instead.")] public static void CloseAnimated(this IDialogReference instance, DialogResult result, IJSRuntime jsRuntime = null) - => _ = AnimateClose(instance, (i) => { i.Close(result); return Task.CompletedTask; }, jsRuntime); + => _ = CloseAnimatedAsync(instance, result, jsRuntime); + + [Obsolete("Use CloseAnimatedAsync instead.")] public static void CloseAnimated(this IDialogReference instance, T result, IJSRuntime jsRuntime = null) - => _ = AnimateClose(instance, (i) => { i.Close(DialogResult.Ok(result)); return Task.CompletedTask; }, jsRuntime); + => _ = CloseAnimatedAsync(instance, result, jsRuntime); + [Obsolete("Use CloseAnimatedIfAsync instead.")] public static void CloseAnimatedIf(this IDialogReference instance, IJSRuntime jsRuntime = null) - => _ = AnimateClose(instance, (i) => { i.Close(); return Task.CompletedTask; }, jsRuntime, true); + => _ = CloseAnimatedIfAsync(instance, jsRuntime); + [Obsolete("Use CancelAnimatedIfAsync instead.")] public static void CancelAnimatedIf(this IDialogReference instance, IJSRuntime jsRuntime = null) - => _ = AnimateClose(instance, (i) => { i.Close(DialogResult.Cancel()); return Task.CompletedTask; }, jsRuntime, true); + => _ = CancelAnimatedIfAsync(instance, jsRuntime); + [Obsolete("Use CloseAnimatedIfAsync instead.")] public static void CloseAnimatedIf(this IDialogReference instance, DialogResult result, IJSRuntime jsRuntime = null) - => _ = AnimateClose(instance, (i) => { i.Close(result); return Task.CompletedTask; }, jsRuntime, true); + => _ = CloseAnimatedIfAsync(instance, result, jsRuntime); + [Obsolete("Use CloseAnimatedIfAsync instead.")] public static void CloseAnimatedIf(this IDialogReference instance, T result, IJSRuntime jsRuntime = null) - => _ = AnimateClose(instance, (i) => { i.Close(DialogResult.Ok(result)); return Task.CompletedTask; }, jsRuntime, true); + => _ = CloseAnimatedIfAsync(instance, result, jsRuntime); + + // --- Private helpers --- private static async Task AnimateClose(this IMudDialogInstance instance, Func callback, IJSRuntime jsRuntime = null, bool checkOptions = false) { diff --git a/MudBlazor.Extensions/MudBlazor.Extensions.csproj b/MudBlazor.Extensions/MudBlazor.Extensions.csproj index fbe55995..26f5e79a 100644 --- a/MudBlazor.Extensions/MudBlazor.Extensions.csproj +++ b/MudBlazor.Extensions/MudBlazor.Extensions.csproj @@ -69,12 +69,12 @@ - + - + diff --git a/MudBlazor.Extensions/Services/MudExDialogService.cs b/MudBlazor.Extensions/Services/MudExDialogService.cs index aa123afa..020fa973 100644 --- a/MudBlazor.Extensions/Services/MudExDialogService.cs +++ b/MudBlazor.Extensions/Services/MudExDialogService.cs @@ -74,7 +74,7 @@ private void WorkaroundClosingNonModalDialog(ComponentBase dialogComponent) MethodInfo removeMethod = dialogs.FieldType.GetMethod("Remove", BindingFlags.Public | BindingFlags.Instance); removeMethod?.Invoke(dialogs.GetValue(provider), new object[] { dialogRef }); - dialogRef.CloseAnimatedIf(); + _ = dialogRef.CloseAnimatedIfAsync(); } } diff --git a/MudBlazor.Extensions/_Imports.razor b/MudBlazor.Extensions/_Imports.razor index 71dd584d..2b588979 100644 --- a/MudBlazor.Extensions/_Imports.razor +++ b/MudBlazor.Extensions/_Imports.razor @@ -5,6 +5,7 @@ @using MudBlazor.Extensions @using MudBlazor @using Nextended.Blazor +@using Nextended.Core.Helper @using Blazored.FluentValidation; @using BlazorJS @using Microsoft.AspNetCore.Components.Web.Virtualization \ No newline at end of file diff --git a/Samples/MainSample.WebAssembly/Pages/SampleDialog.razor b/Samples/MainSample.WebAssembly/Pages/SampleDialog.razor index 24dd7b97..e36a5faa 100644 --- a/Samples/MainSample.WebAssembly/Pages/SampleDialog.razor +++ b/Samples/MainSample.WebAssembly/Pages/SampleDialog.razor @@ -24,8 +24,8 @@ [CascadingParameter] IMudDialogInstance MudDialog { get; set; } [Parameter] public string ContentMessage { get; set; } - void Submit() => MudDialog.CloseAnimatedIf(DialogResult.Ok(true)); - void Cancel() => MudDialog.CancelAnimatedIf(); + async Task Submit() => await MudDialog.CloseAnimatedIfAsync(DialogResult.Ok(true)); + async Task Cancel() => await MudDialog.CancelAnimatedIfAsync(); string icon; string title; diff --git a/Samples/MainSample.WebAssembly/Pages/SampleTabDialog.razor b/Samples/MainSample.WebAssembly/Pages/SampleTabDialog.razor index 469e5f46..e9f2b171 100644 --- a/Samples/MainSample.WebAssembly/Pages/SampleTabDialog.razor +++ b/Samples/MainSample.WebAssembly/Pages/SampleTabDialog.razor @@ -32,6 +32,6 @@ @code { [CascadingParameter] IMudDialogInstance MudDialog { get; set; } - void Submit() => MudDialog.CloseAnimatedIf(DialogResult.Ok(true)); - void Cancel() => MudDialog.CancelAnimatedIf(); + async Task Submit() => await MudDialog.CloseAnimatedIfAsync(DialogResult.Ok(true)); + async Task Cancel() => await MudDialog.CancelAnimatedIfAsync(); } \ No newline at end of file diff --git a/TryMudEx/TryMudEx.Client/Shared/MainLayout.razor.cs b/TryMudEx/TryMudEx.Client/Shared/MainLayout.razor.cs index b81c35aa..d44b7321 100644 --- a/TryMudEx/TryMudEx.Client/Shared/MainLayout.razor.cs +++ b/TryMudEx/TryMudEx.Client/Shared/MainLayout.razor.cs @@ -42,7 +42,7 @@ private async Task ApplyUserPreferences() } else { - var defaultDarkMode = await _mudThemeProvider.GetSystemPreference(); + var defaultDarkMode = await _mudThemeProvider.GetSystemDarkModeAsync(); await LayoutService.ApplyUserPreferences(defaultDarkMode); } } diff --git a/TryMudEx/TryMudEx.Client/TryMudEx.Client.csproj b/TryMudEx/TryMudEx.Client/TryMudEx.Client.csproj index 906b0d84..063815af 100644 --- a/TryMudEx/TryMudEx.Client/TryMudEx.Client.csproj +++ b/TryMudEx/TryMudEx.Client/TryMudEx.Client.csproj @@ -14,7 +14,7 @@ - + From 15eb7a296983e43582121db30f0d5045cb2bb07d Mon Sep 17 00:00:00 2001 From: Florian Gilde Date: Sat, 28 Feb 2026 17:44:15 +0100 Subject: [PATCH 03/11] fix mudexselect error --- .../Helper/StringMudColorConverter.cs | 2 ++ .../wwwroot/docs/MudBlazor.Extensions.xml | 33 ++++++++++++------- .../MainSample.WebAssembly/wwwroot/index.html | 24 +++++++------- 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/MudBlazor.Extensions/Helper/StringMudColorConverter.cs b/MudBlazor.Extensions/Helper/StringMudColorConverter.cs index 97067547..a32f9cc7 100644 --- a/MudBlazor.Extensions/Helper/StringMudColorConverter.cs +++ b/MudBlazor.Extensions/Helper/StringMudColorConverter.cs @@ -32,11 +32,13 @@ public class MudExDefaultConverter(Func convertFn, Func con { public T2 Convert(T input) { + if (input is null) return default; return convertFn(input); } public T ConvertBack(T2 input) { + if (input is null || convertBackFn is null) return default; return convertBackFn(input); } } \ No newline at end of file diff --git a/MudBlazor.Extensions/wwwroot/docs/MudBlazor.Extensions.xml b/MudBlazor.Extensions/wwwroot/docs/MudBlazor.Extensions.xml index 2ee1a01f..1f56c42b 100644 --- a/MudBlazor.Extensions/wwwroot/docs/MudBlazor.Extensions.xml +++ b/MudBlazor.Extensions/wwwroot/docs/MudBlazor.Extensions.xml @@ -495,6 +495,9 @@ + + + Refreshes this component and forces render @@ -765,6 +768,9 @@ Calls the method if picker is closed or is false. + + + Component to display information about the current recording @@ -16054,6 +16060,9 @@ Is false control isn't rendered at all + + + The value of the ValidationWrapper component. This will be passed to the set validation function. @@ -19445,49 +19454,49 @@ The options. The interface . - + - Shows the dialog and injects dependencies immediately. + Shows the dialog asynchronously. The dialog type. The dialog service. The title. The dialog parameters. The options. - The interface . + A . - + - Shows the dialog and injects dependencies immediately. + Shows the dialog asynchronously. The dialog type. The dialog service. The title. The dialog parameters. The options. - The interface . + A . - + - Shows the dialog and injects dependencies immediately. + Shows the dialog asynchronously. The dialog type. The dialog service. The title. The dialog parameters. The options. - The interface . + A . - + - Shows the dialog and injects dependencies immediately. + Shows the dialog asynchronously. The dialog type. The dialog service. The title. The dialog parameters. The options. - The interface . + A . diff --git a/Samples/MainSample.WebAssembly/wwwroot/index.html b/Samples/MainSample.WebAssembly/wwwroot/index.html index 2fa88a75..41b840a3 100644 --- a/Samples/MainSample.WebAssembly/wwwroot/index.html +++ b/Samples/MainSample.WebAssembly/wwwroot/index.html @@ -21,17 +21,17 @@ - + - - - - + + + + - - - + + + @@ -48,8 +48,8 @@ -

MudBlazor.Extensions v9.0.0-prev-260113145

-

for MudBlazor 9.0.0-preview.1

+

MudBlazor.Extensions v9.0.0-prev-2602281741

+

for MudBlazor 9.0.0

@@ -83,8 +83,8 @@

MudBlazor.Extensions v9.0.0-prev-260113145

- - + + From 419d5e9373a10f4d3614233274758798c20c65ad Mon Sep 17 00:00:00 2001 From: Florian Gilde Date: Sat, 28 Feb 2026 17:48:15 +0100 Subject: [PATCH 04/11] git ignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ece376f8..9eb95c96 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ MudBlazor.Extensions/wwwroot/**/*.min.js # Generated files MudBlazor.Extensions/version.generated.props MudBlazor.Extensions/*Undefined*/ +/.claude From 11333999ee95742ab0365d0d1310d803437d5c34 Mon Sep 17 00:00:00 2001 From: Florian Gilde Date: Sat, 28 Feb 2026 18:45:01 +0100 Subject: [PATCH 05/11] update packages --- .../Components/MudExRangeSlider.razor.cs | 3 ++- .../Helper/ArchiveConverter.cs | 2 +- .../MudBlazor.Extensions.csproj | 8 +++---- .../Services/MudExFileService.cs | 2 +- .../wwwroot/js/components/MudExRangeSlider.js | 6 ++++- .../js/components/MudExRangeSlider.min.js | 2 +- .../MainSample.WebAssembly.csproj | 8 +++---- .../MainSample.WebAssembly/wwwroot/index.html | 22 +++++++++---------- 8 files changed, 29 insertions(+), 24 deletions(-) diff --git a/MudBlazor.Extensions/Components/MudExRangeSlider.razor.cs b/MudBlazor.Extensions/Components/MudExRangeSlider.razor.cs index 8bd41709..9e2e3718 100644 --- a/MudBlazor.Extensions/Components/MudExRangeSlider.razor.cs +++ b/MudBlazor.Extensions/Components/MudExRangeSlider.razor.cs @@ -396,7 +396,8 @@ protected override void OnParametersSet() var s = Snap(Value.Start); var e = Snap(Value.End); var snapped = new MudExRange(s, e).Normalize(); - var bounded = M.EnforceMinMaxLength(snapped, SizeRange, MinLength, MaxLength, Thumb.End); + var activeBias = _dragMode == DragMode.StartThumb ? Thumb.Start : Thumb.End; + var bounded = M.EnforceMinMaxLength(snapped, SizeRange, MinLength, MaxLength, activeBias); if (bounded.Start.CompareTo(bounded.End) > 0) bounded = new MudExRange(bounded.End, bounded.Start); diff --git a/MudBlazor.Extensions/Helper/ArchiveConverter.cs b/MudBlazor.Extensions/Helper/ArchiveConverter.cs index 0ebe9fd8..984d286a 100644 --- a/MudBlazor.Extensions/Helper/ArchiveConverter.cs +++ b/MudBlazor.Extensions/Helper/ArchiveConverter.cs @@ -73,7 +73,7 @@ public static MemoryStream ConvertArchiveToZip(IArchive archive) ///
public static Stream ConvertToSystemCompressionZip(Stream unknownArchiveStream) { - using var archive = ArchiveFactory.Open(unknownArchiveStream); + using var archive = ArchiveFactory.OpenArchive(unknownArchiveStream); return archive.Type == SharpCompress.Common.ArchiveType.Zip ? unknownArchiveStream : ConvertArchiveToZip(archive); } diff --git a/MudBlazor.Extensions/MudBlazor.Extensions.csproj b/MudBlazor.Extensions/MudBlazor.Extensions.csproj index 26f5e79a..d6637da1 100644 --- a/MudBlazor.Extensions/MudBlazor.Extensions.csproj +++ b/MudBlazor.Extensions/MudBlazor.Extensions.csproj @@ -70,19 +70,19 @@ - + - - + + - + diff --git a/MudBlazor.Extensions/Services/MudExFileService.cs b/MudBlazor.Extensions/Services/MudExFileService.cs index e7ad7f1e..53ce0d46 100644 --- a/MudBlazor.Extensions/Services/MudExFileService.cs +++ b/MudBlazor.Extensions/Services/MudExFileService.cs @@ -244,7 +244,7 @@ private async Task RevokeBlobUrlAsync(string blobUrl) public async Task<(HashSet Structure, List List)> ReadArchiveAsync(Stream stream, string rootFolderName, string contentType) { var contentStream = await CopyStreamAsync(stream); - var archive = ArchiveFactory.Open(contentStream); + var archive = ArchiveFactory.OpenArchive(contentStream); if (archive.Entries.Count() == 1 && archive.Entries.First().CompressionType == SharpCompress.Common.CompressionType.GZip) { using var memoryStream = new MemoryStream(); diff --git a/MudBlazor.Extensions/wwwroot/js/components/MudExRangeSlider.js b/MudBlazor.Extensions/wwwroot/js/components/MudExRangeSlider.js index 653c0cce..f70c68e8 100644 --- a/MudBlazor.Extensions/wwwroot/js/components/MudExRangeSlider.js +++ b/MudBlazor.Extensions/wwwroot/js/components/MudExRangeSlider.js @@ -17,7 +17,11 @@ class MudExRangeSlider { window.addEventListener('pointerup', this._onUp, true); } _onMove(evt) { - this.dotnet.invokeMethodAsync('OnPointerMove', evt.clientX, evt.clientY); + if (this._movePending) return; + this._movePending = true; + this.dotnet.invokeMethodAsync('OnPointerMove', evt.clientX, evt.clientY) + .then(() => { this._movePending = false; }) + .catch(() => { this._movePending = false; }); } _onUp() { window.removeEventListener('pointermove', this._onMove, true); diff --git a/MudBlazor.Extensions/wwwroot/js/components/MudExRangeSlider.min.js b/MudBlazor.Extensions/wwwroot/js/components/MudExRangeSlider.min.js index 7261ed0c..fa9d930a 100644 --- a/MudBlazor.Extensions/wwwroot/js/components/MudExRangeSlider.min.js +++ b/MudBlazor.Extensions/wwwroot/js/components/MudExRangeSlider.min.js @@ -1 +1 @@ -export function initializeMudExRangeSlider(t,i,r){return new n(t,i,r)}class n{constructor(n,t,i){this.rootEl=n;this.trackEl=t;this.dotnet=i;this._onMove=this._onMove.bind(this);this._onUp=this._onUp.bind(this)}init(){}measureTrack(){const n=this.trackEl.getBoundingClientRect();return{left:n.left,top:n.top,width:n.width,height:n.height}}startCapture(){window.addEventListener("pointermove",this._onMove,!0);window.addEventListener("pointerup",this._onUp,!0)}_onMove(n){this.dotnet.invokeMethodAsync("OnPointerMove",n.clientX,n.clientY)}_onUp(){window.removeEventListener("pointermove",this._onMove,!0);window.removeEventListener("pointerup",this._onUp,!0);this.dotnet.invokeMethodAsync("OnPointerUp")}}window.MudExRangeSlider=n; \ No newline at end of file +export function initializeMudExRangeSlider(t,i,r){return new n(t,i,r)}class n{constructor(n,t,i){this.rootEl=n;this.trackEl=t;this.dotnet=i;this._onMove=this._onMove.bind(this);this._onUp=this._onUp.bind(this)}init(){}measureTrack(){const n=this.trackEl.getBoundingClientRect();return{left:n.left,top:n.top,width:n.width,height:n.height}}startCapture(){window.addEventListener("pointermove",this._onMove,!0);window.addEventListener("pointerup",this._onUp,!0)}_onMove(n){this._movePending||(this._movePending=!0,this.dotnet.invokeMethodAsync("OnPointerMove",n.clientX,n.clientY).then(()=>{this._movePending=!1}).catch(()=>{this._movePending=!1}))}_onUp(){window.removeEventListener("pointermove",this._onMove,!0);window.removeEventListener("pointerup",this._onUp,!0);this.dotnet.invokeMethodAsync("OnPointerUp")}}window.MudExRangeSlider=n; \ No newline at end of file diff --git a/Samples/MainSample.WebAssembly/MainSample.WebAssembly.csproj b/Samples/MainSample.WebAssembly/MainSample.WebAssembly.csproj index 07aa17c5..88b7ecc1 100644 --- a/Samples/MainSample.WebAssembly/MainSample.WebAssembly.csproj +++ b/Samples/MainSample.WebAssembly/MainSample.WebAssembly.csproj @@ -48,10 +48,10 @@ - - - - + + + + diff --git a/Samples/MainSample.WebAssembly/wwwroot/index.html b/Samples/MainSample.WebAssembly/wwwroot/index.html index 41b840a3..8834d9e0 100644 --- a/Samples/MainSample.WebAssembly/wwwroot/index.html +++ b/Samples/MainSample.WebAssembly/wwwroot/index.html @@ -21,17 +21,17 @@ - + - - - - + + + + - - - + + + @@ -48,7 +48,7 @@ -

MudBlazor.Extensions v9.0.0-prev-2602281741

+

MudBlazor.Extensions v9.0.0-prev-2602281843

for MudBlazor 9.0.0

@@ -83,8 +83,8 @@

MudBlazor.Extensions v9.0.0-prev-2602281741

- - + + From 96cf3fd49be8136844052ae7fbd4d3fd1587be47 Mon Sep 17 00:00:00 2001 From: Florian Gilde Date: Sat, 28 Feb 2026 18:47:58 +0100 Subject: [PATCH 06/11] update more nuget packages --- .../MudBlazor.Extensions.Tests.csproj | 10 +++++----- TryMudEx/Try.Core/Try.Core.csproj | 4 ++-- TryMudEx/Try.Tests/Try.Tests.csproj | 4 ++-- TryMudEx/TryMudEx.Client/TryMudEx.Client.csproj | 6 +++--- TryMudEx/TryMudEx.Server/TryMudEx.Server.csproj | 8 ++++---- TryMudEx/UserComponents/Try.UserComponents.csproj | 2 +- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/MudBlazor.Extensions.Tests/MudBlazor.Extensions.Tests.csproj b/MudBlazor.Extensions.Tests/MudBlazor.Extensions.Tests.csproj index bd84b308..bb00d4f1 100644 --- a/MudBlazor.Extensions.Tests/MudBlazor.Extensions.Tests.csproj +++ b/MudBlazor.Extensions.Tests/MudBlazor.Extensions.Tests.csproj @@ -10,20 +10,20 @@ - + - + - + - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/TryMudEx/Try.Core/Try.Core.csproj b/TryMudEx/Try.Core/Try.Core.csproj index 16bbb901..4f28a58d 100644 --- a/TryMudEx/Try.Core/Try.Core.csproj +++ b/TryMudEx/Try.Core/Try.Core.csproj @@ -7,10 +7,10 @@ - + - + diff --git a/TryMudEx/Try.Tests/Try.Tests.csproj b/TryMudEx/Try.Tests/Try.Tests.csproj index ede1b4e0..dfe13409 100644 --- a/TryMudEx/Try.Tests/Try.Tests.csproj +++ b/TryMudEx/Try.Tests/Try.Tests.csproj @@ -6,9 +6,9 @@ - + - + diff --git a/TryMudEx/TryMudEx.Client/TryMudEx.Client.csproj b/TryMudEx/TryMudEx.Client/TryMudEx.Client.csproj index 063815af..2e582c64 100644 --- a/TryMudEx/TryMudEx.Client/TryMudEx.Client.csproj +++ b/TryMudEx/TryMudEx.Client/TryMudEx.Client.csproj @@ -11,9 +11,9 @@ - - - + + + diff --git a/TryMudEx/TryMudEx.Server/TryMudEx.Server.csproj b/TryMudEx/TryMudEx.Server/TryMudEx.Server.csproj index 55ab1580..3d772c80 100644 --- a/TryMudEx/TryMudEx.Server/TryMudEx.Server.csproj +++ b/TryMudEx/TryMudEx.Server/TryMudEx.Server.csproj @@ -6,11 +6,11 @@ - + - - - + + + diff --git a/TryMudEx/UserComponents/Try.UserComponents.csproj b/TryMudEx/UserComponents/Try.UserComponents.csproj index 84944d7e..a8756486 100644 --- a/TryMudEx/UserComponents/Try.UserComponents.csproj +++ b/TryMudEx/UserComponents/Try.UserComponents.csproj @@ -4,7 +4,7 @@ net10.0 - + From 1f36fd7ca2b99dd2fd07e6df543a39aa0534aba9 Mon Sep 17 00:00:00 2001 From: Florian Gilde Date: Sat, 28 Feb 2026 20:03:22 +0100 Subject: [PATCH 07/11] add MudMenu with ActivatorContent to fix menu changes in demo box from mud 9 --- .../Shared/MudExDemoBox.razor | 3 ++- .../MainSample.WebAssembly/wwwroot/index.html | 22 +++++++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Samples/MainSample.WebAssembly/Shared/MudExDemoBox.razor b/Samples/MainSample.WebAssembly/Shared/MudExDemoBox.razor index 2b6801c9..4ef62608 100644 --- a/Samples/MainSample.WebAssembly/Shared/MudExDemoBox.razor +++ b/Samples/MainSample.WebAssembly/Shared/MudExDemoBox.razor @@ -71,7 +71,8 @@ + StartIcon="@Icons.Material.Filled.Settings" + OnClick="@context.ToggleAsync"> @TryLocalize(EditInstanceText) diff --git a/Samples/MainSample.WebAssembly/wwwroot/index.html b/Samples/MainSample.WebAssembly/wwwroot/index.html index 8834d9e0..1ce38306 100644 --- a/Samples/MainSample.WebAssembly/wwwroot/index.html +++ b/Samples/MainSample.WebAssembly/wwwroot/index.html @@ -21,17 +21,17 @@ - + - - - - + + + + - - - + + + @@ -48,7 +48,7 @@ -

MudBlazor.Extensions v9.0.0-prev-2602281843

+

MudBlazor.Extensions v9.0.0-prev-260228201

for MudBlazor 9.0.0

@@ -83,8 +83,8 @@

MudBlazor.Extensions v9.0.0-prev-2602281843

- - + + From 7dc82f72a1882a20f29da8e86b938dc4c7329a6b Mon Sep 17 00:00:00 2001 From: Florian Gilde Date: Sat, 28 Feb 2026 20:11:00 +0100 Subject: [PATCH 08/11] fix other mud 9 mudmenu changes --- TryMudEx/TryMudEx.Client/Components/PackageReferences.razor | 2 +- TryMudEx/TryMudEx.Client/Components/TabManager.razor | 2 +- TryMudEx/TryMudEx.Client/Pages/Repl.razor | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/TryMudEx/TryMudEx.Client/Components/PackageReferences.razor b/TryMudEx/TryMudEx.Client/Components/PackageReferences.razor index 710faad0..1150a507 100644 --- a/TryMudEx/TryMudEx.Client/Components/PackageReferences.razor +++ b/TryMudEx/TryMudEx.Client/Components/PackageReferences.razor @@ -57,7 +57,7 @@ - + @if (IsInstalled(package)) { @($"Installed v{GetInstalledVersion(package)}") diff --git a/TryMudEx/TryMudEx.Client/Components/TabManager.razor b/TryMudEx/TryMudEx.Client/Components/TabManager.razor index 755ea0f7..8357fc06 100644 --- a/TryMudEx/TryMudEx.Client/Components/TabManager.razor +++ b/TryMudEx/TryMudEx.Client/Components/TabManager.razor @@ -42,7 +42,7 @@ { - + @foreach (var file in CodeFileTemplates.All().Where(f => !Tabs.Contains(f.Path))) diff --git a/TryMudEx/TryMudEx.Client/Pages/Repl.razor b/TryMudEx/TryMudEx.Client/Pages/Repl.razor index 5a2e32ed..8c145038 100644 --- a/TryMudEx/TryMudEx.Client/Pages/Repl.razor +++ b/TryMudEx/TryMudEx.Client/Pages/Repl.razor @@ -100,7 +100,7 @@ { - + @foreach (var package in _installedPackages) From fe18e7da933150190a5630268b0602777ab24207 Mon Sep 17 00:00:00 2001 From: Florian Gilde Date: Sat, 28 Feb 2026 22:33:10 +0100 Subject: [PATCH 09/11] fix mud warnings for mud 9 --- .../Components/MudExFileDisplay.razor | 4 ++-- .../Components/MudExIconPicker.razor | 4 ++-- .../MudExPropertyInfoContainer.razor | 2 +- .../Components/MudExSelect.razor | 1 - .../Components/MudExSplitter.razor.cs | 2 +- .../Components/MudExTaskBar.razor | 2 +- .../Components/MudExTextField.razor | 1 + .../Components/MudExTextField.razor.cs | 5 +++++ .../Components/MudExThemeEdit.razor | 2 +- .../ObjectEdit/MudExCollectionEditor.razor | 2 +- .../ObjectEdit/MudExObjectEdit.razor | 2 +- .../ObjectEdit/MudExPropertyEdit.razor | 2 +- .../wwwroot/docs/MudBlazor.Extensions.xml | 5 +++++ .../Pages/Document.razor | 2 +- .../Pages/Page_AdditionalAdornment.razor | 6 ++--- .../Pages/Page_ObjectEditConfigured.razor | 2 +- .../Pages/Page_ObjectEditPicker.razor | 2 +- .../MainSample.WebAssembly/Pages/Render.razor | 2 +- .../Pages/SampleTabDialog.razor | 4 ++-- .../Shared/DemoComponent.razor | 6 ++--- .../Shared/MainLayout.razor | 4 ++-- .../Shared/MudExDemoBox.razor | 4 ++-- .../MainSample.WebAssembly/wwwroot/index.html | 22 +++++++++---------- TryMudEx/TryMudEx.Client/Pages/Repl.razor | 2 +- 24 files changed, 50 insertions(+), 40 deletions(-) diff --git a/MudBlazor.Extensions/Components/MudExFileDisplay.razor b/MudBlazor.Extensions/Components/MudExFileDisplay.razor index ce7184ce..46f304b1 100644 --- a/MudBlazor.Extensions/Components/MudExFileDisplay.razor +++ b/MudBlazor.Extensions/Components/MudExFileDisplay.razor @@ -111,11 +111,11 @@ @foreach (var c in _possibleRenderControls.Where(c => c.GetType() != _componentForFile.ControlType)) { - @($"{TryLocalize("Render with {0}", c.Name ?? c.GetType().Name)}") + @($"{TryLocalize("Render with {0}", c.Name ?? c.GetType().Name)}") } @if (!_isNativeRendered) { - @($"{TryLocalize("Render with native control")}") + @($"{TryLocalize("Render with native control")}") } @if (AllowCopyUrl) { diff --git a/MudBlazor.Extensions/Components/MudExIconPicker.razor b/MudBlazor.Extensions/Components/MudExIconPicker.razor index cc0f6723..d4e81eea 100644 --- a/MudBlazor.Extensions/Components/MudExIconPicker.razor +++ b/MudBlazor.Extensions/Components/MudExIconPicker.razor @@ -39,8 +39,8 @@ @foreach (var icon in context) { diff --git a/MudBlazor.Extensions/Components/MudExPropertyInfoContainer.razor b/MudBlazor.Extensions/Components/MudExPropertyInfoContainer.razor index 68cb15b6..9d2c9f0b 100644 --- a/MudBlazor.Extensions/Components/MudExPropertyInfoContainer.razor +++ b/MudBlazor.Extensions/Components/MudExPropertyInfoContainer.razor @@ -13,7 +13,7 @@
- +
diff --git a/MudBlazor.Extensions/Components/MudExSelect.razor b/MudBlazor.Extensions/Components/MudExSelect.razor index 26f9e1ae..7fbdfa89 100644 --- a/MudBlazor.Extensions/Components/MudExSelect.razor +++ b/MudBlazor.Extensions/Components/MudExSelect.razor @@ -22,7 +22,6 @@ AdornmentColor="@AdornmentColor" Adornment="@Adornment" Color="@Color" - TextUpdateSuppression="false" Value="@(Strict && !IsValueInList ? null : Text)" Underline="@Underline" Disabled="@Disabled" ReadOnly="true" Error="@Error" ErrorId="@ErrorId" Clearable="@(Clearable && !ReadOnly)" OnClearButtonClick="(async (e) => await SelectClearButtonClickHandlerAsync(e))" diff --git a/MudBlazor.Extensions/Components/MudExSplitter.razor.cs b/MudBlazor.Extensions/Components/MudExSplitter.razor.cs index 75a39a65..7e5d0f9a 100644 --- a/MudBlazor.Extensions/Components/MudExSplitter.razor.cs +++ b/MudBlazor.Extensions/Components/MudExSplitter.razor.cs @@ -40,7 +40,7 @@ private RenderFragment Inherited() => builder => /// /// Indicates whether the Splitter is currently dragging /// - public bool IsDragging { get; private set; } = false; + [Parameter] public bool IsDragging { get; set; } /// /// Callback when dragging starts or ends diff --git a/MudBlazor.Extensions/Components/MudExTaskBar.razor b/MudBlazor.Extensions/Components/MudExTaskBar.razor index 2e32ddb8..408966a6 100644 --- a/MudBlazor.Extensions/Components/MudExTaskBar.razor +++ b/MudBlazor.Extensions/Components/MudExTaskBar.razor @@ -16,7 +16,7 @@ { @if (_taskbarItems?.Any() == true || !OnlyVisibleWithWindows) { - + @foreach (var taskBarItem in _taskbarItems) { + @ChildContent \ No newline at end of file diff --git a/MudBlazor.Extensions/Components/MudExTextField.razor.cs b/MudBlazor.Extensions/Components/MudExTextField.razor.cs index c56d1621..7462d77c 100644 --- a/MudBlazor.Extensions/Components/MudExTextField.razor.cs +++ b/MudBlazor.Extensions/Components/MudExTextField.razor.cs @@ -11,6 +11,11 @@ namespace MudBlazor.Extensions.Components; /// public partial class MudExTextField: MudExBaseInput, IMudExComponent { + /// + /// Optional child content (e.g. for MudExAdditionalAdornment). + /// + [Parameter] public RenderFragment ChildContent { get; set; } + /// /// Classname for the input element /// diff --git a/MudBlazor.Extensions/Components/MudExThemeEdit.razor b/MudBlazor.Extensions/Components/MudExThemeEdit.razor index e1b4ff91..52877ace 100644 --- a/MudBlazor.Extensions/Components/MudExThemeEdit.razor +++ b/MudBlazor.Extensions/Components/MudExThemeEdit.razor @@ -30,7 +30,7 @@ { + } } diff --git a/MudBlazor.Extensions/Components/ObjectEdit/MudExObjectEdit.razor b/MudBlazor.Extensions/Components/ObjectEdit/MudExObjectEdit.razor index 2f25606c..cdbdb1c8 100644 --- a/MudBlazor.Extensions/Components/ObjectEdit/MudExObjectEdit.razor +++ b/MudBlazor.Extensions/Components/ObjectEdit/MudExObjectEdit.razor @@ -170,7 +170,7 @@ @if (AddScrollToTop) { - + } diff --git a/MudBlazor.Extensions/Components/ObjectEdit/MudExPropertyEdit.razor b/MudBlazor.Extensions/Components/ObjectEdit/MudExPropertyEdit.razor index 5a6eac42..51d57044 100644 --- a/MudBlazor.Extensions/Components/ObjectEdit/MudExPropertyEdit.razor +++ b/MudBlazor.Extensions/Components/ObjectEdit/MudExPropertyEdit.razor @@ -39,7 +39,7 @@ { @(Localizer != null ? Localizer.TryLocalize(reset.ResetText) : reset.ResetText) } - + ; } diff --git a/MudBlazor.Extensions/wwwroot/docs/MudBlazor.Extensions.xml b/MudBlazor.Extensions/wwwroot/docs/MudBlazor.Extensions.xml index 1f56c42b..09c3bcb0 100644 --- a/MudBlazor.Extensions/wwwroot/docs/MudBlazor.Extensions.xml +++ b/MudBlazor.Extensions/wwwroot/docs/MudBlazor.Extensions.xml @@ -7986,6 +7986,11 @@ MudTextField with additional features
+ + + Optional child content (e.g. for MudExAdditionalAdornment). + + Classname for the input element diff --git a/Samples/MainSample.WebAssembly/Pages/Document.razor b/Samples/MainSample.WebAssembly/Pages/Document.razor index b95bf43e..1a27a87b 100644 --- a/Samples/MainSample.WebAssembly/Pages/Document.razor +++ b/Samples/MainSample.WebAssembly/Pages/Document.razor @@ -50,7 +50,7 @@ else private RenderFragment RenderTypeApiDoc(Type type, int activePanel, bool forSingleType, string documentationValue) { // - return @ + return @ @if (!string.IsNullOrEmpty(documentationValue)) { diff --git a/Samples/MainSample.WebAssembly/Pages/Page_AdditionalAdornment.razor b/Samples/MainSample.WebAssembly/Pages/Page_AdditionalAdornment.razor index 94a50a90..dc64e98d 100644 --- a/Samples/MainSample.WebAssembly/Pages/Page_AdditionalAdornment.razor +++ b/Samples/MainSample.WebAssembly/Pages/Page_AdditionalAdornment.razor @@ -129,8 +129,8 @@ void DemoContent(RenderTreeBuilder __builder) { - - - + + + } } diff --git a/Samples/MainSample.WebAssembly/Pages/Page_ObjectEditConfigured.razor b/Samples/MainSample.WebAssembly/Pages/Page_ObjectEditConfigured.razor index bea046f8..5a125a62 100644 --- a/Samples/MainSample.WebAssembly/Pages/Page_ObjectEditConfigured.razor +++ b/Samples/MainSample.WebAssembly/Pages/Page_ObjectEditConfigured.razor @@ -41,7 +41,7 @@ @L["My Custom action"] - + diff --git a/Samples/MainSample.WebAssembly/Pages/Page_ObjectEditPicker.razor b/Samples/MainSample.WebAssembly/Pages/Page_ObjectEditPicker.razor index eb9b0ae2..d63060ab 100644 --- a/Samples/MainSample.WebAssembly/Pages/Page_ObjectEditPicker.razor +++ b/Samples/MainSample.WebAssembly/Pages/Page_ObjectEditPicker.razor @@ -13,7 +13,7 @@
- + diff --git a/Samples/MainSample.WebAssembly/Pages/Render.razor b/Samples/MainSample.WebAssembly/Pages/Render.razor index 0605c2af..4b3e639f 100644 --- a/Samples/MainSample.WebAssembly/Pages/Render.razor +++ b/Samples/MainSample.WebAssembly/Pages/Render.razor @@ -16,7 +16,7 @@ @L["Here is another Real demo available for"] @(ApiMemberInfo.GetGenericFriendlyTypeName(Type) ?? TypeName) } - + @if (Type.IsGenericType) { diff --git a/Samples/MainSample.WebAssembly/Pages/SampleTabDialog.razor b/Samples/MainSample.WebAssembly/Pages/SampleTabDialog.razor index e9f2b171..74ffa2a6 100644 --- a/Samples/MainSample.WebAssembly/Pages/SampleTabDialog.razor +++ b/Samples/MainSample.WebAssembly/Pages/SampleTabDialog.razor @@ -9,8 +9,8 @@ - - + +
@L["Item One"]
diff --git a/Samples/MainSample.WebAssembly/Shared/DemoComponent.razor b/Samples/MainSample.WebAssembly/Shared/DemoComponent.razor index a8e8720a..c80c2156 100644 --- a/Samples/MainSample.WebAssembly/Shared/DemoComponent.razor +++ b/Samples/MainSample.WebAssembly/Shared/DemoComponent.razor @@ -23,7 +23,7 @@ @if (CodeFiles is { Length: > 1 }) { - + @foreach (var file in CodeFiles) { @System.IO.Path.GetFileName(file) @@ -43,7 +43,7 @@ @if (_docFiles.Count > 1) { - + @foreach (var doc in _docFiles) { @Path.GetFileNameWithoutExtension(doc.Key) @@ -63,7 +63,7 @@ @if (DemoAttribute.RelatedComponents.Count() > 1) { - + @foreach (var type in DemoAttribute?.RelatedComponents ?? Array.Empty()) { @ApiMemberInfo.GetGenericFriendlyTypeName(type) diff --git a/Samples/MainSample.WebAssembly/Shared/MainLayout.razor b/Samples/MainSample.WebAssembly/Shared/MainLayout.razor index 2a5efdb6..0f521ff4 100644 --- a/Samples/MainSample.WebAssembly/Shared/MainLayout.razor +++ b/Samples/MainSample.WebAssembly/Shared/MainLayout.razor @@ -31,7 +31,7 @@ - + @@ -52,7 +52,7 @@ } - @L["Try MudEx online"] + @L["Try MudEx online"] @L["Official MudBlazor website"] Nuget diff --git a/Samples/MainSample.WebAssembly/Shared/MudExDemoBox.razor b/Samples/MainSample.WebAssembly/Shared/MudExDemoBox.razor index 4ef62608..26994b02 100644 --- a/Samples/MainSample.WebAssembly/Shared/MudExDemoBox.razor +++ b/Samples/MainSample.WebAssembly/Shared/MudExDemoBox.razor @@ -109,7 +109,7 @@ - + @if (DemoContent is not null) @@ -176,7 +176,7 @@ @if (_additionalCode?.Any() == true) { - + @if (!string.IsNullOrWhiteSpace(Code)) { diff --git a/Samples/MainSample.WebAssembly/wwwroot/index.html b/Samples/MainSample.WebAssembly/wwwroot/index.html index 1ce38306..99bf1c6b 100644 --- a/Samples/MainSample.WebAssembly/wwwroot/index.html +++ b/Samples/MainSample.WebAssembly/wwwroot/index.html @@ -21,17 +21,17 @@ - + - - - - + + + + - - - + + + @@ -48,7 +48,7 @@
-

MudBlazor.Extensions v9.0.0-prev-260228201

+

MudBlazor.Extensions v9.0.0-prev-2602282219

for MudBlazor 9.0.0

@@ -83,8 +83,8 @@

MudBlazor.Extensions v9.0.0-prev-260228201

- - + + diff --git a/TryMudEx/TryMudEx.Client/Pages/Repl.razor b/TryMudEx/TryMudEx.Client/Pages/Repl.razor index 8c145038..0a3ce9dd 100644 --- a/TryMudEx/TryMudEx.Client/Pages/Repl.razor +++ b/TryMudEx/TryMudEx.Client/Pages/Repl.razor @@ -9,7 +9,7 @@
- + From b963d4bee29da327c9e2f37d5034d650fdf5a273 Mon Sep 17 00:00:00 2001 From: Florian Gilde Date: Sun, 1 Mar 2026 18:14:53 +0100 Subject: [PATCH 10/11] cache some reflection code fix outstanding mud9 errors in mudexinput repair tagfiled remove no longer supported tag use --- .../Components/MudExCardList.razor.cs | 5 +- .../Components/MudExCodeView.razor.cs | 47 +++++----- .../Components/MudExDialog.razor.cs | 2 +- .../Components/MudExFileDisplay.razor.cs | 18 +++- .../Components/MudExInput.razor.cs | 17 ---- .../Components/MudExTagField.razor.cs | 48 ++++++++-- .../Components/MudExThemeEdit.razor.cs | 25 +++-- .../ObjectEdit/MudExObjectEdit.razor.cs | 94 +++++++++++++++---- .../ObjectEdit/MudExObjectEditForm.razor.cs | 15 ++- .../ObjectEdit/MudExPropertyEdit.razor.cs | 17 +++- .../MudExStructuredDataEditor.razor.cs | 13 ++- .../ObjectEdit/Options/ObjectEditMeta.base.cs | 43 +++++---- .../ObjectEdit/Options/ObjectEditMeta.cs | 24 ++++- .../Options/ObjectEditPropertyMeta.cs | 3 +- .../Options/ObjectEditPropertyMetaSettings.cs | 2 + .../ObjectEdit/Options/RenderData.base.cs | 25 +++-- .../ObjectEdit/Options/RenderData.cs | 17 +++- .../ObjectEdit/Options/RenderData.defaults.cs | 39 ++++++-- MudBlazor.Extensions/DialogService.cs | 3 +- .../Helper/ComponentHelper.cs | 35 ++++--- MudBlazor.Extensions/Helper/EnumHelper.cs | 20 +++- .../Helper/MudExCssBuilder.cs | 28 +++++- .../Helper/MudExStyleBuilder.cs | 43 +++++---- .../MudBlazor.Extensions.csproj | 4 +- .../wwwroot/docs/MudBlazor.Extensions.xml | 18 +++- 25 files changed, 440 insertions(+), 165 deletions(-) diff --git a/MudBlazor.Extensions/Components/MudExCardList.razor.cs b/MudBlazor.Extensions/Components/MudExCardList.razor.cs index 2dc99335..9ae4034e 100644 --- a/MudBlazor.Extensions/Components/MudExCardList.razor.cs +++ b/MudBlazor.Extensions/Components/MudExCardList.razor.cs @@ -130,7 +130,8 @@ public MudExCardHoverMode? HoverMode /// /// Methods returns List of MudExCardHoverMode, where hover modes are applied. /// - public List AllAppliedHoverModes => Enum.GetValues(typeof(MudExCardHoverMode)).Cast().Where(HoverModeMatches).ToList(); + private static readonly MudExCardHoverMode[] AllHoverModeValues = (MudExCardHoverMode[])Enum.GetValues(typeof(MudExCardHoverMode)); + public List AllAppliedHoverModes => AllHoverModeValues.Where(HoverModeMatches).ToList(); private string GetCss() { @@ -138,7 +139,7 @@ private string GetCss() .AddClass($"mud-ex-card-list-{_id}"); foreach (var mode in AllAppliedHoverModes) - res.AddClass($"mud-ex-card-list-{mode.ToString().ToLower()}"); + res.AddClass($"mud-ex-card-list-{mode.ToString().ToLowerInvariant()}"); res.AddClass(Class); return res.Build(); diff --git a/MudBlazor.Extensions/Components/MudExCodeView.razor.cs b/MudBlazor.Extensions/Components/MudExCodeView.razor.cs index 1ae52960..b88a893b 100644 --- a/MudBlazor.Extensions/Components/MudExCodeView.razor.cs +++ b/MudBlazor.Extensions/Components/MudExCodeView.razor.cs @@ -26,6 +26,14 @@ namespace MudBlazor.Extensions.Components; ///
public partial class MudExCodeView { + private static readonly Regex LambdaStartRegex = new(@"^\s*(?:async\s+)?\([^)]*\)\s*=>\s*{?", RegexOptions.Compiled | RegexOptions.Singleline); + private static readonly Regex LambdaEndRegex = new(@"\s*}\s*$", RegexOptions.Compiled | RegexOptions.Singleline); + private static readonly Regex GenericTypeRegex = new(@"^(?\w+)<(?.+)>$", RegexOptions.Compiled); + private static readonly Regex EmptyTagRegex = new(@"<(\w+)([^>]*)>\s*", RegexOptions.Compiled); + private static readonly Regex HtmlTagRegex = new(@"(\<[^/][^>]*\>)|(\<\/[^>]*\>)", RegexOptions.Compiled); + private static readonly Regex MultiNewlineRegex = new(@"[\r\n]+", RegexOptions.Compiled); + private static readonly Regex BeforeTagRegex = new(@"([^\r\n])<", RegexOptions.Compiled); + private static readonly Regex AfterTagRegex = new(@">([^\r\n])", RegexOptions.Compiled); private ElementReference _element; private string _markdownCode; private string _code; @@ -319,10 +327,8 @@ public static (string CodeStr, Action Func) FuncAsString(Action func, bool repla ///
public static string ReplaceLambdaInFuncString(string caller) { - //caller = Regex.Replace(caller, @"^\s*\([^)]*\)\s*=>\s*{?", "", RegexOptions.Singleline); - //caller = Regex.Replace(caller, @"\s*}\s*$", "", RegexOptions.Singleline); - caller = Regex.Replace(caller, @"^\s*(?:async\s+)?\([^)]*\)\s*=>\s*{?", "", RegexOptions.Singleline); - caller = Regex.Replace(caller, @"\s*}\s*$", "", RegexOptions.Singleline); + caller = LambdaStartRegex.Replace(caller, ""); + caller = LambdaEndRegex.Replace(caller, ""); return caller; } @@ -332,8 +338,9 @@ public static string ReplaceLambdaInFuncString(string caller) public static string GenerateBlazorMarkupFromInstance(TComponent componentInstance, string comment = "", bool hideDefaults = true) { // TODO: Move to central place with ApiMemberInfo - var componentName = componentInstance?.GetType().FullName?.Replace(componentInstance.GetType().Namespace + ".", string.Empty); - var properties = componentInstance?.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance) + var componentType = componentInstance?.GetType(); + var componentName = componentType?.FullName?.Replace(componentType.Namespace + ".", string.Empty); + var properties = componentType?.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => ObjectEditMeta.IsAllowedAsPropertyToEdit(p) && ObjectEditMeta.IsAllowedAsPropertyToEditOnAComponent(p)); var props = properties?.ToDictionary(info => info, info => info.GetValue(componentInstance)) @@ -386,7 +393,7 @@ public static (string StartTag, string EndTag) GetComponentTagNames(string input return (input, input); } input = ApiMemberInfo.GetGenericFriendlyTypeName(input); - var match = Regex.Match(input, @"^(?\w+)<(?.+)>$"); + var match = GenericTypeRegex.Match(input); var className = match.Groups["class"].Value; @@ -404,7 +411,8 @@ private static string MarkupValue(object value, string propName, bool ignoreComp if (value == null || string.IsNullOrEmpty(value.ToString())) return null; - if (value is EventCallback || (value.GetType().IsGenericType && value.GetType().GetGenericTypeDefinition() == typeof(EventCallback<>))) + var valueType = value.GetType(); + if (value is EventCallback || (valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(EventCallback<>))) { return $"@Your{propName}EventHandler"; } @@ -412,8 +420,8 @@ private static string MarkupValue(object value, string propName, bool ignoreComp if (value is bool) return value.ToString()?.ToLower(); - if (value.GetType().IsEnum) - return $"{value.GetType().FullName}.{value}"; + if (valueType.IsEnum) + return $"{valueType.FullName}.{value}"; if (value is DateTime dt) return $"@(System.DateTime.Parse(\"{dt}\"))"; @@ -435,7 +443,7 @@ private static string MarkupValue(object value, string propName, bool ignoreComp return name != null ? $"@{name}" : value.ToString()?.Replace("\"", "\\\""); } - if (value is string || MudExObjectEditHelper.HandleAsPrimitive(value.GetType())) + if (value is string || MudExObjectEditHelper.HandleAsPrimitive(valueType)) { return value.ToString(); } @@ -443,10 +451,8 @@ private static string MarkupValue(object value, string propName, bool ignoreComp if (ignoreComplexTypes) return null; - //return $"{p.Value?.GetType().FullName}.{p.Value}"; - // Create a json string from the object and deserialize it in the markup - var fullName = value.GetType().FullName; + var fullName = valueType.FullName; var friendlyTypeName = ApiMemberInfo.GetGenericFriendlyTypeName(fullName); var json = JsonConvert.SerializeObject(value); return $"@(Newtonsoft.Json.JsonConvert.DeserializeObject<{friendlyTypeName}>(\"{json.Replace("\"", "\\\"")}\"))"; @@ -464,7 +470,7 @@ public static string ShortenMarkup(string markup) string replacement = @"<$1$2 />"; // Replace empty tags with self-closing tags - return RemoveEmptyLines(Regex.Replace(markup, pattern, replacement)); + return RemoveEmptyLines(EmptyTagRegex.Replace(markup, replacement)); } /// @@ -489,15 +495,12 @@ public static string FormatHtml(string html) html = html.Replace(">>", ">"); } - string pattern = @"(\<[^/][^>]*\>)|(\<\/[^>]*\>)"; string replacement = "$1\r\n$2"; - Regex rgx = new Regex(pattern); - - string result = rgx.Replace(html, replacement); - result = Regex.Replace(result, @"[\r\n]+", "\r\n"); - result = Regex.Replace(result, @"([^\r\n])<", "$1\r\n<"); - result = Regex.Replace(result, @">([^\r\n])", ">\r\n$1"); + string result = HtmlTagRegex.Replace(html, replacement); + result = MultiNewlineRegex.Replace(result, "\r\n"); + result = BeforeTagRegex.Replace(result, "$1\r\n<"); + result = AfterTagRegex.Replace(result, ">\r\n$1"); int indentLevel = 0; var lines = result.Split('\n'); diff --git a/MudBlazor.Extensions/Components/MudExDialog.razor.cs b/MudBlazor.Extensions/Components/MudExDialog.razor.cs index 03c1966f..6ccbd850 100644 --- a/MudBlazor.Extensions/Components/MudExDialog.razor.cs +++ b/MudBlazor.Extensions/Components/MudExDialog.razor.cs @@ -154,7 +154,7 @@ private async Task ShowDialogAsync() var cls = Class; OptionsEx.JsRuntime ??= Js; await DialogServiceExt.PrepareOptionsBeforeShow(OptionsEx); - var appliedOptions = DialogServiceExt.PrepareOptionsAfterShow(OptionsEx); + var appliedOptions = DialogServiceExt.PrepareOptionsAfterShow(OptionsEx.CloneOptions()); Class = $"{EnsureInitialClass()} {appliedOptions?.DialogAppearance?.Class}"; var result = await ShowAsync(); Class = cls; diff --git a/MudBlazor.Extensions/Components/MudExFileDisplay.razor.cs b/MudBlazor.Extensions/Components/MudExFileDisplay.razor.cs index e10fe97a..ea7d8646 100644 --- a/MudBlazor.Extensions/Components/MudExFileDisplay.razor.cs +++ b/MudBlazor.Extensions/Components/MudExFileDisplay.razor.cs @@ -241,7 +241,16 @@ public Stream ContentStream /// /// Media Type for current file /// - public string MediaType => ContentType?.Split("/").FirstOrDefault()?.ToLower(); + public string MediaType + { + get + { + var ct = ContentType; + if (ct == null) return null; + var idx = ct.IndexOf('/'); + return idx >= 0 ? ct[..idx].ToLowerInvariant() : ct.ToLowerInvariant(); + } + } /// /// Returns a plugin that is useful to show the content if the content cant displayed @@ -402,7 +411,12 @@ public static (string tag, Dictionary attributes) GetFileRenderI string fileName, string contentType, bool viewDependsOnContentType = true, bool imageAsBackgroundImage = false, bool fallBackInIframe = false, bool sandBoxIframes = true, string onErrorMethod = "") { - var mediaType = contentType?.Split("/").FirstOrDefault()?.ToLower(); + string mediaType = null; + if (contentType != null) + { + var idx = contentType.IndexOf('/'); + mediaType = idx >= 0 ? contentType[..idx].ToLowerInvariant() : contentType.ToLowerInvariant(); + } if (viewDependsOnContentType && !string.IsNullOrEmpty(mediaType)) { switch (mediaType) diff --git a/MudBlazor.Extensions/Components/MudExInput.razor.cs b/MudBlazor.Extensions/Components/MudExInput.razor.cs index de7c80bf..a9744058 100644 --- a/MudBlazor.Extensions/Components/MudExInput.razor.cs +++ b/MudBlazor.Extensions/Components/MudExInput.razor.cs @@ -287,29 +287,12 @@ public override ValueTask SelectRangeAsync(int pos1, int pos2) private Size GetButtonSize() => Margin == Margin.Dense ? Size.Small : Size.Medium; - private void UpdateClearable(object value) - { - var showClearable = HasValue((T)value); - if (Clearable != showClearable) - Clearable = showClearable; - } - private bool GetClearable() => Clearable && ((Value is string stringValue && !string.IsNullOrWhiteSpace(stringValue)) || (Value is not string && Value is not null)); - /// - protected override async Task UpdateTextPropertyAsync(bool updateValue) - { - await base.UpdateTextPropertyAsync(updateValue); - if (Clearable) - UpdateClearable(Text); - } - /// protected override async Task UpdateValuePropertyAsync(bool updateText) { await base.UpdateValuePropertyAsync(updateText); - if (Clearable) - UpdateClearable(Value); } /// diff --git a/MudBlazor.Extensions/Components/MudExTagField.razor.cs b/MudBlazor.Extensions/Components/MudExTagField.razor.cs index 1b7b0d37..6d0486d1 100644 --- a/MudBlazor.Extensions/Components/MudExTagField.razor.cs +++ b/MudBlazor.Extensions/Components/MudExTagField.razor.cs @@ -17,6 +17,9 @@ public partial class MudExTagField { private Adornment _renderChipsAdditional = Adornment.None; private List _values; + private string _trackedText; + private T _trackedValue; + private bool _swallowNextInput; /// /// Set to true to allow duplicates @@ -209,16 +212,46 @@ protected override void OnInitialized() base.OnInitialized(); } + /// + protected override async Task SetTextAndUpdateValueAsync(string text, bool updateValue = true) + { + if (_swallowNextInput) + { + _swallowNextInput = false; + _trackedText = string.Empty; + _trackedValue = default; + if (InputReference != null) + await InputReference.SetText(string.Empty); + return; + } + + _trackedText = text; + await base.SetTextAndUpdateValueAsync(text, updateValue); + _trackedValue = ReadValue; + } + + /// + protected override async Task UpdateValuePropertyAsync(bool updateText) + { + await base.UpdateValuePropertyAsync(updateText); + _trackedValue = ReadValue; + } + private bool ShouldShowVisualiser() => Values?.Any() == true && RenderChipsAdditional == Adornment.None; /// protected override async Task InvokeKeyDownAsync(KeyboardEventArgs args) { await base.InvokeKeyDownAsync(args); - if (((Delimiters?.Contains(args.Key[0]) == true && args.Key.Length == 1) || (SetChipsOnEnter && args.Key == "Enter")) && Value != null) - await ApplyChips(); + var isDelimiter = Delimiters?.Length > 0 && args.Key.Length == 1 && Delimiters.Contains(args.Key[0]); + if ((isDelimiter || (SetChipsOnEnter && args.Key == "Enter")) && !string.IsNullOrEmpty(_trackedText)) + { + await ApplyChips(); + if (isDelimiter) + _swallowNextInput = true; + } - if (args.Key == "Backspace" && string.IsNullOrEmpty(ConvertSet(Value)) && Values?.Any() == true) + if (args.Key == "Backspace" && string.IsNullOrEmpty(_trackedText) && Values?.Any() == true) { Values.RemoveAt(Values.Count - 1); await InvokeValuesChanged(); @@ -228,20 +261,23 @@ protected override async Task InvokeKeyDownAsync(KeyboardEventArgs args) private async Task ApplyChips() { Values ??= new(); - if (Value == null || (Values.Contains(Value) && !AllowDuplicates)) + var currentValue = _trackedValue; + if (currentValue == null || (Values.Contains(currentValue) && !AllowDuplicates)) { await SetErrorWithStyle(DuplicateErrorText); return; } SetError(); - Values.Add(Value); + Values.Add(currentValue); await InvokeValuesChanged(); if (AutoClear) { if (RuntimeLocation.IsServerSide) await BlurAsync(); - + await Clear(); + _trackedText = null; + _trackedValue = default; if (RuntimeLocation.IsServerSide) await FocusAsync(); diff --git a/MudBlazor.Extensions/Components/MudExThemeEdit.razor.cs b/MudBlazor.Extensions/Components/MudExThemeEdit.razor.cs index face9892..fb09fa36 100644 --- a/MudBlazor.Extensions/Components/MudExThemeEdit.razor.cs +++ b/MudBlazor.Extensions/Components/MudExThemeEdit.razor.cs @@ -18,7 +18,8 @@ namespace MudBlazor.Extensions.Components; /// public partial class MudExThemeEdit { - private const int ExtraDelay = 200; // Currently needed because of timing issues in MudExObjectEdit. But will fixed later + private const int ExtraDelay = 50; // Reduced from 200ms thanks to batched StateHasChanged in MudExObjectEdit + private bool _isUpdating; private KeyValuePair[] _cssVars; private bool _isLoading = true; @@ -245,12 +246,20 @@ private async Task EditModeChangedInternally(ThemeEditMode arg) private async Task SetTheme(TTheme theme) { - Theme = null; - using var _ = Loading(); - Theme = theme; - await OnThemeChanged(Theme); - StateHasChanged(); - await Task.Delay(ExtraDelay); + if (_isUpdating) + return; + _isUpdating = true; + try + { + using var _ = Loading(); + Theme = theme; + await OnThemeChanged(Theme); + await Task.Delay(ExtraDelay); + } + finally + { + _isUpdating = false; + } } private void UpdateInitialTheme() @@ -290,6 +299,8 @@ private Task OnThemeChanged(TTheme arg) private Task OnPropertyChanged(ObjectEditPropertyMeta arg) { + if (_isUpdating) + return Task.CompletedTask; if(EditMode == ThemeEditMode.Simple && !arg.Settings.Ignored) { if (arg.PropertyInfo.Name == nameof(DefaultTypography.FontFamily)) { SetIfIgnored( diff --git a/MudBlazor.Extensions/Components/ObjectEdit/MudExObjectEdit.razor.cs b/MudBlazor.Extensions/Components/ObjectEdit/MudExObjectEdit.razor.cs index 7a982c1d..2a2fbf10 100644 --- a/MudBlazor.Extensions/Components/ObjectEdit/MudExObjectEdit.razor.cs +++ b/MudBlazor.Extensions/Components/ObjectEdit/MudExObjectEdit.razor.cs @@ -36,6 +36,12 @@ public partial class MudExObjectEdit private T _value; private List _groups = new(); private Type? _registeredEditorType; + private List> _groupedMetaCache; + private bool _groupedMetaCacheDirty = true; + private CancellationTokenSource _stateChangeCts; + private bool _stateChangePending; + private List _cachedFilterValues; + private string _lastFilterKey; /// /// If this is set all properties will be readonly depending on the value otherwise the property settings for meta configuration will be used @@ -403,6 +409,7 @@ public string Filter { var oldValue = _filter; _filter = value; + InvalidateGroupedMetaCache(); _= CheckReinitAsync(oldValue, _filter); } } @@ -419,6 +426,7 @@ public List Filters { var oldValue = _filters; _filters = value; + InvalidateGroupedMetaCache(); _= CheckReinitAsync(oldValue, _filters); } } @@ -595,6 +603,7 @@ internal virtual bool UpdateConditions() if (AutoUpdateConditions) { UpdateAllConditions(); + InvalidateGroupedMetaCache(); return true; } return false; @@ -724,6 +733,7 @@ public virtual async Task SaveState() /// public void Invalidate(bool useRefresh = false) { + InvalidateGroupedMetaCache(); if (useRefresh) Refresh(); else @@ -804,6 +814,7 @@ protected virtual async Task OnPropertyChange(ObjectEditPropertyMeta property) UpdateConditions(); await PropertyChanged.InvokeAsync(property); await ValueChanged.InvokeAsync(Value); + DebouncedStateHasChanged(); if (Value is IComponent c) { try @@ -823,25 +834,44 @@ private async void SetValue(T value) _value = value; MetaInformation?.SetValue(value); await CreateMetaIfNotExists(); + InvalidateGroupedMetaCache(); Invalidate(); } + private List GetEffectiveFilters() + { + var filterKey = $"{Filter}|{string.Join(",", Filters ?? Enumerable.Empty())}"; + if (_cachedFilterValues != null && _lastFilterKey == filterKey) + return _cachedFilterValues; + + _lastFilterKey = filterKey; + _cachedFilterValues = (!string.IsNullOrEmpty(Filter) ? new[] { Filter } : Enumerable.Empty()) + .Concat(Filters ?? Enumerable.Empty()) + .Where(f => !string.IsNullOrWhiteSpace(f)) + .Distinct() + .ToList(); + return _cachedFilterValues; + } + private bool IsInFilter(ObjectEditPropertyMeta propertyMeta) { - var allFilters = (!string.IsNullOrEmpty(Filter) ? new[] { Filter } : Enumerable.Empty()).Concat(Filters ?? Enumerable.Empty()).Distinct().ToList(); + var allFilters = GetEffectiveFilters(); // No filters, nothing to filter against, so return true - return !allFilters.Any() || - // Loop through each filter in allFilters - (from filter in allFilters - where !string.IsNullOrWhiteSpace(filter) - select propertyMeta.Settings.LabelFor(LocalizerToUse).Contains(filter, StringComparison.InvariantCultureIgnoreCase) - || propertyMeta.Settings.DescriptionFor(LocalizerToUse).Contains(filter, StringComparison.InvariantCultureIgnoreCase) - || propertyMeta.PropertyInfo.Name.Contains(filter, StringComparison.InvariantCultureIgnoreCase) - || (propertyMeta.Value?.ToString()?.Contains(filter, StringComparison.InvariantCultureIgnoreCase) == true) - || (propertyMeta.GroupInfo?.Name?.Contains(filter, StringComparison.InvariantCultureIgnoreCase) == true) - || (propertyMeta.RenderData?.Attributes.Values.OfType().Any(x => x.Contains(filter, StringComparison.InvariantCultureIgnoreCase)) == true)) - .Any(matchesCurrentFilter => matchesCurrentFilter); + if (allFilters.Count == 0) + return true; + + foreach (var filter in allFilters) + { + if (propertyMeta.Settings.LabelFor(LocalizerToUse).Contains(filter, StringComparison.OrdinalIgnoreCase) + || propertyMeta.Settings.DescriptionFor(LocalizerToUse).Contains(filter, StringComparison.OrdinalIgnoreCase) + || propertyMeta.PropertyInfo.Name.Contains(filter, StringComparison.OrdinalIgnoreCase) + || (propertyMeta.Value?.ToString()?.Contains(filter, StringComparison.OrdinalIgnoreCase) == true) + || (propertyMeta.GroupInfo?.Name?.Contains(filter, StringComparison.OrdinalIgnoreCase) == true) + || (propertyMeta.RenderData?.Attributes.Values.OfType().Any(x => x.Contains(filter, StringComparison.OrdinalIgnoreCase)) == true)) + return true; + } + return false; } @@ -882,7 +912,19 @@ private List> AllGroupedMetaPropertyIn private List> GroupedMetaPropertyInfos() - => !RenderIgnoredReferences ? DefaultGroupedMetaPropertyInfos() : AllGroupedMetaPropertyInfos(); + { + if (_groupedMetaCacheDirty || _groupedMetaCache == null) + { + _groupedMetaCache = !RenderIgnoredReferences ? DefaultGroupedMetaPropertyInfos() : AllGroupedMetaPropertyInfos(); + _groupedMetaCacheDirty = false; + } + return _groupedMetaCache; + } + + private void InvalidateGroupedMetaCache() + { + _groupedMetaCacheDirty = true; + } private bool ContainsRenderWrapperType(IEnumerable meta, Type type) @@ -943,7 +985,23 @@ protected virtual async Task CreateMetaIfNotExists(bool? reconfigure = null) private void OnMetaUpdateRequired(ObjectEditPropertyMeta meta) { - CallStateHasChanged(); + DebouncedStateHasChanged(); + } + + private void DebouncedStateHasChanged(int delayMs = 50) + { + _stateChangePending = true; + _stateChangeCts?.Cancel(); + _stateChangeCts = new CancellationTokenSource(); + var token = _stateChangeCts.Token; + _ = Task.Delay(delayMs, token).ContinueWith(_ => + { + if (!token.IsCancellationRequested && _stateChangePending) + { + _stateChangePending = false; + InvokeAsync(StateHasChanged); + } + }, token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current); } private async Task OnResetClick(MouseEventArgs arg) @@ -980,7 +1038,7 @@ private string GetStyle() }, SizeUnit, Style); } - private bool HasFixedHeight() => Height != null || MaxHeight != null || Style?.ToLower().Contains("height") == true; + private bool HasFixedHeight() => Height != null || MaxHeight != null || (Style != null && Style.Contains("height", StringComparison.OrdinalIgnoreCase)); private string GetToolbarStickyTop() { @@ -1266,12 +1324,16 @@ private void SetEditorValueProperties(T value) } } + private static readonly System.Collections.Concurrent.ConcurrentDictionary _typePropertiesCache = new(); + private IDictionary GetPropertiesWithPaths(object obj, string currentPath = "") { if (obj == null) return new Dictionary(); var result = new Dictionary(); - foreach (var prop in obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) + var objType = obj.GetType(); + var properties = _typePropertiesCache.GetOrAdd(objType, t => t.GetProperties(BindingFlags.Public | BindingFlags.Instance)); + foreach (var prop in properties) { var propValue = prop.GetValue(obj); var newPath = string.IsNullOrEmpty(currentPath) ? prop.Name : $"{currentPath}.{prop.Name}"; diff --git a/MudBlazor.Extensions/Components/ObjectEdit/MudExObjectEditForm.razor.cs b/MudBlazor.Extensions/Components/ObjectEdit/MudExObjectEditForm.razor.cs index ff8fad4a..0dd2478f 100644 --- a/MudBlazor.Extensions/Components/ObjectEdit/MudExObjectEditForm.razor.cs +++ b/MudBlazor.Extensions/Components/ObjectEdit/MudExObjectEditForm.razor.cs @@ -221,6 +221,7 @@ public partial class MudExObjectEditForm private FluentValidationValidator _fluentValidationValidator; private DataAnnotationsValidator _dataAnnotationValidator; + private CancellationTokenSource _validationCts; /// /// Returns a list of validation results produced by DataAnnotation validation @@ -320,7 +321,19 @@ protected string GetActionBarClass() protected override async Task OnPropertyChange(ObjectEditPropertyMeta property) { await base.OnPropertyChange(property); - Validate(); + DebouncedValidate(); + } + + private void DebouncedValidate(int delayMs = 100) + { + _validationCts?.Cancel(); + _validationCts = new CancellationTokenSource(); + var token = _validationCts.Token; + _ = Task.Delay(delayMs, token).ContinueWith(_ => + { + if (!token.IsCancellationRequested) + InvokeAsync(Validate); + }, token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current); } /// diff --git a/MudBlazor.Extensions/Components/ObjectEdit/MudExPropertyEdit.razor.cs b/MudBlazor.Extensions/Components/ObjectEdit/MudExPropertyEdit.razor.cs index 06a31743..dee584d0 100644 --- a/MudBlazor.Extensions/Components/ObjectEdit/MudExPropertyEdit.razor.cs +++ b/MudBlazor.Extensions/Components/ObjectEdit/MudExPropertyEdit.razor.cs @@ -98,6 +98,8 @@ public DynamicComponent Editor private bool _urlSetDone; private DynamicComponent _editor; + private static MethodInfo _createFieldForExpressionMethod; + private static readonly System.Collections.Concurrent.ConcurrentDictionary _genericMethodCache = new(); //private Expression> CreateFieldForExpression() // => Check.TryCatch>, Exception>(() => Expression.Lambda>(Expression.Property(Expression.Constant(PropertyMeta.ReferenceHolder, PropertyMeta.ReferenceHolder.GetType()), PropertyMeta.PropertyInfo))); @@ -118,8 +120,9 @@ private Expression> CreateFieldForExpression( private object CreateFieldForExpressionPropertyType() { - MethodInfo createFieldForExpression = GetType().GetMethod(nameof(CreateFieldForExpression), BindingFlags.NonPublic | BindingFlags.Instance); - MethodInfo genericMethod = createFieldForExpression?.MakeGenericMethod(PropertyMeta.ComponentFieldType); + _createFieldForExpressionMethod ??= GetType().GetMethod(nameof(CreateFieldForExpression), BindingFlags.NonPublic | BindingFlags.Instance); + var genericMethod = _genericMethodCache.GetOrAdd(PropertyMeta.ComponentFieldType, + type => _createFieldForExpressionMethod?.MakeGenericMethod(type)); return genericMethod?.Invoke(this, Array.Empty()) ?? CreateFieldForExpression(); } @@ -322,11 +325,19 @@ public void Invalidate(bool useRefresh = false) CallStateHasChanged(); } + private static readonly System.Collections.Concurrent.ConcurrentDictionary<(Type, string), PropertyInfo> _valueFieldPropertyCache = new(); + /// /// Returns the current Value independent of the PropertyMeta for example if binding is disabled /// public object GetCurrentValue() - => Editor.Instance?.GetType().GetProperty(PropertyMeta.RenderData.ValueField)?.GetValue(Editor.Instance); + { + var instance = Editor.Instance; + if (instance == null) return null; + var prop = _valueFieldPropertyCache.GetOrAdd((instance.GetType(), PropertyMeta.RenderData.ValueField), + key => key.Item1.GetProperty(key.Item2)); + return prop?.GetValue(instance); + } /// /// Sets a Value independent of the PropertyMeta for example if binding is disabled diff --git a/MudBlazor.Extensions/Components/ObjectEdit/MudExStructuredDataEditor.razor.cs b/MudBlazor.Extensions/Components/ObjectEdit/MudExStructuredDataEditor.razor.cs index 595f604e..bcb4ec96 100644 --- a/MudBlazor.Extensions/Components/ObjectEdit/MudExStructuredDataEditor.razor.cs +++ b/MudBlazor.Extensions/Components/ObjectEdit/MudExStructuredDataEditor.razor.cs @@ -89,9 +89,16 @@ public override async Task SetParametersAsync(ParameterView parameters) ErrorMessage = null; MetaInformation = null; - Value = DataType.HasValue - ? ReflectionHelper.CreateTypeAndDeserialize(Prepare(Data), DataType.Value, nameof(MudExStructuredDataEditor), true) - : ReflectionHelper.CreateTypeAndDeserialize(Prepare(Data), nameof(MudExStructuredDataEditor), true); + try + { + Value = DataType.HasValue + ? ReflectionHelper.CreateTypeAndDeserialize(Prepare(Data), DataType.Value, nameof(MudExStructuredDataEditor), true) + : ReflectionHelper.CreateTypeAndDeserialize(Prepare(Data), nameof(MudExStructuredDataEditor), true); + } + catch (MissingMethodException) + { + ErrorMessage = "Structured data editing is not supported in this runtime. Please update the Nextended.Core package or check Newtonsoft.Json compatibility."; + } } } diff --git a/MudBlazor.Extensions/Components/ObjectEdit/Options/ObjectEditMeta.base.cs b/MudBlazor.Extensions/Components/ObjectEdit/Options/ObjectEditMeta.base.cs index 5ea9a208..85d5e7cb 100644 --- a/MudBlazor.Extensions/Components/ObjectEdit/Options/ObjectEditMeta.base.cs +++ b/MudBlazor.Extensions/Components/ObjectEdit/Options/ObjectEditMeta.base.cs @@ -1,4 +1,5 @@ -using System.Globalization; +using System.Collections.Concurrent; +using System.Globalization; using System.Linq.Expressions; using System.Reflection; using System.Runtime.Serialization; @@ -81,36 +82,46 @@ public static ObjectEditMeta Create(T value, params Action _allowedPropertyCache = new(); + private static readonly Type[] ForbiddenAttributes = { typeof(InjectAttribute), typeof(IgnoreDataMemberAttribute), typeof(IgnoreOnObjectEditAttribute) }; + internal static bool IsAllowedAsPropertyToEdit(PropertyInfo p) { - var forbiddenAttributes = new[] { typeof(InjectAttribute), typeof(IgnoreDataMemberAttribute), typeof(IgnoreOnObjectEditAttribute) }; - return forbiddenAttributes.All(a => p.GetCustomAttribute(a) == null); + return _allowedPropertyCache.GetOrAdd(p, prop => ForbiddenAttributes.All(a => prop.GetCustomAttribute(a) == null)); } + private static readonly ConcurrentDictionary<(Type, PropertyInfo), bool> _componentPropertyAllowedCache = new(); + private static readonly Type[] ForbiddenComponentTypes = { typeof(EventCallback), typeof(EventCallback<>), typeof(Expression<>), typeof(Func<>), typeof(Converter<,>), typeof(Converter<,>), typeof(CultureInfo), typeof(RenderFragment), typeof(RenderFragment<>), typeof(IStringLocalizer<>), typeof(IStringLocalizer) }; + internal static bool IsAllowedAsPropertyToEditOnAComponent(PropertyInfo p) { - if (p.PropertyType.IsNullableOf() && p.Name == nameof(MudBaseDatePicker.PickerMonth)) - return false; // TODO: find out why its so hard crashing without this - if (typeof(T) == typeof(MudChip<>) && p.Name == nameof(MudChip.Value)) - return false; // TODO: find out why its so hard crashing without this + return _componentPropertyAllowedCache.GetOrAdd((typeof(T), p), key => + { + var (ownerType, prop) = key; + if (prop.PropertyType.IsNullableOf() && prop.Name == nameof(MudBaseDatePicker.PickerMonth)) + return false; + if (ownerType == typeof(MudChip<>) && prop.Name == nameof(MudChip.Value)) + return false; - var forbiddenTypes = new[] { typeof(EventCallback), typeof(EventCallback<>), typeof(Expression<>), typeof(Func<>), typeof(Converter<,>), typeof(Converter<,>), typeof(CultureInfo), typeof(RenderFragment), typeof(RenderFragment<>), typeof(IStringLocalizer<>), typeof(IStringLocalizer) }; - var isIComponent = typeof(ComponentBase).IsAssignableFrom(p.DeclaringType); - return (!isIComponent || p.GetCustomAttribute() != null || p.GetCustomAttribute() != null) - && !p.PropertyType.IsFunc() && !p.PropertyType.IsExpression() //&& !p.PropertyType.IsAction() - // && p.PropertyType is {IsInterface: false, IsAbstract: false} // Bad idea - && forbiddenTypes.All(t => t != p.PropertyType && (!p.PropertyType.IsGenericType || t != p.PropertyType.GetGenericTypeDefinition())); + var isIComponent = typeof(ComponentBase).IsAssignableFrom(prop.DeclaringType); + return (!isIComponent || prop.GetCustomAttribute() != null || prop.GetCustomAttribute() != null) + && !prop.PropertyType.IsFunc() && !prop.PropertyType.IsExpression() + && ForbiddenComponentTypes.All(t => t != prop.PropertyType && (!prop.PropertyType.IsGenericType || t != prop.PropertyType.GetGenericTypeDefinition())); + }); } + private static readonly ConcurrentDictionary _editableComponentParamCache = new(); + /// /// Returns whether the given property is editable. /// public static bool IsEditableComponentParameter(PropertyInfo p) { - return (p.GetCustomAttribute() != null || p.GetCustomAttribute() != null) - && !p.PropertyType.IsEventCallback() - && !p.PropertyType.IsExpression(); + return _editableComponentParamCache.GetOrAdd(p, prop => + (prop.GetCustomAttribute() != null || prop.GetCustomAttribute() != null) + && !prop.PropertyType.IsEventCallback() + && !prop.PropertyType.IsExpression()); } diff --git a/MudBlazor.Extensions/Components/ObjectEdit/Options/ObjectEditMeta.cs b/MudBlazor.Extensions/Components/ObjectEdit/Options/ObjectEditMeta.cs index 056efa79..dcafce13 100644 --- a/MudBlazor.Extensions/Components/ObjectEdit/Options/ObjectEditMeta.cs +++ b/MudBlazor.Extensions/Components/ObjectEdit/Options/ObjectEditMeta.cs @@ -1,4 +1,5 @@ -using System.Linq.Expressions; +using System.Collections.Concurrent; +using System.Linq.Expressions; using System.Reflection; using Nextended.Core; using Nextended.Core.Extensions; @@ -11,6 +12,7 @@ namespace MudBlazor.Extensions.Components.ObjectEdit.Options; /// public sealed class ObjectEditMeta : ObjectEditMeta { + private static readonly ConcurrentDictionary<(Type, BindingFlags), PropertyInfo[]> _propertyInfoCache = new(); /// /// Value of the object to be edited. /// @@ -22,6 +24,7 @@ public sealed class ObjectEditMeta : ObjectEditMeta public List> PropertyResolverFunctions { get; set; } = new(); private List _properties; + private List _allPropertiesCache; internal Func OrderFn { get; set; } = meta => meta.Settings.Order; internal bool OrderAscending { get; set; } = true; @@ -38,13 +41,22 @@ internal ObjectEditMeta SetValue(T value) { AllProperties.Apply(pm => pm.SetReferenceHolder(pm.ReferenceHolder == (object)Value ? value : pm.ReferenceHolder)); Value = value; + InvalidateCache(); return this; } /// /// All properties of the object to be edited. /// - public override IList AllProperties => Ordered((_properties ??= ReadProperties()).Recursive(m => m.Children)).ToList(); + public override IList AllProperties => _allPropertiesCache ??= Ordered((_properties ??= ReadProperties()).Recursive(m => m.Children)).ToList(); + + /// + /// Invalidates the cached AllProperties list, forcing recalculation on next access. + /// + public void InvalidateCache() + { + _allPropertiesCache = null; + } internal IEnumerable Ordered(IEnumerable entries) => OrderAscending ? entries.OrderBy(OrderFn) : entries.OrderByDescending(OrderFn); @@ -80,10 +92,16 @@ public ObjectEditPropertyMetaOf Property(Expression> expressi return (ObjectEditPropertyMetaOf) (Property(name) ?? Property(infos)); } + private PropertyInfo[] GetCachedPropertyInfos(Type type) + { + return _propertyInfoCache.GetOrAdd((type, BindingFlags), key => key.Item1.GetProperties(key.Item2)); + } + private IEnumerable GetProperties(Type type, object value, ObjectEditMeta owner = null) { var res = new List(); - foreach (var propertyInfo in (value?.GetType() ?? type).GetProperties(BindingFlags).Where(ShouldResolve)) + var actualType = value?.GetType() ?? type; + foreach (var propertyInfo in GetCachedPropertyInfos(actualType).Where(ShouldResolve)) { try { diff --git a/MudBlazor.Extensions/Components/ObjectEdit/Options/ObjectEditPropertyMeta.cs b/MudBlazor.Extensions/Components/ObjectEdit/Options/ObjectEditPropertyMeta.cs index 28afc931..68983587 100644 --- a/MudBlazor.Extensions/Components/ObjectEdit/Options/ObjectEditPropertyMeta.cs +++ b/MudBlazor.Extensions/Components/ObjectEdit/Options/ObjectEditPropertyMeta.cs @@ -135,7 +135,8 @@ public IRenderData RenderData public void UpdateConditionalSettings(TModel model) { Settings?.UpdateConditionalSettings(model); - RenderData?.UpdateConditionalSettings(model); + if (_renderData != null) + _renderData.UpdateConditionalSettings(model); } /// diff --git a/MudBlazor.Extensions/Components/ObjectEdit/Options/ObjectEditPropertyMetaSettings.cs b/MudBlazor.Extensions/Components/ObjectEdit/Options/ObjectEditPropertyMetaSettings.cs index 9e9aaeb1..889569b6 100644 --- a/MudBlazor.Extensions/Components/ObjectEdit/Options/ObjectEditPropertyMetaSettings.cs +++ b/MudBlazor.Extensions/Components/ObjectEdit/Options/ObjectEditPropertyMetaSettings.cs @@ -109,6 +109,8 @@ private string TextFor(IStringLocalizer localizer, Func re internal void UpdateConditionalSettings(TModel model) { + if (_conditions.Count == 0) + return; _conditions.Where(c => c.modelType == typeof(TModel)).Apply(condition => (condition.condition(model) ? condition.trueFn : condition.falseFn)(this)); _conditions.Where(c => c.modelType == typeof(ObjectEditPropertyMeta)).Apply(condition => (condition.condition(Owner) ? condition.trueFn : condition.falseFn)(this)); _conditions.Where(c => c.modelType == typeof(PropertyInfo)).Apply(condition => (condition.condition(Owner.PropertyInfo) ? condition.trueFn : condition.falseFn)(this)); diff --git a/MudBlazor.Extensions/Components/ObjectEdit/Options/RenderData.base.cs b/MudBlazor.Extensions/Components/ObjectEdit/Options/RenderData.base.cs index 19a546e0..f28b61fc 100644 --- a/MudBlazor.Extensions/Components/ObjectEdit/Options/RenderData.base.cs +++ b/MudBlazor.Extensions/Components/ObjectEdit/Options/RenderData.base.cs @@ -1,4 +1,5 @@ -using Nextended.Blazor.Helper; +using System.Collections.Concurrent; +using Nextended.Blazor.Helper; using Nextended.Core.Extensions; using Nextended.Core.Helper; using System.Reflection; @@ -111,10 +112,17 @@ public void OnRendered(Action onReferenceSet) } + private static readonly ConcurrentDictionary<(Type componentType, string key, Type valueType), bool> _validParameterCache = new(); + /// /// Returns whether the given key and value are valid parameters for the current component type. /// - public bool IsValidParameterAttribute(string key, object value) => ComponentRenderHelper.IsValidParameter(ComponentType, key, value); + public bool IsValidParameterAttribute(string key, object value) + { + if (ComponentType == null) return false; + return _validParameterCache.GetOrAdd((ComponentType, key, value?.GetType()), + k => ComponentRenderHelper.IsValidParameter(k.componentType, k.key, value)); + } /// /// Set of valid attributes for the current component type. @@ -236,15 +244,20 @@ public object Clone() /// public virtual void UpdateConditionalSettings(TModel model) { - Conditions?.Where(c => c.modelType == typeof(TModel)).Apply(condition => (condition.condition(model) ? condition.trueFn : condition.falseFn)(this)); - Conditions?.Where(c => c.modelType == ComponentType).Apply(condition => + if (Conditions == null || Conditions.Count == 0) + { + Wrapper?.UpdateConditionalSettings(model); + return; + } + Conditions.Where(c => c.modelType == typeof(TModel)).Apply(condition => (condition.condition(model) ? condition.trueFn : condition.falseFn)(this)); + Conditions.Where(c => c.modelType == ComponentType).Apply(condition => { var componentInstanceClone = Attributes.ToObject(ComponentType); (condition.condition(componentInstanceClone) ? condition.trueFn : condition.falseFn)(this); }); if (PropertyMeta != null) { - Conditions?.Where(c => c.modelType == typeof(ObjectEditPropertyMeta)).Apply(condition => (condition.condition(PropertyMeta) ? condition.trueFn : condition.falseFn)(this)); - Conditions?.Where(c => c.modelType == typeof(PropertyInfo)).Apply(condition => (condition.condition(PropertyMeta.PropertyInfo) ? condition.trueFn : condition.falseFn)(this)); + Conditions.Where(c => c.modelType == typeof(ObjectEditPropertyMeta)).Apply(condition => (condition.condition(PropertyMeta) ? condition.trueFn : condition.falseFn)(this)); + Conditions.Where(c => c.modelType == typeof(PropertyInfo)).Apply(condition => (condition.condition(PropertyMeta.PropertyInfo) ? condition.trueFn : condition.falseFn)(this)); } // Notice if you want to allow more types as condition you need to do it here, and in ObjectEditPropertyMetaSettings.cs as well Wrapper?.UpdateConditionalSettings(model); diff --git a/MudBlazor.Extensions/Components/ObjectEdit/Options/RenderData.cs b/MudBlazor.Extensions/Components/ObjectEdit/Options/RenderData.cs index d378a3ae..e4dfcaa7 100644 --- a/MudBlazor.Extensions/Components/ObjectEdit/Options/RenderData.cs +++ b/MudBlazor.Extensions/Components/ObjectEdit/Options/RenderData.cs @@ -14,6 +14,8 @@ public sealed class RenderData : RenderData internal override Type FieldType => typeof(TFieldType); internal override Type PropertyType => typeof(TPropertyType); private bool _bindingInitialized = false; + private object _cachedEventTarget; + private Func _cachedValueChanged; /// /// Converter function to convert the property type to the field type. @@ -40,9 +42,11 @@ public RenderData(string valueField, Type componentType, IDictionary(TModel model) { base.UpdateConditionalSettings(model); + if (Conditions == null || Conditions.Count == 0) + return; // fallback if condition not match model, we try property value instead - Conditions?.Where(c => c.modelType == typeof(TPropertyType)).Apply(condition => (condition.condition(ValueWrapper.Value) ? condition.trueFn : condition.falseFn)(this)); - Conditions?.Where(c => c.modelType == typeof(TFieldType)).Apply(condition => (condition.condition(ToFieldTypeConverterFn(ValueWrapper.Value)) ? condition.trueFn : condition.falseFn)(this)); + Conditions.Where(c => c.modelType == typeof(TPropertyType)).Apply(condition => (condition.condition(ValueWrapper.Value) ? condition.trueFn : condition.falseFn)(this)); + Conditions.Where(c => c.modelType == typeof(TFieldType)).Apply(condition => (condition.condition(ToFieldTypeConverterFn(ValueWrapper.Value)) ? condition.trueFn : condition.falseFn)(this)); } /// @@ -104,6 +108,15 @@ internal bool AttachValueChanged(object eventTarget, Func valueChanged) var eventKeyName = $"{ValueField}Changed"; if (DisableValueBinding || !IsValidParameterAttribute(eventKeyName, null)) return false; + // Only recreate the EventCallback if the target or delegate changed + if (_cachedEventTarget == eventTarget && _cachedValueChanged == valueChanged && Attributes.ContainsKey(eventKeyName)) + { + // Update value binding without recreating the callback + Attributes.AddOrUpdate(ValueField, ToFieldTypeConverterFn(ValueWrapper.Value)); + return true; + } + _cachedEventTarget = eventTarget; + _cachedValueChanged = valueChanged; Attributes.AddOrUpdate(eventKeyName, RuntimeHelpers.TypeCheck( EventCallback.Factory.Create( eventTarget, diff --git a/MudBlazor.Extensions/Components/ObjectEdit/Options/RenderData.defaults.cs b/MudBlazor.Extensions/Components/ObjectEdit/Options/RenderData.defaults.cs index b2459078..3dd9f363 100644 --- a/MudBlazor.Extensions/Components/ObjectEdit/Options/RenderData.defaults.cs +++ b/MudBlazor.Extensions/Components/ObjectEdit/Options/RenderData.defaults.cs @@ -1,4 +1,5 @@ -using System.Globalization; +using System.Collections.Concurrent; +using System.Globalization; using System.Linq.Expressions; using Microsoft.Extensions.DependencyInjection; using Microsoft.JSInterop; @@ -407,24 +408,38 @@ public static IRenderData RegisterDefault(Type propertyType, IRenderData renderD => RegisterDefault(typeof(TPropertyType), Options.RenderData.For(valueField).AddAttributes(false, options?.ToArray())) as RenderData; + private static readonly ConcurrentDictionary _enumTypeCache = new(); + private static readonly ConcurrentDictionary _collectionTypeCache = new(); + private static IRenderData TryFindDynamicRenderData(ObjectEditPropertyMeta propertyMeta) { var propertyType = propertyMeta.PropertyInfo.PropertyType; if (propertyType.IsEnum || propertyType.IsNullableEnum()) // Enum support { - var controlType = typeof(MudExEnumSelect<>).MakeGenericType(propertyType); - var renderDataType = typeof(RenderData<,>).MakeGenericType(propertyType, propertyType); - return renderDataType.CreateInstance(nameof(MudExEnumSelect.Value), controlType, null); + var cached = _enumTypeCache.GetOrAdd(propertyType, pt => + { + var controlType = typeof(MudExEnumSelect<>).MakeGenericType(pt); + var renderDataType = typeof(RenderData<,>).MakeGenericType(pt, pt); + return (controlType, renderDataType); + }); + return cached?.renderDataType.CreateInstance(nameof(MudExEnumSelect.Value), cached?.controlType, null); } - + if (propertyMeta.PropertyInfo.PropertyType.IsCollection() || propertyMeta.PropertyInfo.PropertyType.IsEnumerable()) // Collection support { // TODO: When IEnumerable maybe disable add button try { var collectionType = propertyType.GetGenericArguments().FirstOrDefault() ?? propertyType.GetElementType(); - var renderDataType = typeof(RenderData<,>).MakeGenericType(propertyMeta.PropertyInfo.PropertyType, typeof(ICollection<>).MakeGenericType(collectionType)); - var type = typeof(MudExCollectionEditor<>).MakeGenericType(collectionType); - return Activator.CreateInstance(renderDataType, nameof(MudExCollectionEditor.Items), type, new Dictionary() + var cached = _collectionTypeCache.GetOrAdd(propertyType, pt => + { + var ct = pt.GetGenericArguments().FirstOrDefault() ?? pt.GetElementType(); + if (ct == null) return null; + var rdt = typeof(RenderData<,>).MakeGenericType(pt, typeof(ICollection<>).MakeGenericType(ct)); + var edt = typeof(MudExCollectionEditor<>).MakeGenericType(ct); + return (rdt, edt); + }); + if (cached == null) return null; + return Activator.CreateInstance(cached.Value.renderDataType, nameof(MudExCollectionEditor.Items), cached.Value.editorType, new Dictionary() { //{nameof(MudExCollectionEditor.MaxHeight), 400} // TODO: Make this configurable or think about default setting max height to have the advantages of virtualization }) as IRenderData; @@ -451,10 +466,14 @@ private static IRenderData FindFromProvider(ObjectEditPropertyMeta propertyMeta) .FirstOrDefault(renderData => renderData != null); } + private static readonly ConcurrentDictionary<(Type providerType, Type propertyType), bool> _providerValidityCache = new(); + private static bool ProviderIsValidFor(IDefaultRenderDataProvider defaultRenderDataProvider, ObjectEditPropertyMeta propertyMeta) { var providerType = defaultRenderDataProvider.GetType(); - return providerType.ImplementsInterface(typeof(IDefaultRenderDataProviderFor<>).MakeGenericType(propertyMeta.PropertyInfo.PropertyType)) - || !providerType.GetInterfaces().Any(p => p.IsGenericType && p.GetGenericTypeDefinition() == typeof(IDefaultRenderDataProviderFor<>)); + var propertyType = propertyMeta.PropertyInfo.PropertyType; + return _providerValidityCache.GetOrAdd((providerType, propertyType), key => + key.providerType.ImplementsInterface(typeof(IDefaultRenderDataProviderFor<>).MakeGenericType(key.propertyType)) + || !key.providerType.GetInterfaces().Any(p => p.IsGenericType && p.GetGenericTypeDefinition() == typeof(IDefaultRenderDataProviderFor<>))); } } \ No newline at end of file diff --git a/MudBlazor.Extensions/DialogService.cs b/MudBlazor.Extensions/DialogService.cs index b675c1e9..20f28d85 100644 --- a/MudBlazor.Extensions/DialogService.cs +++ b/MudBlazor.Extensions/DialogService.cs @@ -208,7 +208,7 @@ public static async Task GetDialogAsync(this IDialogReference dialogRefere await Task.Run(() => { while (dialogReference.Dialog == null) - Thread.Sleep(10); + Thread.Sleep(10); }); return dialogReference.Dialog as T; } @@ -299,7 +299,6 @@ private static async Task> WaitForCallbackR internal static DialogOptionsEx PrepareOptionsAfterShow(DialogOptionsEx options) { options ??= DefaultOptions(); - options = options.CloneOptions(); (options.DialogAppearance ??= MudExAppearance.Empty()).WithCss(options.DisableSizeMarginY ?? false ? MudExCss.Classes.Dialog.FullHeightWithoutMargin : MudExCss.Classes.Dialog.FullHeightWithMargin, options.FullHeight ?? false); (options.DialogAppearance ??= MudExAppearance.Empty()).WithCss(options.MaxHeight != null ? $"mud-ex-dialog-max-height-{options.MaxHeight.GetDescription()}" : string.Empty); diff --git a/MudBlazor.Extensions/Helper/ComponentHelper.cs b/MudBlazor.Extensions/Helper/ComponentHelper.cs index f98046e7..6aaf8a4a 100644 --- a/MudBlazor.Extensions/Helper/ComponentHelper.cs +++ b/MudBlazor.Extensions/Helper/ComponentHelper.cs @@ -1,4 +1,5 @@ -using System.Reflection; +using System.Collections.Concurrent; +using System.Reflection; using Nextended.Core.Extensions; using Microsoft.AspNetCore.Components; using Nextended.Core.Helper; @@ -7,17 +8,21 @@ namespace MudBlazor.Extensions.Helper; public static class ComponentHelper { + private static readonly FieldInfo RenderHandleField = typeof(ComponentBase) + .GetField("_renderHandle", BindingFlags.NonPublic | BindingFlags.Instance); + + private static readonly ConcurrentDictionary StateHasChangedMethodCache = new(); + private static readonly ConcurrentDictionary InvokeAsyncMethodCache = new(); + private static readonly ConcurrentDictionary GetDialogReferenceMethodCache = new(); + public static bool IsRenderHandleAssigned(this IComponent component) { try { if (component is ComponentBase baseComponent) - { - var fieldInfo = typeof(ComponentBase) - .GetField("_renderHandle", BindingFlags.NonPublic | BindingFlags.Instance); - - var value = fieldInfo?.GetValue(baseComponent); - var handleValue = value as RenderHandle? ?? default; + { + var value = RenderHandleField?.GetValue(baseComponent); + var handleValue = value as RenderHandle? ?? default; return handleValue.IsInitialized; } } @@ -29,9 +34,12 @@ public static bool IsRenderHandleAssigned(this IComponent component) internal static Task CallReflectionStateHasChanged(this IComponent cmp) { - var stateHasChangedMethod = cmp.GetType().GetMethod("StateHasChanged", BindingFlags.Instance | BindingFlags.NonPublic); - var invokeAsyncMethod = cmp.GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic) - .FirstOrDefault(m => m.Name == "InvokeAsync" && m.GetParameters()?.FirstOrDefault()?.ParameterType == typeof(Action)); + var type = cmp.GetType(); + var stateHasChangedMethod = StateHasChangedMethodCache.GetOrAdd(type, + t => t.GetMethod("StateHasChanged", BindingFlags.Instance | BindingFlags.NonPublic)); + var invokeAsyncMethod = InvokeAsyncMethodCache.GetOrAdd(type, + t => t.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic) + .FirstOrDefault(m => m.Name == "InvokeAsync" && m.GetParameters()?.FirstOrDefault()?.ParameterType == typeof(Action))); if (stateHasChangedMethod == null) return Task.CompletedTask; @@ -78,10 +86,9 @@ internal static MudDialogProvider FindMudDialogProvider(this IDialogReference co internal static IDialogReference GetDialogReference(this IMudDialogInstance dlgInstance) { var provider = dlgInstance.FindMudDialogProvider(); - - //ComponentHelper.FindMudDialogProvider - MethodInfo getDialog = provider.GetType().GetMethod("GetDialogReference", - BindingFlags.NonPublic | BindingFlags.Instance); + var providerType = provider.GetType(); + var getDialog = GetDialogReferenceMethodCache.GetOrAdd(providerType, + t => t.GetMethod("GetDialogReference", BindingFlags.NonPublic | BindingFlags.Instance)); IDialogReference? dialogRef = (IDialogReference)getDialog.Invoke(provider, new object[] { dlgInstance.Id }); return dialogRef; } diff --git a/MudBlazor.Extensions/Helper/EnumHelper.cs b/MudBlazor.Extensions/Helper/EnumHelper.cs index b48bb350..f7761244 100644 --- a/MudBlazor.Extensions/Helper/EnumHelper.cs +++ b/MudBlazor.Extensions/Helper/EnumHelper.cs @@ -1,12 +1,28 @@ -using MudBlazor.Extensions.Components.ObjectEdit.Options; +using System.Collections.Concurrent; +using System.Reflection; +using MudBlazor.Extensions.Components.ObjectEdit.Options; namespace MudBlazor.Extensions.Helper; public class EnumHelper { + private static readonly ConcurrentDictionary<(Type enumType, string valueName), FieldInfo> FieldInfoCache = new(); + private static readonly ConcurrentDictionary<(Type enumType, string valueName, Type attributeType, bool inherit), object[]> AttributeCache = new(); + public static TAttribute[] GetCustomAttributes(Enum val, bool inherit) { - var customAttributes =val.GetType().GetField(val.ToString())?.GetCustomAttributes(typeof(TAttribute), inherit); + var enumType = val.GetType(); + var valueName = val.ToString(); + var attrType = typeof(TAttribute); + + var cacheKey = (enumType, valueName, attrType, inherit); + var customAttributes = AttributeCache.GetOrAdd(cacheKey, key => + { + var fieldKey = (key.enumType, key.valueName); + var fieldInfo = FieldInfoCache.GetOrAdd(fieldKey, fk => fk.enumType.GetField(fk.valueName)); + return fieldInfo?.GetCustomAttributes(key.attributeType, key.inherit); + }); + return customAttributes?.Select(o => (TAttribute)o).ToArray() ?? Array.Empty(); } } \ No newline at end of file diff --git a/MudBlazor.Extensions/Helper/MudExCssBuilder.cs b/MudBlazor.Extensions/Helper/MudExCssBuilder.cs index 434a1f96..901fae88 100644 --- a/MudBlazor.Extensions/Helper/MudExCssBuilder.cs +++ b/MudBlazor.Extensions/Helper/MudExCssBuilder.cs @@ -106,13 +106,21 @@ public MudExCssBuilder AddClass(string value) /// Adds one or more css classes to this builder /// public MudExCssBuilder AddClass(MudExCss.Classes cssClass, params MudExCss.Classes[] other) - => new[] { cssClass }.Concat(other).Aggregate(this, (acc, f) => acc.AddClass(f, true)); - + { + AddClass(cssClass, true); + foreach (var c in other) AddClass(c, true); + return this; + } + /// - /// Adds one or more css classes to this builder + /// Adds one or more css classes to this builder /// public MudExCssBuilder AddClass(string value, params string[] other) - => new[] { value }.Concat(other).Aggregate(this, (acc, f) => acc.AddClass(f, true)); + { + AddClass(value, true); + foreach (var v in other) AddClass(v, true); + return this; + } /// /// Adds one or more css classes to this builder if given condition is true @@ -207,7 +215,17 @@ public MudExCssBuilder AddClassFromAttributes(IReadOnlyDictionary /// Builds and returns an applicable css string /// - public string Build() => string.Join(" ", _cssClasses.Select(c => $"{c.Key}")); + public string Build() + { + if (_cssClasses.Count == 0) return string.Empty; + var sb = new System.Text.StringBuilder(); + foreach (var key in _cssClasses.Keys) + { + if (sb.Length > 0) sb.Append(' '); + sb.Append(key); + } + return sb.ToString(); + } /// public override string ToString() => Build(); diff --git a/MudBlazor.Extensions/Helper/MudExStyleBuilder.cs b/MudBlazor.Extensions/Helper/MudExStyleBuilder.cs index d543c81a..06d6ae21 100644 --- a/MudBlazor.Extensions/Helper/MudExStyleBuilder.cs +++ b/MudBlazor.Extensions/Helper/MudExStyleBuilder.cs @@ -22,12 +22,17 @@ namespace MudBlazor.Extensions.Helper; [RegisterAs(typeof(MudExStyleBuilder), ServiceLifetime = ServiceLifetime.Scoped)] public sealed class MudExStyleBuilder : IAsyncDisposable, IMudExStyleAppearance { - private static readonly string[] PropertiesToAddUnits = { "height", "width", "min-height", "min-width", "max-height", "max-width", + private static readonly HashSet PropertiesToAddUnits = new() { "height", "width", "min-height", "min-width", "max-height", "max-width", "padding", "padding-top", "padding-right", "padding-bottom", "padding-left", "margin", "margin-top", "margin-right", "margin-bottom", "margin-left", "border-width", "border-top-width", "border-right-width", "border-bottom-width", "border-left-width", "font-size", "letter-spacing", "line-height", "word-spacing", "text-indent", "column-gap", "column-width", "top", "right", "bottom", "left", "transform", "translate", "translateX", "translateY", "translateZ", "translate3d", "rotate", "rotateX", "rotateY", "rotateZ", "scale", "scaleX", "scaleY", "scaleZ", "scale3d", "skew", "skewX", "skewY", "perspective"}; + private static readonly Regex CamelToKebabRegex = new("(? StylePropertyCache = new(); + private static readonly ConcurrentDictionary CamelToKebabCache = new(); private Dictionary _additionalStyles = new(); private readonly List _rawStyles = new(); @@ -90,20 +95,22 @@ public static string GenerateStyleString(object obj, CssUnit cssUnit, string exi { string unit = cssUnit.GetDescription(); var cssBuilder = new StringBuilder(); - var properties = obj.GetType().GetProperties(); + var objType = obj.GetType(); + var properties = StylePropertyCache.GetOrAdd(objType, t => t.GetProperties()); foreach (var property in properties) { - var cssPropertyName = Regex.Replace(property.Name, "(? CamelToKebabRegex.Replace(name, "-$1").ToLower()); object propertyValue = property.GetValue(obj, null); if (propertyValue != null) { if (propertyValue is Color color) propertyValue = color.CssVarDeclaration(); - var formattedPropertyValue = propertyValue is string ? $"{propertyValue}" : propertyValue.ToString(); + var formattedPropertyValue = propertyValue is string s ? s : propertyValue.ToString(); if (int.TryParse(formattedPropertyValue, out _) && PropertiesToAddUnits.Contains(cssPropertyName)) formattedPropertyValue += unit; - cssBuilder.Append(cssPropertyName + ": " + formattedPropertyValue + ";"); + cssBuilder.Append(cssPropertyName).Append(": ").Append(formattedPropertyValue).Append(';'); } } @@ -119,17 +126,16 @@ public static string GenerateStyleString(object obj, CssUnit cssUnit, string exi public static string CombineStyleStrings(string cssString, string leadingCssString) { var cssProperties = new Dictionary(); - var cssRegex = new Regex(@"([\w-]+)\s*:\s*([^;]+)"); - var cssProperties1 = cssRegex.Matches(cssString); - foreach (var property in cssProperties1.Cast()) - cssProperties.TryAdd(property.Groups[1].Value.Trim(), property.Groups[2].Value.Trim()); + foreach (Match match in CssPropertyRegex.Matches(cssString)) + cssProperties.TryAdd(match.Groups[1].Value.Trim(), match.Groups[2].Value.Trim()); + foreach (Match match in CssPropertyRegex.Matches(leadingCssString)) + cssProperties[match.Groups[1].Value.Trim()] = match.Groups[2].Value.Trim(); - var cssProperties2 = cssRegex.Matches(leadingCssString); - foreach (var property in cssProperties2.Cast()) - cssProperties[property.Groups[1].Value.Trim()] = property.Groups[2].Value.Trim(); - - return cssProperties.Aggregate("", (current, property) => current + (property.Key + ": " + property.Value + "; ")); + var sb = new StringBuilder(); + foreach (var property in cssProperties) + sb.Append(property.Key).Append(": ").Append(property.Value).Append("; "); + return sb.ToString(); } /// @@ -141,6 +147,8 @@ public static string CombineStyleStrings(string cssString, string leadingCssStri public static T StyleStringToObject(string css) where T : new() { var obj = new T(); + var objType = typeof(T); + var typeProperties = StylePropertyCache.GetOrAdd(objType, t => t.GetProperties()); // Split the CSS string into individual properties var properties = css.Split(';'); @@ -156,13 +164,14 @@ public static string CombineStyleStrings(string cssString, string leadingCssStri var propertyName = propertyParts[0].Trim(); var propertyValue = propertyParts[1].Trim(); - // Convert the property name to camelCase - propertyName = Regex.Replace(propertyName, "-([a-z])", m => m.Groups[1].Value.ToUpperInvariant()).ToUpper(true); + // Convert the property name to PascalCase + propertyName = KebabToCamelRegex.Replace(propertyName, m => m.Groups[1].Value.ToUpperInvariant()).ToUpper(true); // Try to set the property value on the object try { - obj.GetType().GetProperty(propertyName)?.SetValue(obj, propertyValue); + var prop = Array.Find(typeProperties, p => p.Name == propertyName); + prop?.SetValue(obj, propertyValue); } catch (Exception) { diff --git a/MudBlazor.Extensions/MudBlazor.Extensions.csproj b/MudBlazor.Extensions/MudBlazor.Extensions.csproj index d6637da1..2cec5162 100644 --- a/MudBlazor.Extensions/MudBlazor.Extensions.csproj +++ b/MudBlazor.Extensions/MudBlazor.Extensions.csproj @@ -75,8 +75,8 @@ - - + + diff --git a/MudBlazor.Extensions/wwwroot/docs/MudBlazor.Extensions.xml b/MudBlazor.Extensions/wwwroot/docs/MudBlazor.Extensions.xml index 09c3bcb0..4a3eba0e 100644 --- a/MudBlazor.Extensions/wwwroot/docs/MudBlazor.Extensions.xml +++ b/MudBlazor.Extensions/wwwroot/docs/MudBlazor.Extensions.xml @@ -1482,7 +1482,7 @@ Gets or Sets Light Bulb Size Unit Property. - + Methods returns List of MudExCardHoverMode, where hover modes are applied. @@ -4997,9 +4997,6 @@ Custom numeric down icon. - - - @@ -7920,6 +7917,12 @@ + + + + + + @@ -14360,6 +14363,11 @@ All properties of the object to be edited. + + + Invalidates the cached AllProperties list, forcing recalculation on next access. + + All properties of the object to be edited with excepting the given. @@ -20736,7 +20744,7 @@ - Adds one or more css classes to this builder + Adds one or more css classes to this builder From 0d05d865627b53a8c0ac12950c0aa1bc6aa2bf94 Mon Sep 17 00:00:00 2001 From: Florian Gilde Date: Sun, 1 Mar 2026 18:15:04 +0100 Subject: [PATCH 11/11] version --- .../MainSample.WebAssembly/wwwroot/index.html | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Samples/MainSample.WebAssembly/wwwroot/index.html b/Samples/MainSample.WebAssembly/wwwroot/index.html index 99bf1c6b..7af5da9a 100644 --- a/Samples/MainSample.WebAssembly/wwwroot/index.html +++ b/Samples/MainSample.WebAssembly/wwwroot/index.html @@ -21,17 +21,17 @@ - + - - - - + + + + - - - + + + @@ -48,7 +48,7 @@ -

MudBlazor.Extensions v9.0.0-prev-2602282219

+

MudBlazor.Extensions v9.0.0-prev-2603011811

for MudBlazor 9.0.0

@@ -83,8 +83,8 @@

MudBlazor.Extensions v9.0.0-prev-2602282219

- - + +