Skip to content

Commit 28f78f8

Browse files
committed
Fixed a few bugs and applied some optimizations to ValueSelect
1 parent 0a199f3 commit 28f78f8

File tree

5 files changed

+129
-92
lines changed

5 files changed

+129
-92
lines changed

src/EventLogExpert/Shared/Components/ValueSelect.razor

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44

55
<div class="dropdown-input" @onkeydown="HandleKeyDown" @onkeydown:preventDefault="_preventDefault" @ref="_selectComponent">
66
<input aria-activedescendant="@HighlightedItem?.ItemId" aria-controls="@_itemId"
7+
aria-expanded="false" aria-haspopup="listbox"
78
aria-label="@(string.IsNullOrWhiteSpace(AriaLabelledBy) ? AriaLabel : null)" aria-labelledby="@AriaLabelledBy"
89
class="@CssClass" @oninput="OnInputChange" readonly="@(!IsInput || IsMultiSelect)" role="combobox" tabindex="0" type="text" value="@DisplayString" />
910

10-
<div class="dropdown-list" id="@_itemId" role="listbox" tabindex="-1">
11+
<div class="dropdown-list" id="@_itemId" role="listbox" aria-multiselectable="@(IsMultiSelect ? "true" : null)" tabindex="-1">
1112
<CascadingValue Value="this">
1213
@ChildContent
1314
</CascadingValue>

src/EventLogExpert/Shared/Components/ValueSelect.razor.cs

Lines changed: 78 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
namespace EventLogExpert.Shared.Components;
1010

11-
public sealed partial class ValueSelect<T> : BaseComponent<T>
11+
public sealed partial class ValueSelect<T> : BaseComponent<T>, IAsyncDisposable
1212
{
1313
private readonly string _itemId = $"select_{Guid.NewGuid().ToString()[..8]}";
1414
private readonly List<ValueSelectItem<T>> _items = [];
@@ -30,6 +30,8 @@ public ValueSelectItem<T>? HighlightedItem
3030
get => _highlightedItem;
3131
set
3232
{
33+
if (ReferenceEquals(_highlightedItem, value)) { return; }
34+
3335
_highlightedItem = value;
3436
StateHasChanged();
3537
}
@@ -62,46 +64,64 @@ private string? DisplayString
6264

6365
[Inject] private IJSRuntime JSRuntime { get; init; } = null!;
6466

65-
public bool AddItem(ValueSelectItem<T> item)
67+
public void AddItem(ValueSelectItem<T> item)
6668
{
67-
if (_items.Contains(item))
69+
if (!_items.Contains(item))
6870
{
69-
return _selectedValues.Contains(item.Value);
71+
_items.Add(item);
7072
}
73+
}
7174

72-
_items.Add(item);
75+
public async Task ClearAll()
76+
{
77+
_selectedValues.Clear();
78+
_highlightedItem = null;
7379

74-
if (IsMultiSelect && Values.Contains(item.Value))
80+
if (IsMultiSelect)
7581
{
76-
_selectedValues.Add(item.Value);
77-
return true;
82+
Values.Clear();
83+
await ValuesChanged.InvokeAsync(Values);
7884
}
85+
else
86+
{
87+
Value = default!;
88+
await ValueChanged.InvokeAsync(Value);
89+
}
90+
}
7991

80-
if (Value?.Equals(item.Value) is not true) { return false; }
81-
82-
_selectedValues.Clear();
83-
_selectedValues.Add(item.Value);
92+
public async Task CloseDropDown() => await JSRuntime.InvokeVoidAsync("closeDropdown", _selectComponent);
8493

85-
return true;
94+
public async ValueTask DisposeAsync()
95+
{
96+
try
97+
{
98+
await JSRuntime.InvokeVoidAsync("unregisterDropdown", _selectComponent);
99+
}
100+
catch (JSDisconnectedException)
101+
{
102+
// Expected during app shutdown
103+
}
86104
}
87105

88-
public void ClearSelected() => _selectedValues.Clear();
89-
90-
public async Task CloseDropDown() => await JSRuntime.InvokeVoidAsync("closeDropdown", _selectComponent);
106+
public bool IsItemSelected(T value) => _selectedValues.Contains(value);
91107

92108
public async Task OpenDropDown() => await JSRuntime.InvokeVoidAsync("openDropdown", _selectComponent);
93109

94-
public void RemoveItem(ValueSelectItem<T> item) => _items.Remove(item);
110+
public void RemoveItem(ValueSelectItem<T> item)
111+
{
112+
_items.Remove(item);
113+
114+
if (ReferenceEquals(_highlightedItem, item))
115+
{
116+
HighlightedItem = null;
117+
}
118+
}
95119

96120
public async Task UpdateValue(T item)
97121
{
98122
if (IsMultiSelect)
99123
{
100-
if (item is null)
101-
{
102-
Values.Clear();
103-
}
104-
else if (_selectedValues.Remove(item))
124+
if (_selectedValues.Remove(item))
105125
{
106126
Values.Remove(item);
107127
}
@@ -137,8 +157,27 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
137157
await base.OnAfterRenderAsync(firstRender);
138158
}
139159

160+
protected override void OnParametersSet()
161+
{
162+
_selectedValues.Clear();
163+
164+
if (IsMultiSelect)
165+
{
166+
foreach (var v in Values)
167+
{
168+
_selectedValues.Add(v);
169+
}
170+
}
171+
else if (Value is not null)
172+
{
173+
_selectedValues.Add(Value);
174+
}
175+
}
176+
140177
private async Task HandleKeyDown(KeyboardEventArgs args)
141178
{
179+
_preventDefault = false;
180+
142181
switch (args.Code)
143182
{
144183
case "Space":
@@ -164,10 +203,12 @@ private async Task HandleKeyDown(KeyboardEventArgs args)
164203
{
165204
if (HighlightedItem.ClearItem)
166205
{
167-
ClearSelected();
206+
await ClearAll();
207+
}
208+
else
209+
{
210+
await UpdateValue(HighlightedItem.Value);
168211
}
169-
170-
await UpdateValue(HighlightedItem.Value);
171212
}
172213
else
173214
{
@@ -180,14 +221,15 @@ private async Task HandleKeyDown(KeyboardEventArgs args)
180221

181222
return;
182223
}
183-
184-
_preventDefault = false;
185224
}
186225

187226
private async Task OnInputChange(ChangeEventArgs args)
188227
{
189-
Value = (T)Convert.ChangeType(args.Value, typeof(T))!;
190-
await ValueChanged.InvokeAsync(Value);
228+
if (BindConverter.TryConvertTo<T>($"{args.Value}", null, out var result))
229+
{
230+
Value = result;
231+
await ValueChanged.InvokeAsync(Value);
232+
}
191233
}
192234

193235
private async Task SelectAdjacentItem(int direction)
@@ -196,33 +238,30 @@ private async Task SelectAdjacentItem(int direction)
196238

197239
if (IsMultiSelect || IsInput)
198240
{
199-
index = _items.FindIndex(x => x.Equals(_items.FirstOrDefault(item => item.Equals(HighlightedItem))));
241+
index = HighlightedItem is not null ? _items.IndexOf(HighlightedItem) : -1;
200242
}
201243
else
202244
{
203-
index = _items.FindIndex(x => x.Equals(
204-
_items.FirstOrDefault(item => item.Value?.Equals(_selectedValues.FirstOrDefault()) is true)));
245+
index = _items.FindIndex(item => item.Value?.Equals(_selectedValues.FirstOrDefault()) is true);
205246
}
206247

207-
// Need to account for first item being an empty placeholder
208-
if (index < 0) { index = 0; }
248+
if (index < 0)
249+
{
250+
index = direction > 0 ? -1 : _items.Count;
251+
}
209252

210253
for (int i = 0; i < _items.Count; i++)
211254
{
212255
index += direction;
213256

214-
if (index < 0) { index = 0; }
215-
216-
if (index >= _items.Count) { index = _items.Count - 1; }
257+
if (index < 0 || index >= _items.Count) { return; }
217258

218259
if (_items[index].IsDisabled) { continue; }
219260

220261
if (IsMultiSelect || IsInput)
221262
{
222263
HighlightedItem = _items[index];
223264

224-
StateHasChanged();
225-
226265
await JSRuntime.InvokeVoidAsync("scrollToHighlightedItem", _selectComponent);
227266
}
228267
else

src/EventLogExpert/Shared/Components/ValueSelectItem.razor

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
@typeparam T
22

3-
<div aria-selected="@_isSelected.ToString().ToLower()" class="@CssClass" highlighted="@(_parent.HighlightedItem?.Equals(this) ?? false)"
3+
<div aria-disabled="@(IsDisabled ? "true" : null)" aria-selected="@IsSelected.ToString().ToLower()" class="@CssClass"
4+
highlighted="@(IsHighlighted ? "" : null)"
45
id="@ItemId" @onmousedown="SelectItem" @onmouseenter="HighlightItem" role="option">
56

67
@if (ChildContent is not null)

src/EventLogExpert/Shared/Components/ValueSelectItem.razor.cs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ namespace EventLogExpert.Shared.Components;
88

99
public sealed partial class ValueSelectItem<T> : IDisposable
1010
{
11-
private bool _isSelected;
1211
private ValueSelect<T> _parent = null!;
1312

1413
[Parameter]
@@ -37,22 +36,26 @@ private string? DisplayString
3736
}
3837
}
3938

39+
private bool IsHighlighted => _parent.HighlightedItem?.Equals(this) ?? false;
40+
41+
private bool IsSelected => _parent.IsItemSelected(Value);
42+
4043
[CascadingParameter]
4144
private ValueSelect<T> ValueSelect
4245
{
4346
get => _parent;
4447
set
4548
{
4649
_parent = value;
47-
_isSelected = _parent.AddItem(this);
50+
_parent.AddItem(this);
4851
}
4952
}
5053

5154
public void Dispose() => ValueSelect.RemoveItem(this);
5255

5356
private void HighlightItem()
5457
{
55-
if (!_parent.IsMultiSelect) { return; }
58+
if (_parent is { IsMultiSelect: false, IsInput: false }) { return; }
5659

5760
_parent.HighlightedItem = this;
5861
}
@@ -61,10 +64,15 @@ private async Task SelectItem()
6164
{
6265
if (IsDisabled) { return; }
6366

64-
if (ClearItem) { ValueSelect.ClearSelected(); }
65-
6667
if (!ValueSelect.IsMultiSelect) { await ValueSelect.CloseDropDown(); }
6768

68-
await ValueSelect.UpdateValue(Value);
69+
if (ClearItem)
70+
{
71+
await ValueSelect.ClearAll();
72+
}
73+
else
74+
{
75+
await ValueSelect.UpdateValue(Value);
76+
}
6977
}
7078
}

0 commit comments

Comments
 (0)