fix(ui): templates root-menu Escape + double-menu guard + reorder disabled tests (#258)

- Root context menu now has tabindex/focus + Escape-key close (OnRootMenuKeyDown) mirroring the node menu
- Opening root menu calls _tree.DismissNodeContextMenu(); opening node menu fires OnNodeContextMenuOpened → DismissRootContextMenu so only one menu is ever visible
- Add FolderContextMenu_MoveUp_IsDisabled_OnFirstSibling and FolderContextMenu_MoveDown_IsDisabled_OnLastSibling bUnit tests
This commit is contained in:
Joseph Doherty
2026-06-19 03:28:18 -04:00
parent 8f85cce298
commit 282bc8b53c
4 changed files with 79 additions and 6 deletions
@@ -21,7 +21,8 @@
SelectedKeys="SelectedKeys"
SelectedKeysChanged="SelectedKeysChanged"
InitiallyExpanded="@(_initiallyExpanded)"
StorageKey="@StorageKey">
StorageKey="@StorageKey"
OnNodeContextMenuOpened="OnNodeContextMenuOpened">
<NodeContent Context="node">
@if (NodeContent != null)
{
@@ -72,6 +73,8 @@
[Parameter] public RenderFragment? EmptyContent { get; set; }
[Parameter] public EventCallback<TemplateTreeNode> OnNodeClick { get; set; }
[Parameter] public string? StorageKey { get; set; }
/// <summary>Raised when a node context menu opens; forwarded from the inner TreeView.</summary>
[Parameter] public EventCallback OnNodeContextMenuOpened { get; set; }
/// <summary>
/// Optional: caller-supplied extra leaves to nest under a template. Used by
@@ -225,4 +228,10 @@
/// <summary>Forwarded to the inner TreeView so callers can drive expand/collapse.</summary>
public void CollapseAll() => _tree?.CollapseAll();
/// <summary>
/// Close any open node context menu. Called by the host page when it opens the
/// root context menu so both menus are never visible simultaneously.
/// </summary>
public void DismissNodeContextMenu() => _tree?.DismissContextMenu();
}
@@ -119,6 +119,8 @@ else
[Parameter, EditorRequired] public RenderFragment<TItem> NodeContent { get; set; } = default!;
[Parameter] public RenderFragment? EmptyContent { get; set; }
[Parameter] public RenderFragment<TItem>? ContextMenu { get; set; }
/// <summary>Raised when a node context menu opens, so the host page can dismiss any sibling menus.</summary>
[Parameter] public EventCallback OnNodeContextMenuOpened { get; set; }
[Parameter] public int IndentPx { get; set; } = 24;
[Parameter] public bool ShowGuideLines { get; set; } = true;
[Parameter] public Func<TItem, bool>? InitiallyExpanded { get; set; }
@@ -315,7 +317,7 @@ else
}
}
private void OnContextMenu(MouseEventArgs e, TItem item)
private async Task OnContextMenu(MouseEventArgs e, TItem item)
{
if (ContextMenu == null) return;
@@ -324,9 +326,13 @@ else
_contextMenuY = e.ClientY;
_showContextMenu = true;
_contextMenuNeedsFocus = true;
if (OnNodeContextMenuOpened.HasDelegate)
await OnNodeContextMenuOpened.InvokeAsync();
}
private void DismissContextMenu()
/// <summary>Programmatically close the node context menu (e.g. so a sibling menu can take over).</summary>
public void DismissContextMenu()
{
_showContextMenu = false;
_contextMenuItem = default;