Skip to content

Commit 7dbbbaf

Browse files
committed
Refresh closed select presentation from live state
1 parent c2adfda commit 7dbbbaf

8 files changed

Lines changed: 185 additions & 6 deletions

File tree

‎src/CodeBeam.MudBlazor.Extensions/Components/SelectExtended/MudSelectExtended.PresentationState.cs‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ public partial class MudSelectExtended<T>
99
private bool ValuesEqual(T? left, T? right)
1010
=> (_comparer ?? EqualityComparer<T?>.Default).Equals(left, right);
1111

12+
internal bool PresentationValuesEqual(T? left, T? right)
13+
=> ValuesEqual(left, right);
14+
1215
private bool TryGetCollectionValue(T? value, out T? collectionValue)
1316
{
1417
if (ItemCollection != null)
@@ -30,6 +33,16 @@ private bool TryGetCollectionValue(T? value, out T? collectionValue)
3033
private MudSelectItemExtended<T?>? FindRegisteredItem(T? value)
3134
=> Items?.FirstOrDefault(item => ValuesEqual(item.Value, value));
3235

36+
internal bool IsSelectedPresentationValue(T? value)
37+
=> MultiSelection
38+
? SelectedValues?.Contains(value, _comparer) == true
39+
: ValuesEqual(ReadValue, value);
40+
41+
private bool _declarativePresentationDirty;
42+
43+
internal void MarkDeclarativePresentationDirty()
44+
=> _declarativePresentationDirty = true;
45+
3346
/// <summary>
3447
/// Resolves the selected option for ItemContent presentation without requiring that
3548
/// option to have a currently rendered list-item component.

‎src/CodeBeam.MudBlazor.Extensions/Components/SelectExtended/MudSelectExtended.SelectionText.cs‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ protected override async Task OnParametersSetAsync()
77
{
88
await base.OnParametersSetAsync();
99

10-
// SelectedValues is a synchronous parameter setter. Its existing async text update
11-
// cannot be awaited there, so reconcile collection-backed presentation after the
12-
// complete parameter set has been applied. This is value-driven and does not depend
13-
// on any MudSelectItemExtended component being materialized.
14-
if (MultiSelection && ItemCollection is not null)
10+
// Reconcile collection-backed presentation after the complete parameter set has
11+
// been applied. ItemCollection and the selected values are authoritative, so display
12+
// text is derived from their current state rather than retained item components.
13+
//
14+
// This also covers mutable selected objects whose display representation changes
15+
// without replacing the collection or selected-value reference.
16+
if (ItemCollection is not null)
1517
await UpdateTextPropertyAsync(false);
1618
}
1719
}

‎src/CodeBeam.MudBlazor.Extensions/Components/SelectExtended/MudSelectExtended.razor.cs‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -829,9 +829,17 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
829829
await KeyInterceptorService.SubscribeAsync(_elementId, options, keyDown: HandleKeyDownAsync, keyUp: HandleKeyUpAsync);
830830

831831
await UpdateTextPropertyAsync(false);
832+
_declarativePresentationDirty = false;
832833
_list?.ForceUpdateItems();
833834
StateHasChanged();
834835
}
836+
else if (_declarativePresentationDirty)
837+
{
838+
_declarativePresentationDirty = false;
839+
await UpdateTextPropertyAsync(false);
840+
StateHasChanged();
841+
}
842+
835843
//Console.WriteLine("Select rendered");
836844
await base.OnAfterRenderAsync(firstRender);
837845
}
@@ -1166,9 +1174,16 @@ protected internal bool Add(MudSelectItemExtended<T?>? item)
11661174
if (item == null)
11671175
return false;
11681176

1177+
var isSelected = IsSelectedPresentationValue(item.Value);
1178+
11691179
if (!_items.Contains(item) && FindRegisteredItem(item.Value) == null)
1180+
{
11701181
_items.Add(item);
11711182

1183+
if (isSelected)
1184+
MarkDeclarativePresentationDirty();
1185+
}
1186+
11721187
return ValuesEqual(item.Value, ReadValue);
11731188
}
11741189

@@ -1181,7 +1196,10 @@ protected internal void Remove(MudSelectItemExtended<T?>? item)
11811196
if (item == null)
11821197
return;
11831198

1184-
_items.Remove(item);
1199+
var wasSelected = IsSelectedPresentationValue(item.Value);
1200+
1201+
if (_items.Remove(item) && wasSelected)
1202+
MarkDeclarativePresentationDirty();
11851203
}
11861204

11871205
#endregion

‎src/CodeBeam.MudBlazor.Extensions/Components/SelectExtended/MudSelectItemExtended.razor.cs‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ public partial class MudSelectItemExtended<T> : MudComponentBase, IDisposable
1616

1717
private IMudSelectExtended? _parent;
1818
private MudSelectExtended<T?>? _registeredSelect;
19+
private bool _hasRegisteredPresentationMetadata;
20+
private T? _registeredValue;
21+
private string? _registeredDisplayString;
22+
private bool _registeredDisabled;
23+
private bool _registeredIsFunctional;
24+
private string? _registeredHref;
25+
private string? _registeredClass;
26+
private string? _registeredStyle;
1927

2028
internal MudSelectExtended<T?>? MudSelectExtended => (MudSelectExtended<T?>?)_parent;
2129

@@ -166,18 +174,62 @@ protected override void OnParametersSet()
166174
// backed items are always transient view components; ItemCollection and selected values
167175
// are authoritative regardless of whether the visible list itself is virtualized.
168176
var shouldRegister = select != null && select.ItemCollection == null && HideContent;
177+
var previousValue = _registeredValue;
178+
var presentationMetadataChanged = HasRegisteredPresentationMetadataChanged(select);
169179

170180
if (!shouldRegister || !ReferenceEquals(_registeredSelect, select))
171181
{
172182
_registeredSelect?.Remove(this);
173183
_registeredSelect = null;
184+
_hasRegisteredPresentationMetadata = false;
174185
}
175186

176187
if (shouldRegister && _registeredSelect == null)
177188
{
178189
select!.Add(this);
179190
_registeredSelect = select;
191+
CaptureRegisteredPresentationMetadata();
180192
}
193+
else if (shouldRegister && presentationMetadataChanged)
194+
{
195+
CaptureRegisteredPresentationMetadata();
196+
197+
if (select!.IsSelectedPresentationValue(previousValue) ||
198+
select.IsSelectedPresentationValue(Value))
199+
{
200+
select.MarkDeclarativePresentationDirty();
201+
}
202+
}
203+
}
204+
205+
private bool HasRegisteredPresentationMetadataChanged(MudSelectExtended<T?>? select)
206+
{
207+
if (!_hasRegisteredPresentationMetadata)
208+
return false;
209+
210+
var valueChanged = select == null
211+
? !EqualityComparer<T?>.Default.Equals(_registeredValue, Value)
212+
: !select.PresentationValuesEqual(_registeredValue, Value);
213+
214+
return valueChanged
215+
|| _registeredDisplayString != DisplayString
216+
|| _registeredDisabled != Disabled
217+
|| _registeredIsFunctional != IsFunctional
218+
|| _registeredHref != Href
219+
|| _registeredClass != Class
220+
|| _registeredStyle != Style;
221+
}
222+
223+
private void CaptureRegisteredPresentationMetadata()
224+
{
225+
_registeredValue = Value;
226+
_registeredDisplayString = DisplayString;
227+
_registeredDisabled = Disabled;
228+
_registeredIsFunctional = IsFunctional;
229+
_registeredHref = Href;
230+
_registeredClass = Class;
231+
_registeredStyle = Style;
232+
_hasRegisteredPresentationMetadata = true;
181233
}
182234

183235
/// <summary>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace MudExtensions.UnitTests.TestComponents;
2+
3+
public sealed class MutableSelectItem
4+
{
5+
public MutableSelectItem(int id, string name)
6+
{
7+
Id = id;
8+
Name = name;
9+
}
10+
11+
public int Id { get; }
12+
13+
public string Name { get; set; }
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
@namespace MudExtensions.UnitTests.TestComponents
2+
3+
<MudPopoverProvider />
4+
5+
<MudSelectExtended T="string" Value="@Selected">
6+
<MudSelectItemExtended T="string" Value="@ItemValue" Text="@ItemText" />
7+
</MudSelectExtended>
8+
9+
@code {
10+
private const string ItemValue = "one";
11+
12+
public string Selected { get; } = ItemValue;
13+
public string ItemText { get; private set; } = "One";
14+
15+
public void RenameSelectedItem(string text)
16+
{
17+
ItemText = text;
18+
StateHasChanged();
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
@namespace MudExtensions.UnitTests.TestComponents
2+
3+
<MudPopoverProvider />
4+
5+
<MudSelectExtended T="MutableSelectItem"
6+
ItemCollection="@Items"
7+
Value="@Selected"
8+
ToStringFunc="@Format" />
9+
10+
@code {
11+
public List<MutableSelectItem?> Items { get; } =
12+
[
13+
new(1, "One"),
14+
new(2, "Two"),
15+
new(3, "Three")
16+
];
17+
18+
public MutableSelectItem Selected { get; }
19+
20+
public SelectSingleItemCollectionMutationTest()
21+
{
22+
Selected = Items[1]!;
23+
}
24+
25+
private static string? Format(MutableSelectItem? item) => item?.Name;
26+
27+
public void RenameSelected(string name)
28+
{
29+
Selected.Name = name;
30+
StateHasChanged();
31+
}
32+
}

‎tests/CodeBeam.MudBlazor.Extensions.UnitTests/Components/SelectExtendedPresentationStateTests.cs‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using AwesomeAssertions;
22
using Bunit;
33
using Microsoft.AspNetCore.Components;
4+
using MudExtensions.UnitTests.TestComponents;
45

56
namespace MudExtensions.UnitTests.Components
67
{
@@ -30,6 +31,33 @@ public void DeclarativeItems_RegisterMetadataWithoutHiddenList()
3031
cut.FindComponents<MudSelectItemExtended<string>>().Should().ContainSingle();
3132
}
3233

34+
35+
[Test]
36+
public async Task ItemCollection_SelectedObjectMutation_RefreshesClosedPresentation()
37+
{
38+
var cut = Context.Render<SelectSingleItemCollectionMutationTest>();
39+
40+
cut.Find("input").GetAttribute("value").Should().Be("Two");
41+
42+
await cut.InvokeAsync(() => cut.Instance.RenameSelected("Two updated"));
43+
44+
cut.WaitForAssertion(() =>
45+
cut.Find("input").GetAttribute("value").Should().Be("Two updated"));
46+
}
47+
48+
[Test]
49+
public async Task DeclarativeSelectedItemTextMutation_RefreshesClosedPresentation()
50+
{
51+
var cut = Context.Render<SelectDeclarativeSelectedItemMutationTest>();
52+
53+
cut.Find("input").GetAttribute("value").Should().Be("One");
54+
55+
await cut.InvokeAsync(() => cut.Instance.RenameSelectedItem("One updated"));
56+
57+
cut.WaitForAssertion(() =>
58+
cut.Find("input").GetAttribute("value").Should().Be("One updated"));
59+
}
60+
3361
[Test]
3462
public async Task ItemCollection_SelectOptionByIndex_UsesLogicalCollection()
3563
{

0 commit comments

Comments
 (0)