@using System.Text.Json @using LANCommander.SDK.Models @if (_rows.Count == 0) {

No items.

} @for (int i = 0; i < _rows.Count; i++) { var index = i; var row = _rows[index];
@if (Definition.IsCompositeList) { @foreach (var field in Definition.Fields) { var fieldKey = field.Key; var fieldDef = field.Value; var fieldLabel = !string.IsNullOrWhiteSpace(fieldDef.DisplayName) ? fieldDef.DisplayName : fieldKey; var fieldValue = row.TryGetValue(fieldKey, out var v) ? v : "";
@fieldLabel
}
} else { }
@if (Definition.MinItems.HasValue || Definition.MaxItems.HasValue) { @(Definition.MinItems.HasValue ? $"min {Definition.MinItems.Value}" : "")@(Definition.MinItems.HasValue && Definition.MaxItems.HasValue ? ", " : "")@(Definition.MaxItems.HasValue ? $"max {Definition.MaxItems.Value}" : "") } @code { const string ValueKey = "__value"; [Parameter] public OptionDefinition Definition { get; set; } [Parameter] public string Value { get; set; } [Parameter] public EventCallback ValueChanged { get; set; } List> _rows = new(); OptionDefinition _scalarItemDefinition; string _lastSyncedValue; protected override void OnParametersSet() { _scalarItemDefinition = new OptionDefinition { Type = string.IsNullOrWhiteSpace(Definition.ItemType) ? "string" : Definition.ItemType }; // Only reparse Value when it changed externally (not as the result of our own emit). if (!ReferenceEquals(Value, _lastSyncedValue) && Value != _lastSyncedValue) { _rows = ParseValue(Value); _lastSyncedValue = Value; } } List> ParseValue(string json) { var result = new List>(); if (string.IsNullOrWhiteSpace(json)) return result; JsonElement root; try { using var doc = JsonDocument.Parse(json); root = doc.RootElement.Clone(); } catch { return result; } if (root.ValueKind != JsonValueKind.Array) return result; foreach (var item in root.EnumerateArray()) { if (Definition.IsCompositeList) { var dict = new Dictionary(); if (item.ValueKind == JsonValueKind.Object) { foreach (var field in Definition.Fields) { if (item.TryGetProperty(field.Key, out var fieldEl)) dict[field.Key] = ReadScalar(fieldEl); } } result.Add(dict); } else { result.Add(new Dictionary { [ValueKey] = ReadScalar(item) }); } } return result; } static string ReadScalar(JsonElement element) => element.ValueKind switch { JsonValueKind.String => element.GetString() ?? "", JsonValueKind.Null or JsonValueKind.Undefined => "", _ => element.GetRawText() }; async Task OnFieldChanged(int index, string fieldKey, string value) { if (index < 0 || index >= _rows.Count) return; _rows[index][fieldKey] = value; await EmitChange(); } async Task AddRow() { if (Definition.MaxItems.HasValue && _rows.Count >= Definition.MaxItems.Value) return; var row = new Dictionary(); if (Definition.IsCompositeList) { foreach (var field in Definition.Fields) { var fieldDefault = field.Value?.GetDefaultAsString(); if (!string.IsNullOrWhiteSpace(fieldDefault)) row[field.Key] = fieldDefault; } } else { row[ValueKey] = ""; } _rows.Add(row); await EmitChange(); } async Task RemoveRow(int index) { if (index < 0 || index >= _rows.Count) return; if (_rows.Count <= (Definition.MinItems ?? 0)) return; _rows.RemoveAt(index); await EmitChange(); } async Task EmitChange() { var json = Serialize(); _lastSyncedValue = json; if (ValueChanged.HasDelegate) await ValueChanged.InvokeAsync(json); } string Serialize() { if (Definition.IsCompositeList) { var items = new List>(); foreach (var row in _rows) { var entry = new Dictionary(); foreach (var field in Definition.Fields) { entry[field.Key] = row.TryGetValue(field.Key, out var v) ? v : ""; } items.Add(entry); } return JsonSerializer.Serialize(items); } else { var items = new List(); foreach (var row in _rows) items.Add(row.TryGetValue(ValueKey, out var v) ? v : ""); return JsonSerializer.Serialize(items); } } }