fix(ui): normalize TreeView expanded keys to strings for sessionStorage compatibility

Keys from KeySelector (e.g. boxed int) were compared against string keys
restored from sessionStorage, causing expansion state to be lost on
navigation. All keys are now normalized to strings internally.
This commit is contained in:
Joseph Doherty
2026-03-23 06:07:52 -04:00
parent a0a6bb4986
commit 0abaa47de2

View File

@@ -32,7 +32,7 @@ else
var key = KeySelector(item); var key = KeySelector(item);
var children = ChildrenSelector(item); var children = ChildrenSelector(item);
var isBranch = HasChildrenSelector(item); var isBranch = HasChildrenSelector(item);
var isExpanded = _expandedKeys.Contains(key); var isExpanded = _expandedKeys.Contains(KeyStr(key));
<li role="treeitem" @key="key" <li role="treeitem" @key="key"
aria-expanded="@(isBranch ? (isExpanded ? "true" : "false") : null)" aria-expanded="@(isBranch ? (isExpanded ? "true" : "false") : null)"
@@ -66,7 +66,10 @@ else
@code { @code {
private IReadOnlyList<TItem>? _items; private IReadOnlyList<TItem>? _items;
private HashSet<object> _expandedKeys = new(); private HashSet<string> _expandedKeys = new();
/// <summary>Normalize any key object to a string for consistent comparison with sessionStorage.</summary>
private string KeyStr(object key) => key.ToString()!;
private bool _initialExpansionApplied; private bool _initialExpansionApplied;
private bool _storageLoaded; private bool _storageLoaded;
private TItem? _contextMenuItem; private TItem? _contextMenuItem;
@@ -124,7 +127,7 @@ else
var keys = System.Text.Json.JsonSerializer.Deserialize<List<string>>(json); var keys = System.Text.Json.JsonSerializer.Deserialize<List<string>>(json);
if (keys != null) if (keys != null)
{ {
_expandedKeys = new HashSet<object>(keys); _expandedKeys = new HashSet<string>(keys);
_initialExpansionApplied = true; _initialExpansionApplied = true;
} }
} }
@@ -159,7 +162,7 @@ else
{ {
if (InitiallyExpanded!(item)) if (InitiallyExpanded!(item))
{ {
_expandedKeys.Add(KeySelector(item)); _expandedKeys.Add(KeyStr(KeySelector(item)));
} }
var children = ChildrenSelector(item); var children = ChildrenSelector(item);
@@ -172,9 +175,10 @@ else
private void ToggleExpand(object key) private void ToggleExpand(object key)
{ {
if (!_expandedKeys.Remove(key)) var k = KeyStr(key);
if (!_expandedKeys.Remove(k))
{ {
_expandedKeys.Add(key); _expandedKeys.Add(k);
} }
PersistExpandedState(); PersistExpandedState();
@@ -184,8 +188,7 @@ else
{ {
if (StorageKey != null) if (StorageKey != null)
{ {
var keys = _expandedKeys.Select(k => k.ToString()!).ToList(); var json = System.Text.Json.JsonSerializer.Serialize(_expandedKeys.ToList());
var json = System.Text.Json.JsonSerializer.Serialize(keys);
_ = JSRuntime.InvokeVoidAsync("treeviewStorage.save", StorageKey, json); _ = JSRuntime.InvokeVoidAsync("treeviewStorage.save", StorageKey, json);
} }
} }
@@ -232,7 +235,7 @@ else
{ {
if (HasChildrenSelector(item)) if (HasChildrenSelector(item))
{ {
_expandedKeys.Add(KeySelector(item)); _expandedKeys.Add(KeyStr(KeySelector(item)));
} }
var children = ChildrenSelector(item); var children = ChildrenSelector(item);
@@ -258,13 +261,14 @@ else
public async Task RevealNode(object key, bool select = false) public async Task RevealNode(object key, bool select = false)
{ {
var parentLookup = BuildParentLookup(); var parentLookup = BuildParentLookup();
var k = KeyStr(key);
// If key is not in the tree at all, no-op // If key is not in the tree at all, no-op
if (!parentLookup.ContainsKey(key)) if (!parentLookup.ContainsKey(k))
return; return;
// Walk up through ancestors // Walk up through ancestors
var current = key; var current = k;
while (parentLookup.TryGetValue(current, out var parentKey) && parentKey != null) while (parentLookup.TryGetValue(current, out var parentKey) && parentKey != null)
{ {
_expandedKeys.Add(parentKey); _expandedKeys.Add(parentKey);
@@ -280,9 +284,9 @@ else
StateHasChanged(); StateHasChanged();
} }
private Dictionary<object, object?> BuildParentLookup() private Dictionary<string, string?> BuildParentLookup()
{ {
var lookup = new Dictionary<object, object?>(); var lookup = new Dictionary<string, string?>();
if (_items is { Count: > 0 }) if (_items is { Count: > 0 })
{ {
BuildParentLookupRecursive(_items, null, lookup); BuildParentLookupRecursive(_items, null, lookup);
@@ -290,11 +294,11 @@ else
return lookup; return lookup;
} }
private void BuildParentLookupRecursive(IReadOnlyList<TItem> items, object? parentKey, Dictionary<object, object?> lookup) private void BuildParentLookupRecursive(IReadOnlyList<TItem> items, string? parentKey, Dictionary<string, string?> lookup)
{ {
foreach (var item in items) foreach (var item in items)
{ {
var key = KeySelector(item); var key = KeyStr(KeySelector(item));
lookup[key] = parentKey; lookup[key] = parentKey;
var children = ChildrenSelector(item); var children = ChildrenSelector(item);