public sealed class MokaJsonViewer : ComponentBase, IMokaJsonViewer, System.IAsyncDisposable
Namespace: Moka.Blazor.Json.Components
Inheritance
Inherits from: ComponentBase
Implements: IMokaJsonViewer, System.IAsyncDisposable
Properties
| Name | Description |
|---|---|
AdditionalAttributes |
Additional HTML attributes to apply to the root element. |
CollapseMode |
Initial collapse behavior when a document is loaded. Default is MokaJsonCollapseMode.Depth. |
ContextMenuActions |
Custom context menu actions to add. |
DebugStats |
Debug stats from the lazy source, set only when MOKA_DEBUG_LAZY env var is set. Used by Moka.Blazor.Json.Diagnostics overlay. |
Height |
Height of the viewer. Default is "400px". Use "100%" for fill. |
IsEditing |
Gets whether the viewer is currently in edit mode. |
Json |
The JSON string to display. Mutually exclusive with MokaJsonViewer.JsonStream. |
JsonChanged |
Two-way binding callback for MokaJsonViewer.Json. Enables @bind-Json. |
JsonStream |
A stream containing JSON data. Use this for large documents. |
MaxDepthExpanded |
Initial depth to expand. Default is 2. |
OnError |
Callback when an error occurs. |
OnJsonChanged |
Callback when the JSON content changes in edit mode. |
OnNodeSelected |
Callback when a node is selected. |
ReadOnly |
Whether the viewer is read-only. Default is true. |
SelectedPath |
Gets the currently selected node's JSON Pointer path, or null if nothing is selected. |
ShowBottomBar |
Whether the bottom status bar is displayed. Default is true. |
ShowBreadcrumb |
Whether the breadcrumb path is displayed. Default is true. |
ShowChildCount |
Whether to show child count (e.g. "13 items") on collapsed containers. Default is true. |
ShowLineNumbers |
Whether to show line numbers in the gutter. Default is true. |
ShowSettingsButton |
Whether the settings gear button is shown in the toolbar. When null, the global default from DI (MokaJsonViewerOptions.ShowSettingsButton) is used. |
ShowToolbar |
Whether the toolbar is displayed. Default is true. |
Theme |
Theme mode for the viewer. |
ToggleSize |
Size of expand/collapse toggle indicators. Default is MokaJsonToggleSize.Small. |
ToggleStyle |
Style of expand/collapse toggle indicators. Default is MokaJsonToggleStyle.Triangle. |
ToolbarExtra |
Extra content to render in the toolbar. |
ToolbarMode |
How toolbar buttons are displayed. Overrides MokaJsonViewerOptions.DefaultToolbarMode. When null, the global default from DI is used. |
WordWrap |
Whether long values wrap to the next line. Default is true. |
AdditionalAttributes
[Parameter]
public Dictionary<string, object>? AdditionalAttributes { get; set; }
Additional HTML attributes to apply to the root element.
CollapseMode
[Parameter]
public MokaJsonCollapseMode CollapseMode { get; set; }
Initial collapse behavior when a document is loaded. Default is MokaJsonCollapseMode.Depth.
ContextMenuActions
[Parameter]
public IReadOnlyList<MokaJsonContextAction>? ContextMenuActions { get; set; }
Custom context menu actions to add.
DebugStats
public LazyDebugStats? DebugStats { get; private set; }
Debug stats from the lazy source, set only when MOKA_DEBUG_LAZY env var is set. Used by Moka.Blazor.Json.Diagnostics overlay.
Height
[Parameter]
public string Height { get; set; }
Height of the viewer. Default is "400px". Use "100%" for fill.
IsEditing
public bool IsEditing { get; }
Gets whether the viewer is currently in edit mode.
Json
[Parameter]
public string? Json { get; set; }
The JSON string to display. Mutually exclusive with MokaJsonViewer.JsonStream.
JsonChanged
[Parameter]
public EventCallback<string?> JsonChanged { get; set; }
Two-way binding callback for MokaJsonViewer.Json. Enables @bind-Json.
JsonStream
[Parameter]
public Stream? JsonStream { get; set; }
A stream containing JSON data. Use this for large documents.
Remarks
MaxDepthExpanded
[Parameter]
public int MaxDepthExpanded { get; set; }
Initial depth to expand. Default is 2.
OnError
[Parameter]
public EventCallback<JsonErrorEventArgs> OnError { get; set; }
Callback when an error occurs.
OnJsonChanged
[Parameter]
public EventCallback<JsonChangeEventArgs> OnJsonChanged { get; set; }
Callback when the JSON content changes in edit mode.
OnNodeSelected
[Parameter]
public EventCallback<JsonNodeSelectedEventArgs> OnNodeSelected { get; set; }
Callback when a node is selected.
ReadOnly
[Parameter]
public bool ReadOnly { get; set; }
Whether the viewer is read-only. Default is true.
SelectedPath
public string? SelectedPath { get; private set; }
Gets the currently selected node's JSON Pointer path, or null if nothing is selected.
ShowBottomBar
[Parameter]
public bool ShowBottomBar { get; set; }
Whether the bottom status bar is displayed. Default is true.
ShowBreadcrumb
[Parameter]
public bool ShowBreadcrumb { get; set; }
Whether the breadcrumb path is displayed. Default is true.
ShowChildCount
[Parameter]
public bool ShowChildCount { get; set; }
Whether to show child count (e.g. "13 items") on collapsed containers. Default is true.
ShowLineNumbers
[Parameter]
public bool ShowLineNumbers { get; set; }
Whether to show line numbers in the gutter. Default is true.
ShowSettingsButton
[Parameter]
public bool? ShowSettingsButton { get; set; }
Whether the settings gear button is shown in the toolbar. When null, the global default from DI (MokaJsonViewerOptions.ShowSettingsButton) is used.
ShowToolbar
[Parameter]
public bool ShowToolbar { get; set; }
Whether the toolbar is displayed. Default is true.
Theme
[Parameter]
public MokaJsonTheme Theme { get; set; }
Theme mode for the viewer.
ToggleSize
[Parameter]
public MokaJsonToggleSize ToggleSize { get; set; }
Size of expand/collapse toggle indicators. Default is MokaJsonToggleSize.Small.
ToggleStyle
[Parameter]
public MokaJsonToggleStyle ToggleStyle { get; set; }
Style of expand/collapse toggle indicators. Default is MokaJsonToggleStyle.Triangle.
ToolbarExtra
[Parameter]
public RenderFragment? ToolbarExtra { get; set; }
Extra content to render in the toolbar.
ToolbarMode
[Parameter]
public MokaJsonToolbarMode? ToolbarMode { get; set; }
How toolbar buttons are displayed. Overrides MokaJsonViewerOptions.DefaultToolbarMode. When null, the global default from DI is used.
WordWrap
[Parameter]
public bool WordWrap { get; set; }
Whether long values wrap to the next line. Default is true.
Methods
| Name | Description |
|---|---|
ClearSearch() |
Clears the current search results and highlighting. |
CollapseAll() |
Collapses all nodes in the tree. |
DisposeAsync() |
|
ExpandAll() |
Starts expanding all nodes in the tree without waiting for it to finish. Errors are reported through OnError. Use IMokaJsonViewer.ExpandAllAsync to await it. |
ExpandAllAsync() |
Expands all nodes in the tree and completes once the tree has been refreshed. |
ExpandToDepth(int depth) |
Expands all nodes up to the specified depth. Pass -1 for unlimited. |
GetJson(bool indented) |
Gets the current JSON content as a formatted string. |
NavigateToAsync(string jsonPointer, CancellationToken cancellationToken) |
Navigates to and selects the node at the specified JSON Pointer path (RFC 6901). |
NextMatch() |
Starts navigating to the next search match without waiting for it to finish. Errors are reported through OnError. Use IMokaJsonViewer.NextMatchAsync to await it. |
NextMatchAsync() |
Navigates to the next search match, expanding and scrolling to it. |
OnAfterRenderAsync(bool firstRender) override |
|
OnInitialized() override |
|
OnParametersSetAsync() override |
|
PreviousMatch() |
Starts navigating to the previous search match without waiting for it to finish. Errors are reported through OnError. Use IMokaJsonViewer.PreviousMatchAsync to await it. |
PreviousMatchAsync() |
Navigates to the previous search match, expanding and scrolling to it. |
Redo() |
Starts redoing the last undone edit without waiting for it to finish. Only available when editing is enabled. Errors are reported through OnError. Use IMokaJsonViewer.RedoAsync to await it. |
RedoAsync() |
Redoes the last undone edit operation. Only available when editing is enabled. |
SearchAsync(…) |
Initiates a search with the given query. |
SetParametersAsync(ParameterView parameters) override |
|
ShouldRender() override |
|
Undo() |
Starts undoing the last edit without waiting for it to finish. Only available when editing is enabled. Errors are reported through OnError. Use IMokaJsonViewer.UndoAsync to await it. |
UndoAsync() |
Undoes the last edit operation. Only available when editing is enabled. |
ClearSearch()
public void ClearSearch()
Clears the current search results and highlighting.
CollapseAll()
public void CollapseAll()
Collapses all nodes in the tree.
ExpandAll()
public void ExpandAll()
Starts expanding all nodes in the tree without waiting for it to finish. Errors are reported through OnError. Use IMokaJsonViewer.ExpandAllAsync to await it.
ExpandAllAsync()
public ValueTask ExpandAllAsync()
Expands all nodes in the tree and completes once the tree has been refreshed.
ExpandToDepth(int depth)
public void ExpandToDepth(int depth)
Expands all nodes up to the specified depth. Pass -1 for unlimited.
Parameters
| Name | Type | Description |
|---|---|---|
depth | int | Maximum depth to expand, or -1 for all. |
GetJson(bool indented)
public string GetJson(bool indented = true)
Gets the current JSON content as a formatted string.
Parameters
| Name | Type | Description |
|---|---|---|
indented | bool | Whether to pretty-print the output. |
Returns: The JSON string.
NavigateToAsync(string jsonPointer, CancellationToken cancellationToken)
public ValueTask NavigateToAsync(string jsonPointer, CancellationToken cancellationToken = default)
Navigates to and selects the node at the specified JSON Pointer path (RFC 6901).
Parameters
| Name | Type | Description |
|---|---|---|
jsonPointer | string | The JSON Pointer path, e.g. "/users/0/name". |
cancellationToken | CancellationToken | Cancellation token. |
NextMatch()
public void NextMatch()
Starts navigating to the next search match without waiting for it to finish. Errors are reported through OnError. Use IMokaJsonViewer.NextMatchAsync to await it.
NextMatchAsync()
public ValueTask NextMatchAsync()
Navigates to the next search match, expanding and scrolling to it.
PreviousMatch()
public void PreviousMatch()
Starts navigating to the previous search match without waiting for it to finish. Errors are reported through OnError. Use IMokaJsonViewer.PreviousMatchAsync to await it.
PreviousMatchAsync()
public ValueTask PreviousMatchAsync()
Navigates to the previous search match, expanding and scrolling to it.
Redo()
public void Redo()
Starts redoing the last undone edit without waiting for it to finish. Only available when editing is enabled. Errors are reported through OnError. Use IMokaJsonViewer.RedoAsync to await it.
RedoAsync()
public ValueTask RedoAsync()
Redoes the last undone edit operation. Only available when editing is enabled.
SearchAsync(string query, JsonSearchOptions? options, CancellationToken cancellationToken)
public ValueTask<int> SearchAsync(string query, JsonSearchOptions? options = null, CancellationToken cancellationToken = default)
Initiates a search with the given query.
Parameters
| Name | Type | Description |
|---|---|---|
query | string | The search query string. |
options | JsonSearchOptions? | Search options (regex, case sensitivity, etc.). |
cancellationToken | CancellationToken | Cancellation token. |
Returns: The number of matches found.
Undo()
public void Undo()
Starts undoing the last edit without waiting for it to finish. Only available when editing is enabled. Errors are reported through OnError. Use IMokaJsonViewer.UndoAsync to await it.
UndoAsync()
public ValueTask UndoAsync()
Undoes the last edit operation. Only available when editing is enabled.
Type Relationships
classDiagram
style MokaJsonViewer fill:#f9f,stroke:#333,stroke-width:2px
MokaJsonViewer --|> ComponentBase : inherits
MokaJsonViewer ..|> IMokaJsonViewer : implements
MokaJsonViewer ..|> IAsyncDisposable : implements
View Source
/// <summary>
/// Top-level JSON viewer and editor component. The primary entry point for consumers.
/// </summary>
public sealed partial class MokaJsonViewer : ComponentBase, IMokaJsonViewer, IAsyncDisposable
{
#region IAsyncDisposable
/// <inheritdoc/>
public async ValueTask DisposeAsync()
{
if (_disposed)
{
return;
}
_disposed = true;
_loadVersion++;
_searchDebounceTimer?.Dispose();
_toastTimer?.Dispose();
if (_loadCts is not null)
{
await _loadCts.CancelAsync();
_loadCts.Dispose();
_loadCts = null;
}
if (_searchCts is not null)
{
await _searchCts.CancelAsync();
_searchCts.Dispose();
_searchCts = null;
}
if (_treeCts is not null)
{
await _treeCts.CancelAsync();
_treeCts.Dispose();
}
if (_statsCts is not null)
{
await _statsCts.CancelAsync();
_statsCts.Dispose();
}
if (_documentSource is not null)
{
await _documentSource.DisposeAsync();
}
else if (_documentManager is not null)
{
await _documentManager.DisposeAsync();
}
if (_contextMenu is not null)
{
await _contextMenu.DisposeAsync();
}
if (OptionsAccessor.Value.AggressiveCleanup)
{
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
}
#endregion
#region Injected Services
#endregion
#region Parameters
/// <summary>
/// The JSON string to display. Mutually exclusive with <see cref = "JsonStream"/>.
/// </summary>
[Parameter]
public string? Json { get; set; }
/// <summary>
/// A stream containing JSON data. Use this for large documents.
/// </summary>
/// <remarks>
/// A seekable stream (such as a <see cref = "FileStream"/>) above
/// <see cref = "MokaJsonViewerOptions.LazyParsingThresholdBytes"/> is read on demand, so it can be far larger
/// than memory. Keep it open and don't read from it while the viewer shows it. Other streams, such as
/// browser uploads, are copied to memory or a temp file first.
/// </remarks>
[Parameter]
public Stream? JsonStream { get; set; }
/// <summary>
/// Theme mode for the viewer.
/// </summary>
[Parameter]
public MokaJsonTheme Theme { get; set; } = MokaJsonTheme.Auto;
/// <summary>
/// Whether the toolbar is displayed. Default is <c>true</c>.
/// </summary>
[Parameter]
public bool ShowToolbar { get; set; } = true;
/// <summary>
/// Whether the bottom status bar is displayed. Default is <c>true</c>.
/// </summary>
[Parameter]
public bool ShowBottomBar { get; set; } = true;
/// <summary>
/// Whether the breadcrumb path is displayed. Default is <c>true</c>.
/// </summary>
[Parameter]
public bool ShowBreadcrumb { get; set; } = true;
/// <summary>
/// Whether to show line numbers in the gutter. Default is <c>true</c>.
/// </summary>
[Parameter]
public bool ShowLineNumbers { get; set; }
/// <summary>
/// Whether the viewer is read-only. Default is <c>true</c>.
/// </summary>
[Parameter]
public bool ReadOnly { get; set; } = true;
/// <summary>
/// Initial depth to expand. Default is <c>2</c>.
/// </summary>
[Parameter]
public int MaxDepthExpanded { get; set; } = 2;
/// <summary>
/// Height of the viewer. Default is "400px". Use "100%" for fill.
/// </summary>
[Parameter]
public string Height { get; set; } = "400px";
/// <summary>
/// Callback when a node is selected.
/// </summary>
[Parameter]
public EventCallback<JsonNodeSelectedEventArgs> OnNodeSelected { get; set; }
/// <summary>
/// Callback when the JSON content changes in edit mode.
/// </summary>
[Parameter]
public EventCallback<JsonChangeEventArgs> OnJsonChanged { get; set; }
/// <summary>
/// Two-way binding callback for <see cref = "Json"/>. Enables <c>@bind-Json</c>.
/// </summary>
[Parameter]
public EventCallback<string?> JsonChanged { get; set; }
/// <summary>
/// Callback when an error occurs.
/// </summary>
[Parameter]
public EventCallback<JsonErrorEventArgs> OnError { get; set; }
/// <summary>
/// Custom context menu actions to add.
/// </summary>
[Parameter]
public IReadOnlyList<MokaJsonContextAction>? ContextMenuActions { get; set; }
/// <summary>
/// Style of expand/collapse toggle indicators. Default is <see cref = "MokaJsonToggleStyle.Triangle"/>.
/// </summary>
[Parameter]
public MokaJsonToggleStyle ToggleStyle { get; set; } = MokaJsonToggleStyle.Triangle;
/// <summary>
/// Size of expand/collapse toggle indicators. Default is <see cref = "MokaJsonToggleSize.Small"/>.
/// </summary>
[Parameter]
public MokaJsonToggleSize ToggleSize { get; set; } = MokaJsonToggleSize.Small;
/// <summary>
/// Initial collapse behavior when a document is loaded. Default is <see cref = "MokaJsonCollapseMode.Depth"/>.
/// </summary>
[Parameter]
public MokaJsonCollapseMode CollapseMode { get; set; } = MokaJsonCollapseMode.Depth;
/// <summary>
/// Whether long values wrap to the next line. Default is <c>true</c>.
/// </summary>
[Parameter]
public bool WordWrap { get; set; } = true;
/// <summary>
/// Whether to show child count (e.g. "13 items") on collapsed containers. Default is <c>true</c>.
/// </summary>
[Parameter]
public bool ShowChildCount { get; set; } = true;
/// <summary>
/// How toolbar buttons are displayed. Overrides <see cref = "MokaJsonViewerOptions.DefaultToolbarMode"/>.
/// When <c>null</c>, the global default from DI is used.
/// </summary>
[Parameter]
public MokaJsonToolbarMode? ToolbarMode { get; set; }
/// <summary>
/// Extra content to render in the toolbar.
/// </summary>
[Parameter]
public RenderFragment? ToolbarExtra { get; set; }
/// <summary>
/// Whether the settings gear button is shown in the toolbar.
/// When <c>null</c>, the global default from DI (<see cref = "MokaJsonViewerOptions.ShowSettingsButton"/>) is used.
/// </summary>
[Parameter]
public bool? ShowSettingsButton { get; set; }
/// <summary>
/// Additional HTML attributes to apply to the root element.
/// </summary>
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object>? AdditionalAttributes { get; set; }
#endregion
#region State Fields
/// <summary>
/// Debug stats from the lazy source, set only when MOKA_DEBUG_LAZY env var is set.
/// Used by Moka.Blazor.Json.Diagnostics overlay.
/// </summary>
public LazyDebugStats? DebugStats { get; private set; }
#endregion
#region Computed Properties
#endregion
#region Lifecycle
/// <inheritdoc/>
protected override void OnInitialized()
{
MokaJsonViewerOptions options = OptionsAccessor.Value;
if (!_parentSettings.ContainsKey(nameof(Theme)))
{
Theme = options.DefaultTheme;
}
if (!_parentSettings.ContainsKey(nameof(MaxDepthExpanded)))
{
MaxDepthExpanded = options.DefaultExpandDepth;
}
BuildContextActions();
}
/// <inheritdoc/>
protected override bool ShouldRender()
{
if (_skipNextRender)
{
_skipNextRender = false;
return false;
}
return true;
}
/// <inheritdoc/>
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && !_cssReady)
{
_cssReady = true;
StateHasChanged();
}
if (!_keyGuardsAttached)
{
_keyGuardsAttached = true;
await TryInteropAsync(() => Interop.AttachKeyGuardsAsync(_viewerRef));
}
if (_focusViewerAfterRender)
{
_focusViewerAfterRender = false;
await TryInteropAsync(() => _viewerRef.FocusAsync(preventScroll: true));
}
if (_pendingRevealPath is not null && _viewport is not null && _flatNodes is not null)
{
string path = _pendingRevealPath;
_pendingRevealPath = null;
int row = _flatNodes.FindIndex(n => !n.IsClosingBracket && n.Path == path);
if (row >= 0)
{
MokaJsonTreeViewport viewport = _viewport;
await TryInteropAsync(() => viewport.ScrollToRowAsync(row));
}
}
}
/// <inheritdoc/>
protected override async Task OnParametersSetAsync()
{
if (ContextMenuActions != _previousContextMenuActions)
{
_previousContextMenuActions = ContextMenuActions;
BuildContextActions();
}
if (Json != _previousJson || JsonStream != _previousStream)
{
_previousJson = Json;
_previousStream = JsonStream;
// The interactive render starts over with a new instance. Work done now would be thrown away, and it
// would hold up the page's first response. A forward-only stream can also only be read once.
if (IsPrerendering && (JsonStream is not null || Json?.Length > OptionsAccessor.Value.LazyParsingThresholdBytes))
{
_isLoading = true;
return;
}
await LoadJsonAsync();
}
}
#endregion
#region Toolbar Handlers
#endregion
#region Search Handlers
#endregion
#region Tree Interaction Handlers
#region Settings Panel
/// <inheritdoc/>
public override Task SetParametersAsync(ParameterView parameters)
{
if (_userSettings.Count == 0)
{
RecordParentSettings(parameters);
return base.SetParametersAsync(parameters);
}
var values = new Dictionary<string, object?>(parameters.ToDictionary());
foreach ((string name, object? userValue)in _userSettings.ToList())
{
if (!values.TryGetValue(name, out object? incoming))
{
continue;
}
if (_parentSettings.TryGetValue(name, out object? previous) && Equals(previous, incoming))
{
values[name] = userValue;
}
else
{
_userSettings.Remove(name);
}
}
RecordParentSettings(parameters);
return base.SetParametersAsync(ParameterView.FromDictionary(values));
}
#endregion
#endregion
#region Context Action Builders
#endregion
#region Private Helpers
#endregion
#region IMokaJsonViewer Implementation
/// <inheritdoc/>
public async ValueTask NavigateToAsync(string jsonPointer, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(jsonPointer);
await InvokeAsync(async () =>
{
if (!IsWithinScope(jsonPointer))
{
_scopedPath = null;
}
_treeFlattener.ExpandAncestors(jsonPointer);
await HandleSelect(jsonPointer);
await RefreshFlatNodesAsync(jsonPointer);
});
}
/// <inheritdoc/>
public void ExpandToDepth(int depth)
{
if (_documentSource is not { } source)
{
return;
}
if (_isLazyMode)
{
RunDetached(async () =>
{
await Task.Run(() => _treeFlattener.ExpandToDepth(source, depth, JsonExpandLimits.LargeDocument));
await InvokeAsync(() => RefreshFlatNodesAsync());
});
return;
}
_treeFlattener.ExpandToDepth(source, depth);
RefreshFlatNodes();
}
/// <inheritdoc/>
public void CollapseAll()
{
_treeFlattener.CollapseAll();
RefreshFlatNodes();
}
/// <inheritdoc/>
public void ExpandAll() => RunDetached(ExpandAllAsync);
/// <inheritdoc/>
public ValueTask ExpandAllAsync() => new(InvokeAsync(HandleExpandAll));
/// <inheritdoc/>
public async ValueTask<int> SearchAsync(string query, JsonSearchOptions? options = null, CancellationToken cancellationToken = default)
{
JsonSearchOptions searchOptions = options ?? new JsonSearchOptions();
int matchCount = 0;
await InvokeAsync(async () =>
{
_searchQuery = query;
_searchCaseSensitive = searchOptions.CaseSensitive;
_searchUseRegex = searchOptions.UseRegex;
_searchRequestId++;
_searchDebounceTimer?.Dispose();
_searchDebounceTimer = null;
CancelSearch();
matchCount = await RunSearchAsync(query, searchOptions, true, cancellationToken);
});
return matchCount;
}
/// <inheritdoc/>
public void NextMatch() => RunDetached(NextMatchAsync);
/// <inheritdoc/>
public ValueTask NextMatchAsync() => new(InvokeAsync(() => NavigateSearchAsync(true)));
/// <inheritdoc/>
public void PreviousMatch() => RunDetached(PreviousMatchAsync);
/// <inheritdoc/>
public ValueTask PreviousMatchAsync() => new(InvokeAsync(() => NavigateSearchAsync(false)));
/// <inheritdoc/>
public void ClearSearch()
{
_searchQuery = null;
ResetSearchState();
RefreshFlatNodes();
}
/// <inheritdoc/>
public void Undo() => RunDetached(UndoAsync);
/// <inheritdoc/>
public ValueTask UndoAsync() => new(InvokeAsync(UndoCoreAsync));
/// <inheritdoc/>
public void Redo() => RunDetached(RedoAsync);
/// <inheritdoc/>
public ValueTask RedoAsync() => new(InvokeAsync(RedoCoreAsync));
/// <inheritdoc/>
public string GetJson(bool indented = true) => _documentSource?.GetJsonString(indented) ?? string.Empty;
/// <inheritdoc/>
public string? SelectedPath { get; private set; }
/// <inheritdoc/>
public bool IsEditing => _activeEdit is not null;
#endregion
}