using Microsoft.AspNetCore.Components;
namespace ScadaLink.CentralUI.Components.Shared;
///
/// A compact multi-select control: a Bootstrap dropdown whose toggle button
/// summarises the current selection ("All" when empty, the single item's label
/// when one is picked, or "N selected" otherwise) over a checkbox menu.
///
///
/// It exists to keep multi-value filter controls one row tall instead of a
/// wrapped block of chip buttons. The component mutates the caller-owned
/// collection in place and raises
/// after every toggle so the parent can react
/// (re-render, prune dependent selections, …).
///
///
///
/// Requires the Bootstrap JS bundle (loaded in App.razor) for the
/// dropdown toggle; data-bs-auto-close="outside" keeps the menu open
/// while the operator ticks several boxes.
///
///
/// The option value type (an enum or string).
public partial class MultiSelectDropdown where TValue : notnull
{
/// The options shown in the menu, in display order.
[Parameter, EditorRequired]
public IReadOnlyList Items { get; set; } = Array.Empty();
///
/// The caller-owned selection set. Mutated in place by .
///
[Parameter, EditorRequired]
public ICollection Selected { get; set; } = default!;
/// Maps an option to its display label. Defaults to ToString().
[Parameter]
public Func Display { get; set; } = static v => v.ToString() ?? string.Empty;
/// Raised after each toggle, once has been updated.
[Parameter]
public EventCallback SelectionChanged { get; set; }
/// Summary text shown on the toggle button when nothing is selected.
[Parameter]
public string AllLabel { get; set; } = "All";
/// Text shown in the menu when there are no options.
[Parameter]
public string EmptyText { get; set; } = "None available";
/// data-test root for this control, its toggle and its options.
[Parameter]
public string DataTest { get; set; } = "multi-select";
private async Task Toggle(TValue item)
{
// ICollection.Remove returns false when the item was absent — that is the
// "not currently selected" case, so add it. This is a plain toggle.
if (!Selected.Remove(item))
{
Selected.Add(item);
}
await SelectionChanged.InvokeAsync();
}
private string Summary()
{
var count = Selected.Count;
if (count == 0)
{
return AllLabel;
}
if (count == 1)
{
// Prefer the single selection's label over a bare "1 selected".
foreach (var item in Items)
{
if (Selected.Contains(item))
{
return Display(item);
}
}
// The one selected value is not in the current Items list (e.g. a Kind
// narrowed out by a Channel change before the parent pruned it).
return "1 selected";
}
return $"{count} selected";
}
}