Class
Sealed
public sealed class MokaJsonNode : ComponentBase
Namespace: Moka.Blazor.Json.Components
Renders a single row in the JSON tree view. Stateless — receives all data via parameters.
Inheritance
Inherits from: ComponentBase
Properties
| Name | Description |
|---|---|
EditState |
Active inline edit state from the parent viewer. |
IsSelected |
Whether this node is currently selected. |
Node |
The flattened node data to render. |
OnContextMenu |
Callback when the context menu is requested. |
OnDoubleClick |
Callback when the node is double-clicked (edit mode). |
OnEditCancel |
Callback when an inline edit is cancelled. |
OnEditCommit |
Callback when an inline edit is committed. |
OnSelect |
Callback when the node row is clicked (selection). |
OnToggle |
Callback when the expand/collapse toggle is clicked. |
ReadOnly |
Whether the viewer is in read-only mode. |
ShowChildCount |
Whether to show the child count (e.g. "13 items") on collapsed containers. |
ShowLineNumbers |
Whether to show line numbers in the gutter. |
ToggleSize |
Size of expand/collapse toggle indicators. |
ToggleStyle |
Style of expand/collapse toggle indicators. |
EditState
InlineEditState? MokaJsonNode.EditState { get; set; }
Active inline edit state from the parent viewer.
IsSelected
bool MokaJsonNode.IsSelected { get; set; }
Whether this node is currently selected.
Node
FlattenedJsonNode MokaJsonNode.Node { get; set; }
The flattened node data to render.
OnContextMenu
EventCallback<(string Path, double ClientX, double ClientY)> MokaJsonNode.OnContextMenu { get; set; }
Callback when the context menu is requested.
OnDoubleClick
EventCallback<string> MokaJsonNode.OnDoubleClick { get; set; }
Callback when the node is double-clicked (edit mode).
OnEditCancel
EventCallback MokaJsonNode.OnEditCancel { get; set; }
Callback when an inline edit is cancelled.
OnEditCommit
EventCallback<InlineEditResult> MokaJsonNode.OnEditCommit { get; set; }
Callback when an inline edit is committed.
OnSelect
EventCallback<string> MokaJsonNode.OnSelect { get; set; }
Callback when the node row is clicked (selection).
OnToggle
EventCallback<string> MokaJsonNode.OnToggle { get; set; }
Callback when the expand/collapse toggle is clicked.
ReadOnly
bool MokaJsonNode.ReadOnly { get; set; }
Whether the viewer is in read-only mode.
ShowChildCount
bool MokaJsonNode.ShowChildCount { get; set; }
Whether to show the child count (e.g. "13 items") on collapsed containers.
ShowLineNumbers
bool MokaJsonNode.ShowLineNumbers { get; set; }
Whether to show line numbers in the gutter.
ToggleSize
MokaJsonToggleSize MokaJsonNode.ToggleSize { get; set; }
Size of expand/collapse toggle indicators.
ToggleStyle
MokaJsonToggleStyle MokaJsonNode.ToggleStyle { get; set; }
Style of expand/collapse toggle indicators.
Methods
| Name | Description |
|---|---|
ShouldRender() override |
Type Relationships
classDiagram
style MokaJsonNode fill:#f9f,stroke:#333,stroke-width:2px
MokaJsonNode --|> ComponentBase : inherits
View Source
/// <summary>
/// Renders a single row in the JSON tree view. Stateless — receives all data via parameters.
/// </summary>
public sealed partial class MokaJsonNode : ComponentBase
{
#region Parameters
/// <summary>
/// The flattened node data to render.
/// </summary>
[Parameter]
public FlattenedJsonNode Node { get; set; }
/// <summary>
/// Whether this node is currently selected.
/// </summary>
[Parameter]
public bool IsSelected { get; set; }
/// <summary>
/// Callback when the expand/collapse toggle is clicked.
/// </summary>
[Parameter]
public EventCallback<string> OnToggle { get; set; }
/// <summary>
/// Callback when the node row is clicked (selection).
/// </summary>
[Parameter]
public EventCallback<string> OnSelect { get; set; }
/// <summary>
/// Callback when the context menu is requested.
/// </summary>
[Parameter]
public EventCallback<(string Path, double ClientX, double ClientY)> OnContextMenu { get; set; }
/// <summary>
/// Callback when the node is double-clicked (edit mode).
/// </summary>
[Parameter]
public EventCallback<string> OnDoubleClick { get; set; }
/// <summary>
/// Whether to show line numbers in the gutter.
/// </summary>
[Parameter]
public bool ShowLineNumbers { get; set; }
/// <summary>
/// Style of expand/collapse toggle indicators.
/// </summary>
[Parameter]
public MokaJsonToggleStyle ToggleStyle { get; set; }
/// <summary>
/// Size of expand/collapse toggle indicators.
/// </summary>
[Parameter]
public MokaJsonToggleSize ToggleSize { get; set; }
/// <summary>
/// Whether to show the child count (e.g. "13 items") on collapsed containers.
/// </summary>
[Parameter]
public bool ShowChildCount { get; set; } = true;
/// <summary>
/// Whether the viewer is in read-only mode.
/// </summary>
[Parameter]
public bool ReadOnly { get; set; }
/// <summary>
/// Active inline edit state from the parent viewer.
/// </summary>
[Parameter]
public InlineEditState? EditState { get; set; }
/// <summary>
/// Callback when an inline edit is committed.
/// </summary>
[Parameter]
public EventCallback<InlineEditResult> OnEditCommit { get; set; }
/// <summary>
/// Callback when an inline edit is cancelled.
/// </summary>
[Parameter]
public EventCallback OnEditCancel { get; set; }
#endregion
#region Computed Properties
private string ElementId => $"moka-node-{Node.Id}";
private string EditInputId => $"moka-edit-{Node.Id}";
private bool IsEditingValue => EditState is not null && EditState.Path == Node.Path && EditState.Target == InlineEditTarget.Value;
private bool IsEditingKey => EditState is not null && EditState.Path == Node.Path && EditState.Target == InlineEditTarget.Key;
// Precomputed CSS class strings indexed by 4 boolean flags (16 combinations).
// Flags: [0] IsSelected, [1] IsSearchMatch, [2] IsActiveSearchMatch, [3] IsEditable
private static readonly string[] CssClassLookup = BuildCssClassLookup();
private static string[] BuildCssClassLookup()
{
string[] table = new string[16];
for (int i = 0; i < 16; i++)
{
string css = "moka-json-node";
if ((i & 1) != 0)
{
css += " moka-json-node--selected";
}
if ((i & 2) != 0)
{
css += " moka-json-node--search-match";
}
if ((i & 4) != 0)
{
css += " moka-json-node--search-active";
}
if ((i & 8) != 0)
{
css += " moka-json-node--editable";
}
table[i] = css;
}
return table;
}
private string CssClass
{
get
{
int flags = 0;
if (IsSelected)
{
flags |= 1;
}
if (Node.IsSearchMatch)
{
flags |= 2;
}
if (Node.IsActiveSearchMatch)
{
flags |= 4;
}
if (!ReadOnly && !Node.IsClosingBracket)
{
flags |= 8;
}
return CssClassLookup[flags];
}
}
private string ValueCssClass => Node.ValueKind switch
{
JsonValueKind.String => "moka-json-value--string",
JsonValueKind.Number => "moka-json-value--number",
JsonValueKind.True or JsonValueKind.False => "moka-json-value--boolean",
JsonValueKind.Null => "moka-json-value--null",
_ => ""
};
private string GetCollapsedPreview()
{
if (Node.ChildCount == 0)
{
return Node.ValueKind == JsonValueKind.Object ? "" : "";
}
string itemWord = Node.ChildCount == 1 ? "item" : "items";
return $" {Node.ChildCount} {itemWord} ";
}
private string ToggleChar => (ToggleStyle, Node.IsExpanded) switch
{
(MokaJsonToggleStyle.Triangle, true) => "\u25BC", // ▼
(MokaJsonToggleStyle.Triangle, false) => "\u25B6", // ▶
(MokaJsonToggleStyle.Chevron, true) => "\u2304", // ⌄
(MokaJsonToggleStyle.Chevron, false) => "\u203A", // ›
(MokaJsonToggleStyle.PlusMinus, true) => "\u2212", // −
(MokaJsonToggleStyle.PlusMinus, false) => "+",
(MokaJsonToggleStyle.Arrow, true) => "\u25BD", // ▽
(MokaJsonToggleStyle.Arrow, false) => "\u25B7", // ▷
_ => Node.IsExpanded ? "\u25BC" : "\u25B6"
};
private string ToggleSizeCss => ToggleSize switch
{
MokaJsonToggleSize.ExtraSmall => "moka-json-toggle--xs",
MokaJsonToggleSize.Small => "moka-json-toggle--sm",
MokaJsonToggleSize.Medium => "moka-json-toggle--md",
MokaJsonToggleSize.Large => "moka-json-toggle--lg",
MokaJsonToggleSize.ExtraLarge => "moka-json-toggle--xl",
_ => "moka-json-toggle--sm"
};
#endregion
#region Event Handlers
private async Task HandleToggle(MouseEventArgs _)
{
if (!Node.IsClosingBracket && Node.ValueKind is JsonValueKind.Object or JsonValueKind.Array)
{
await OnToggle.InvokeAsync(Node.Path);
}
}
private async Task HandleClick(MouseEventArgs _)
{
if (!Node.IsClosingBracket)
{
await OnSelect.InvokeAsync(Node.Path);
}
}
private async Task HandleContextMenu(MouseEventArgs e)
{
if (!Node.IsClosingBracket)
{
await OnContextMenu.InvokeAsync((Node.Path, e.ClientX, e.ClientY));
}
}
private async Task HandleDoubleClick(MouseEventArgs _)
{
if (!Node.IsClosingBracket)
{
await OnDoubleClick.InvokeAsync(Node.Path);
}
}
private void HandleEditInput(ChangeEventArgs e)
{
if (EditState is not null)
{
EditState.CurrentValue = e.Value?.ToString() ?? "";
EditState.ValidationError = null;
}
}
private async Task HandleEditKeyDown(KeyboardEventArgs e)
{
if (e.Key == "Enter")
{
await OnEditCommit.InvokeAsync(new InlineEditResult(EditState?.CurrentValue ?? "", true));
}
else if (e.Key == "Escape")
{
await OnEditCancel.InvokeAsync();
}
}
private async Task HandleEditBlur()
{
// Commit on blur
if (EditState is not null)
{
await OnEditCommit.InvokeAsync(new InlineEditResult(EditState.CurrentValue, true));
}
}
private async Task HandleBoolSelect(ChangeEventArgs e)
{
string newValue = e.Value?.ToString() ?? "false";
await OnEditCommit.InvokeAsync(new InlineEditResult(newValue, true));
}
#endregion
#region Render Optimization
private FlattenedJsonNode _previousNode;
private bool _previousIsSelected;
private bool _previousShowLineNumbers;
private MokaJsonToggleStyle _previousToggleStyle;
private MokaJsonToggleSize _previousToggleSize;
private InlineEditState? _previousEditState;
/// <inheritdoc/>
protected override bool ShouldRender()
{
bool editStateChanged = EditState != _previousEditState;
if (Node != _previousNode || IsSelected != _previousIsSelected || ShowLineNumbers != _previousShowLineNumbers || ToggleStyle != _previousToggleStyle || ToggleSize != _previousToggleSize || editStateChanged)
{
_previousNode = Node;
_previousIsSelected = IsSelected;
_previousShowLineNumbers = ShowLineNumbers;
_previousToggleStyle = ToggleStyle;
_previousToggleSize = ToggleSize;
_previousEditState = EditState;
return true;
}
return false;
}
#endregion
}