EffectiveConfig: migrate edit activation/commit to shared UiContext

- OptionStateBadge: add FieldKey param, call Ui.ActivateFieldAsync instead of OnActivateEdit
- EffectiveConfigOptionItem: add FieldKey, pass to badge; remove OnActivateEdit
- EffectiveConfigFieldDisplay: add FieldKey, commit only via Ui.CommitFieldAsync; remove ActiveFieldKey, OnActivateEdit, OnEditCommitted
- EffectiveConfigSectionPanel: pass FieldKey to FieldDisplay; remove OnActivateField, OnFieldEditCommitted
- EffectiveConfigPanels: remove OnActivateField, OnFieldEditCommitted params and relay
- EffectiveConfigSection: stop passing callback props to Panels (Session + UiContext drive behavior)
This commit is contained in:
2026-02-28 19:25:25 +10:00
parent 91beb9d181
commit bf1f6a0d82
6 changed files with 21 additions and 27 deletions
@@ -34,7 +34,7 @@
@fragment(Descriptor) @fragment(Descriptor)
</CascadingValue> </CascadingValue>
} }
<OptionStateBadge AnchorId="@_readonlyBadgeAnchorId" Source="@Descriptor.GetSource()" OptionName="@Descriptor.OptionName" Value="@effectiveValue" IsInteractive="@IsEditMode" ShowEditableBadge="@showEditableBadge" HasPendingChange="@_hasPendingChange" EditableAsButton="true" OnActivateEdit="@OnActivateEdit" /> <OptionStateBadge AnchorId="@_readonlyBadgeAnchorId" Source="@Descriptor.GetSource()" OptionName="@Descriptor.OptionName" Value="@effectiveValue" IsInteractive="@IsEditMode" ShowEditableBadge="@showEditableBadge" HasPendingChange="@_hasPendingChange" EditableAsButton="true" FieldKey="@FieldKey" />
</span> </span>
</li> </li>
} }
@@ -54,14 +54,14 @@ else
{ {
<li class="ec-field" title="@(Descriptor.GetSource()?.GetReadOnlyTooltip())"> <li class="ec-field" title="@(Descriptor.GetSource()?.GetReadOnlyTooltip())">
<EffectiveConfigOptionItem Label="@Descriptor.OptionName" DisplayValue="@(displayValue ?? "(not set)")" Source="@Descriptor.GetSource()" LabelTooltip="@_labelTooltip" OptionHelpKey="@_optionHelpKey" OptionHelpAnchorId="@_anchorId" <EffectiveConfigOptionItem Label="@Descriptor.OptionName" DisplayValue="@(displayValue ?? "(not set)")" Source="@Descriptor.GetSource()" LabelTooltip="@_labelTooltip" OptionHelpKey="@_optionHelpKey" OptionHelpAnchorId="@_anchorId"
ReadonlyBadgeAnchorId="@_readonlyBadgeAnchorId" RawValue="@value" HasPendingChange="@_hasPendingChange" /> FieldKey="@FieldKey" ReadonlyBadgeAnchorId="@_readonlyBadgeAnchorId" RawValue="@value" HasPendingChange="@_hasPendingChange" />
</li> </li>
} }
else else
{ {
<li class="ec-field @(isActiveEditor ? "ec-field-editing" : "")" title="@(Descriptor.GetSource()?.GetReadOnlyTooltip())"> <li class="ec-field @(isActiveEditor ? "ec-field-editing" : "")" title="@(Descriptor.GetSource()?.GetReadOnlyTooltip())">
<EffectiveConfigOptionItem Label="@Descriptor.OptionName" DisplayValue="@(displayValue ?? "(not set)")" Source="@Descriptor.GetSource()" LabelTooltip="@_labelTooltip" OptionHelpKey="@_optionHelpKey" OptionHelpAnchorId="@_anchorId" <EffectiveConfigOptionItem Label="@Descriptor.OptionName" DisplayValue="@(displayValue ?? "(not set)")" Source="@Descriptor.GetSource()" LabelTooltip="@_labelTooltip" OptionHelpKey="@_optionHelpKey" OptionHelpAnchorId="@_anchorId"
ShowEditableBadge="true" OnActivateEdit="@OnActivateEdit" IsActive="@IsActive" ShowEditableBadge="true" FieldKey="@FieldKey" IsActive="@IsActive"
ReadonlyBadgeAnchorId="@_readonlyBadgeAnchorId" RawValue="@value" HasPendingChange="@_hasPendingChange" ReadonlyBadgeAnchorId="@_readonlyBadgeAnchorId" RawValue="@value" HasPendingChange="@_hasPendingChange"
DisplayValueChanged="@HandleDefaultValueChanged" OnBlur="@HandleDefaultBlur" /> DisplayValueChanged="@HandleDefaultValueChanged" OnBlur="@HandleDefaultBlur" />
</li> </li>
@@ -70,11 +70,9 @@ else
@code { @code {
[Parameter] public EffectiveConfigFieldDescriptor Descriptor { get; set; } = null!; [Parameter] public EffectiveConfigFieldDescriptor Descriptor { get; set; } = null!;
[Parameter] public string FieldKey { get; set; } = "";
[Parameter] public bool IsEditMode { get; set; } [Parameter] public bool IsEditMode { get; set; }
[Parameter] public bool IsActive { get; set; } [Parameter] public bool IsActive { get; set; }
[Parameter] public string? ActiveFieldKey { get; set; }
[Parameter] public EventCallback OnActivateEdit { get; set; }
[Parameter] public EventCallback<EffectiveConfigEditCommittedArgs> OnEditCommitted { get; set; }
[CascadingParameter] public IEffectiveConfigEditSession? Session { get; set; } [CascadingParameter] public IEffectiveConfigEditSession? Session { get; set; }
private readonly string _anchorId = "ec-help-" + Guid.NewGuid().ToString("N")[..8]; private readonly string _anchorId = "ec-help-" + Guid.NewGuid().ToString("N")[..8];
@@ -127,8 +125,6 @@ else
var args = new EffectiveConfigEditCommittedArgs(Descriptor.SectionId, Descriptor.OptionName, _valueAtActivation, newValue, source?.FilePath); var args = new EffectiveConfigEditCommittedArgs(Descriptor.SectionId, Descriptor.OptionName, _valueAtActivation, newValue, source?.FilePath);
if (Ui != null) if (Ui != null)
await Ui.CommitFieldAsync(args); await Ui.CommitFieldAsync(args);
else if (OnEditCommitted.HasDelegate)
await OnEditCommitted.InvokeAsync(args);
} }
_draftValue = null; _draftValue = null;
} }
@@ -7,14 +7,14 @@
{ {
<span class="ec-field-value" @onclick:stopPropagation="true"> <span class="ec-field-value" @onclick:stopPropagation="true">
<input type="text" class="form-control form-control-sm" value="@(_editValue ?? DisplayValue)" @oninput="OnValueInput" @onfocusout="OnInputBlur" /> <input type="text" class="form-control form-control-sm" value="@(_editValue ?? DisplayValue)" @oninput="OnValueInput" @onfocusout="OnInputBlur" />
<OptionStateBadge AnchorId="@(ReadonlyBadgeAnchorId ?? "")" Source="@Source" OptionName="@Label" Value="@(RawValue ?? DisplayValue)" IsInteractive="@(DisplayMode == EffectiveConfigDisplayMode.Edit && !string.IsNullOrEmpty(ReadonlyBadgeAnchorId))" ShowEditableBadge="@ShowEditableBadge" HasPendingChange="@HasPendingChange" EditableAsButton="true" OnActivateEdit="@OnActivateEdit" /> <OptionStateBadge AnchorId="@(ReadonlyBadgeAnchorId ?? "")" Source="@Source" OptionName="@Label" Value="@(RawValue ?? DisplayValue)" IsInteractive="@(DisplayMode == EffectiveConfigDisplayMode.Edit && !string.IsNullOrEmpty(ReadonlyBadgeAnchorId))" ShowEditableBadge="@ShowEditableBadge" HasPendingChange="@HasPendingChange" EditableAsButton="true" FieldKey="@FieldKey" />
</span> </span>
} }
else else
{ {
<span class="ec-field-value"> <span class="ec-field-value">
@DisplayValue @DisplayValue
<OptionStateBadge AnchorId="@(ReadonlyBadgeAnchorId ?? "")" Source="@Source" OptionName="@Label" Value="@(RawValue ?? DisplayValue)" IsInteractive="@(DisplayMode == EffectiveConfigDisplayMode.Edit && !string.IsNullOrEmpty(ReadonlyBadgeAnchorId))" ShowEditableBadge="@ShowEditableBadge" HasPendingChange="@HasPendingChange" EditableAsButton="true" OnActivateEdit="@OnActivateEdit" /> <OptionStateBadge AnchorId="@(ReadonlyBadgeAnchorId ?? "")" Source="@Source" OptionName="@Label" Value="@(RawValue ?? DisplayValue)" IsInteractive="@(DisplayMode == EffectiveConfigDisplayMode.Edit && !string.IsNullOrEmpty(ReadonlyBadgeAnchorId))" ShowEditableBadge="@ShowEditableBadge" HasPendingChange="@HasPendingChange" EditableAsButton="true" FieldKey="@FieldKey" />
</span> </span>
} }
@@ -34,7 +34,8 @@ else
[Parameter] public bool ShowEditableBadge { get; set; } [Parameter] public bool ShowEditableBadge { get; set; }
/// <summary>When true, badge shows "pending" and warning style to indicate this field has an unsaved change.</summary> /// <summary>When true, badge shows "pending" and warning style to indicate this field has an unsaved change.</summary>
[Parameter] public bool HasPendingChange { get; set; } [Parameter] public bool HasPendingChange { get; set; }
[Parameter] public EventCallback OnActivateEdit { get; set; } /// <summary>Field key for activation via context when editable badge is clicked.</summary>
[Parameter] public string FieldKey { get; set; } = "";
/// <summary>When true, add ec-field-editing class and show input when DisplayMode is Edit.</summary> /// <summary>When true, add ec-field-editing class and show input when DisplayMode is Edit.</summary>
[Parameter] public bool IsActive { get; set; } [Parameter] public bool IsActive { get; set; }
/// <summary>When Edit and not readonly: raised when the user changes the value. Not yet wired to save.</summary> /// <summary>When Edit and not readonly: raised when the user changes the value. Not yet wired to save.</summary>
@@ -23,8 +23,7 @@
var isOpen = hasSearch || IsSectionOpen(view.SectionId); var isOpen = hasSearch || IsSectionOpen(view.SectionId);
<EffectiveConfigSectionPanel SectionId="@view.SectionId" Title="@view.Title" Descriptors="@sectionDescriptors" IsOpen="@isOpen" OnToggle="@OnSectionToggle" <EffectiveConfigSectionPanel SectionId="@view.SectionId" Title="@view.Title" Descriptors="@sectionDescriptors" IsOpen="@isOpen" OnToggle="@OnSectionToggle"
DisplayMode="@( IsEditMode ? EffectiveConfigDisplayMode.Edit : EffectiveConfigDisplayMode.View )" DisplayMode="@( IsEditMode ? EffectiveConfigDisplayMode.Edit : EffectiveConfigDisplayMode.View )"
IsEditMode="@IsEditMode" ActiveFieldKey="@ActiveFieldKey" IsEditMode="@IsEditMode" ActiveFieldKey="@ActiveFieldKey" />
OnActivateField="@OnActivateField" OnFieldEditCommitted="@OnFieldEditCommitted" />
} }
</div> </div>
@@ -36,8 +35,6 @@
[Parameter] public EventCallback<string> OnSectionToggle { get; set; } [Parameter] public EventCallback<string> OnSectionToggle { get; set; }
[Parameter] public bool IsEditMode { get; set; } [Parameter] public bool IsEditMode { get; set; }
[Parameter] public string? ActiveFieldKey { get; set; } [Parameter] public string? ActiveFieldKey { get; set; }
[Parameter] public EventCallback<string> OnActivateField { get; set; }
[Parameter] public EventCallback<EffectiveConfigEditCommittedArgs> OnFieldEditCommitted { get; set; }
private bool IsSectionOpen(string id) => OpenSectionIds.Contains(id); private bool IsSectionOpen(string id) => OpenSectionIds.Contains(id);
@@ -22,8 +22,7 @@
<CascadingValue Value="@Session"> <CascadingValue Value="@Session">
<CascadingValue Value="@_uiContext"> <CascadingValue Value="@_uiContext">
<EffectiveConfigPanels Status="@Status" Context="@Context" SearchTerm="@_searchTerm" OpenSectionIds="@_openPanels" <EffectiveConfigPanels Status="@Status" Context="@Context" SearchTerm="@_searchTerm" OpenSectionIds="@_openPanels"
OnSectionToggle="OnSectionToggle" IsEditMode="@Session.IsEditMode" ActiveFieldKey="@Session.ActiveFieldKey" OnSectionToggle="OnSectionToggle" IsEditMode="@Session.IsEditMode" ActiveFieldKey="@Session.ActiveFieldKey" />
OnActivateField="ActivateField" OnFieldEditCommitted="HandleFieldEditCommitted" />
</CascadingValue> </CascadingValue>
</CascadingValue> </CascadingValue>
<EffectiveConfigSaveFlow ShowModal="@_showSaveModal" PendingChanges="@Session.PendingChanges" Status="@Status" Session="@Session" <EffectiveConfigSaveFlow ShowModal="@_showSaveModal" PendingChanges="@Session.PendingChanges" Status="@Status" Session="@Session"
@@ -14,12 +14,8 @@
@foreach (var f in Descriptors) @foreach (var f in Descriptors)
{ {
var fieldKey = $"{SectionId}:{f.OptionName}"; var fieldKey = $"{SectionId}:{f.OptionName}";
<EffectiveConfigFieldDisplay Descriptor="@f" <EffectiveConfigFieldDisplay Descriptor="@f" FieldKey="@fieldKey"
IsEditMode="@IsEditMode" IsEditMode="@IsEditMode" IsActive="@(ActiveFieldKey == fieldKey)" />
IsActive="@(ActiveFieldKey == fieldKey)"
ActiveFieldKey="@ActiveFieldKey"
OnActivateEdit="@(() => OnActivateField.InvokeAsync(fieldKey))"
OnEditCommitted="@OnFieldEditCommitted" />
} }
</ul> </ul>
</div> </div>
@@ -37,8 +33,6 @@
[Parameter] public EffectiveConfigDisplayMode DisplayMode { get; set; } = EffectiveConfigDisplayMode.View; [Parameter] public EffectiveConfigDisplayMode DisplayMode { get; set; } = EffectiveConfigDisplayMode.View;
[Parameter] public bool IsEditMode { get; set; } [Parameter] public bool IsEditMode { get; set; }
[Parameter] public string? ActiveFieldKey { get; set; } [Parameter] public string? ActiveFieldKey { get; set; }
[Parameter] public EventCallback<string> OnActivateField { get; set; }
[Parameter] public EventCallback<EffectiveConfigEditCommittedArgs> OnFieldEditCommitted { get; set; }
private async Task ToggleSection() => await OnToggle.InvokeAsync(SectionId); private async Task ToggleSection() => await OnToggle.InvokeAsync(SectionId);
} }
@@ -22,7 +22,7 @@ else if (ShowEditableBadge)
{ {
<button type="button" <button type="button"
class="badge ec-badge-inline border-0 ms-1 @(HasPendingChange ? "bg-warning text-dark" : "bg-primary")" class="badge ec-badge-inline border-0 ms-1 @(HasPendingChange ? "bg-warning text-dark" : "bg-primary")"
@onclick="OnActivateEdit" @onclick="OnEditableClick"
@onclick:stopPropagation="true" @onclick:stopPropagation="true"
title="@(HasPendingChange ? "Pending change" : "Edit this field")" title="@(HasPendingChange ? "Pending change" : "Edit this field")"
aria-label="Edit"> aria-label="Edit">
@@ -48,9 +48,16 @@ else if (ShowEditableBadge)
[Parameter] public bool HasPendingChange { get; set; } [Parameter] public bool HasPendingChange { get; set; }
/// <summary>When true, editable badge is a button; otherwise a static span.</summary> /// <summary>When true, editable badge is a button; otherwise a static span.</summary>
[Parameter] public bool EditableAsButton { get; set; } = true; [Parameter] public bool EditableAsButton { get; set; } = true;
[Parameter] public EventCallback OnActivateEdit { get; set; } /// <summary>Field key for activation via context when editable badge is clicked.</summary>
[Parameter] public string? FieldKey { get; set; }
[CascadingParameter] public EffectiveConfigUiContext? Ui { get; set; } [CascadingParameter] public EffectiveConfigUiContext? Ui { get; set; }
private Task OnEditableClick()
{
if (Ui == null || string.IsNullOrEmpty(FieldKey)) return Task.CompletedTask;
return Ui.ActivateFieldAsync(FieldKey);
}
private Task OnReadonlyClick() private Task OnReadonlyClick()
{ {
if (Ui == null || Source == null) return Task.CompletedTask; if (Ui == null || Source == null) return Task.CompletedTask;