{
  "name": "ContextMenu",
  "category": "Overlay",
  "subcategory": null,
  "description": "Right-click menu tied to a container element.",
  "thumbnail": "/preview-cards/context-menu.png",
  "hasDocsPage": true,
  "nugetPackage": "Lumeo",
  "files": [
    "UI/ContextMenu/ContextMenu.razor",
    "UI/ContextMenu/ContextMenuCheckboxItem.razor",
    "UI/ContextMenu/ContextMenuContent.razor",
    "UI/ContextMenu/ContextMenuGroup.razor",
    "UI/ContextMenu/ContextMenuItem.razor",
    "UI/ContextMenu/ContextMenuLabel.razor",
    "UI/ContextMenu/ContextMenuRadioGroup.razor",
    "UI/ContextMenu/ContextMenuRadioItem.razor",
    "UI/ContextMenu/ContextMenuSeparator.razor",
    "UI/ContextMenu/ContextMenuTrigger.razor"
  ],
  "dependencies": [],
  "packageDependencies": [
    "Blazicons.Lucide"
  ],
  "cssVars": [
    "--color-accent",
    "--color-accent-foreground",
    "--color-border",
    "--color-popover",
    "--color-popover-foreground",
    "--radius"
  ],
  "api": {
    "name": "ContextMenu",
    "category": "Overlay",
    "subcategory": null,
    "description": "Right-click menu tied to a container element.",
    "nugetPackage": "Lumeo",
    "files": [
      "UI/ContextMenu/ContextMenu.razor",
      "UI/ContextMenu/ContextMenuCheckboxItem.razor",
      "UI/ContextMenu/ContextMenuContent.razor",
      "UI/ContextMenu/ContextMenuGroup.razor",
      "UI/ContextMenu/ContextMenuItem.razor",
      "UI/ContextMenu/ContextMenuLabel.razor",
      "UI/ContextMenu/ContextMenuRadioGroup.razor",
      "UI/ContextMenu/ContextMenuRadioItem.razor",
      "UI/ContextMenu/ContextMenuSeparator.razor",
      "UI/ContextMenu/ContextMenuTrigger.razor"
    ],
    "namespace": "Lumeo",
    "inheritsFrom": null,
    "implements": [],
    "parameters": [
      {
        "name": "ChildContent",
        "type": "RenderFragment?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Open",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "OpenChanged",
        "type": "EventCallback<bool>",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "IsOpen",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "IsOpenChanged",
        "type": "EventCallback<bool>",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "AdditionalAttributes",
        "type": "Dictionary<string, object>?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": true
      }
    ],
    "events": [
      {
        "name": "OpenChanged",
        "type": "EventCallback<bool>",
        "description": null
      },
      {
        "name": "IsOpenChanged",
        "type": "EventCallback<bool>",
        "description": null
      }
    ],
    "enums": [],
    "records": [
      {
        "name": "ContextMenuContext",
        "signature": "ContextMenuContext(bool IsOpen, double X, double Y, EventCallback<bool> SetOpen, EventCallback<(double, double)> SetPosition)",
        "description": null
      }
    ],
    "cssVars": [
      "--color-accent",
      "--color-accent-foreground",
      "--color-border",
      "--color-popover",
      "--color-popover-foreground",
      "--radius"
    ],
    "examples": [
      {
        "title": "Default",
        "code": "<ContextMenu>\n    <ContextMenuTrigger>\n        <div class=\"flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed text-sm\">\n            Right click here\n        </div>\n    </ContextMenuTrigger>\n    <ContextMenuContent>\n        <ContextMenuItem>Back</ContextMenuItem>\n        <ContextMenuItem>Forward</ContextMenuItem>\n        <ContextMenuItem>Reload</ContextMenuItem>\n        <ContextMenuSeparator />\n        <ContextMenuItem>Save As...</ContextMenuItem>\n        <ContextMenuItem>Print</ContextMenuItem>\n    </ContextMenuContent>\n</ContextMenu>"
      },
      {
        "title": "With Labels and Groups",
        "code": "<ContextMenu>\n    <ContextMenuTrigger>\n        <div>Right click here</div>\n    </ContextMenuTrigger>\n    <ContextMenuContent>\n        <ContextMenuLabel>Edit</ContextMenuLabel>\n        <ContextMenuGroup>\n            <ContextMenuItem>Cut</ContextMenuItem>\n            <ContextMenuItem>Copy</ContextMenuItem>\n            <ContextMenuItem>Paste</ContextMenuItem>\n        </ContextMenuGroup>\n        <ContextMenuSeparator />\n        <ContextMenuLabel>View</ContextMenuLabel>\n        <ContextMenuGroup>\n            <ContextMenuItem>Zoom In</ContextMenuItem>\n            <ContextMenuItem>Zoom Out</ContextMenuItem>\n        </ContextMenuGroup>\n        <ContextMenuSeparator />\n        <ContextMenuItem Disabled=\"true\">Delete</ContextMenuItem>\n    </ContextMenuContent>\n</ContextMenu>"
      },
      {
        "title": "File Actions",
        "code": "<ContextMenu>\n    <ContextMenuTrigger>\n        <div class=\"flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed\">\n            <div class=\"text-center\">\n                <div class=\"text-sm font-medium\">document.pdf</div>\n                <div class=\"text-xs text-muted-foreground mt-1\">Right click for options</div>\n            </div>\n        </div>\n    </ContextMenuTrigger>\n    <ContextMenuContent>\n        <ContextMenuItem>Open</ContextMenuItem>\n        <ContextMenuItem>Duplicate</ContextMenuItem>\n        <ContextMenuSeparator />\n        <ContextMenuItem>Move to...</ContextMenuItem>\n        <ContextMenuSeparator />\n        <ContextMenuItem>Add to Favorites</ContextMenuItem>\n        <ContextMenuSeparator />\n        <ContextMenuItem>Delete</ContextMenuItem>\n    </ContextMenuContent>\n</ContextMenu>"
      }
    ],
    "subComponents": {
      "ContextMenuCheckboxItem": {
        "componentName": "ContextMenuCheckboxItem",
        "fileName": "ContextMenuCheckboxItem.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [],
        "parameters": [
          {
            "name": "ChildContent",
            "type": "RenderFragment?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "Checked",
            "type": "bool",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "CheckedChanged",
            "type": "EventCallback<bool>",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "Class",
            "type": "string?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "AdditionalAttributes",
            "type": "Dictionary<string, object>?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": true
          }
        ],
        "events": [
          {
            "name": "CheckedChanged",
            "type": "EventCallback<bool>",
            "description": null
          }
        ],
        "enums": [],
        "records": [],
        "parseFailed": false,
        "parseError": null
      },
      "ContextMenuContent": {
        "componentName": "ContextMenuContent",
        "fileName": "ContextMenuContent.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [
          "IAsyncDisposable"
        ],
        "parameters": [
          {
            "name": "Context",
            "type": "ContextMenu.ContextMenuContext",
            "default": "default!",
            "description": null,
            "isCascading": true,
            "captureUnmatched": false
          },
          {
            "name": "ChildContent",
            "type": "RenderFragment?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "Class",
            "type": "string?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "AdditionalAttributes",
            "type": "Dictionary<string, object>?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": true
          }
        ],
        "events": [],
        "enums": [],
        "records": [],
        "parseFailed": false,
        "parseError": null
      },
      "ContextMenuGroup": {
        "componentName": "ContextMenuGroup",
        "fileName": "ContextMenuGroup.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [],
        "parameters": [
          {
            "name": "ChildContent",
            "type": "RenderFragment?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "Class",
            "type": "string?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "AdditionalAttributes",
            "type": "Dictionary<string, object>?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": true
          }
        ],
        "events": [],
        "enums": [],
        "records": [],
        "parseFailed": false,
        "parseError": null
      },
      "ContextMenuItem": {
        "componentName": "ContextMenuItem",
        "fileName": "ContextMenuItem.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [],
        "parameters": [
          {
            "name": "Context",
            "type": "ContextMenu.ContextMenuContext?",
            "default": null,
            "description": null,
            "isCascading": true,
            "captureUnmatched": false
          },
          {
            "name": "ChildContent",
            "type": "RenderFragment?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "OnClick",
            "type": "EventCallback",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "Disabled",
            "type": "bool",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "Class",
            "type": "string?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "AdditionalAttributes",
            "type": "Dictionary<string, object>?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": true
          }
        ],
        "events": [
          {
            "name": "OnClick",
            "type": "EventCallback",
            "description": null
          }
        ],
        "enums": [],
        "records": [],
        "parseFailed": false,
        "parseError": null
      },
      "ContextMenuLabel": {
        "componentName": "ContextMenuLabel",
        "fileName": "ContextMenuLabel.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [],
        "parameters": [
          {
            "name": "ChildContent",
            "type": "RenderFragment?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "Class",
            "type": "string?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "AdditionalAttributes",
            "type": "Dictionary<string, object>?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": true
          }
        ],
        "events": [],
        "enums": [],
        "records": [],
        "parseFailed": false,
        "parseError": null
      },
      "ContextMenuRadioGroup": {
        "componentName": "ContextMenuRadioGroup",
        "fileName": "ContextMenuRadioGroup.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [],
        "parameters": [
          {
            "name": "ChildContent",
            "type": "RenderFragment?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "Value",
            "type": "string?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "ValueChanged",
            "type": "EventCallback<string>",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "AdditionalAttributes",
            "type": "Dictionary<string, object>?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": true
          }
        ],
        "events": [
          {
            "name": "ValueChanged",
            "type": "EventCallback<string>",
            "description": null
          }
        ],
        "enums": [],
        "records": [
          {
            "name": "ContextMenuRadioGroupContext",
            "signature": "ContextMenuRadioGroupContext(string? Value, EventCallback<string> OnSelect)",
            "description": null
          }
        ],
        "parseFailed": false,
        "parseError": null
      },
      "ContextMenuRadioItem": {
        "componentName": "ContextMenuRadioItem",
        "fileName": "ContextMenuRadioItem.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [],
        "parameters": [
          {
            "name": "Context",
            "type": "ContextMenuRadioGroup.ContextMenuRadioGroupContext",
            "default": "default!",
            "description": null,
            "isCascading": true,
            "captureUnmatched": false
          },
          {
            "name": "ChildContent",
            "type": "RenderFragment?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "Value",
            "type": "string",
            "default": "\"\"",
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "Class",
            "type": "string?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "AdditionalAttributes",
            "type": "Dictionary<string, object>?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": true
          }
        ],
        "events": [],
        "enums": [],
        "records": [],
        "parseFailed": false,
        "parseError": null
      },
      "ContextMenuSeparator": {
        "componentName": "ContextMenuSeparator",
        "fileName": "ContextMenuSeparator.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [],
        "parameters": [
          {
            "name": "Class",
            "type": "string?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "AdditionalAttributes",
            "type": "Dictionary<string, object>?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": true
          }
        ],
        "events": [],
        "enums": [],
        "records": [],
        "parseFailed": false,
        "parseError": null
      },
      "ContextMenuTrigger": {
        "componentName": "ContextMenuTrigger",
        "fileName": "ContextMenuTrigger.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [
          "IAsyncDisposable"
        ],
        "parameters": [
          {
            "name": "Context",
            "type": "ContextMenu.ContextMenuContext",
            "default": "default!",
            "description": null,
            "isCascading": true,
            "captureUnmatched": false
          },
          {
            "name": "ChildContent",
            "type": "RenderFragment?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "Class",
            "type": "string?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "AdditionalAttributes",
            "type": "Dictionary<string, object>?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": true
          },
          {
            "name": "LongPressMs",
            "type": "int",
            "default": "500",
            "description": "Duration in milliseconds a touch must remain on the trigger (without significant movement) before the context menu opens. Default: 500ms.",
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "MoveTolerancePx",
            "type": "double",
            "default": "10.0",
            "description": "Pixel threshold for touch movement that cancels the long-press timer. Above this, the user is interpreted as scrolling, not long-pressing.",
            "isCascading": false,
            "captureUnmatched": false
          }
        ],
        "events": [],
        "enums": [],
        "records": [],
        "parseFailed": false,
        "parseError": null
      }
    },
    "parseFailed": false,
    "parseError": null
  },
  "$schema": "https://lumeo.nativ.sh/registry-component-schema.json",
  "key": "context-menu",
  "docsUrl": "https://lumeo.nativ.sh/components/context-menu",
  "sourceUrl": [
    {
      "file": "UI/ContextMenu/ContextMenu.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/ContextMenu/ContextMenu.razor"
    },
    {
      "file": "UI/ContextMenu/ContextMenuCheckboxItem.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/ContextMenu/ContextMenuCheckboxItem.razor"
    },
    {
      "file": "UI/ContextMenu/ContextMenuContent.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/ContextMenu/ContextMenuContent.razor"
    },
    {
      "file": "UI/ContextMenu/ContextMenuGroup.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/ContextMenu/ContextMenuGroup.razor"
    },
    {
      "file": "UI/ContextMenu/ContextMenuItem.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/ContextMenu/ContextMenuItem.razor"
    },
    {
      "file": "UI/ContextMenu/ContextMenuLabel.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/ContextMenu/ContextMenuLabel.razor"
    },
    {
      "file": "UI/ContextMenu/ContextMenuRadioGroup.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/ContextMenu/ContextMenuRadioGroup.razor"
    },
    {
      "file": "UI/ContextMenu/ContextMenuRadioItem.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/ContextMenu/ContextMenuRadioItem.razor"
    },
    {
      "file": "UI/ContextMenu/ContextMenuSeparator.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/ContextMenu/ContextMenuSeparator.razor"
    },
    {
      "file": "UI/ContextMenu/ContextMenuTrigger.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/ContextMenu/ContextMenuTrigger.razor"
    }
  ],
  "source": [
    {
      "path": "UI/ContextMenu/ContextMenu.razor",
      "content": "@namespace Lumeo\n\n@{\n    var context = new ContextMenuContext(Open, _x, _y, EventCallback.Factory.Create<bool>(this, SetOpen), EventCallback.Factory.Create<(double, double)>(this, SetPosition));\n}\n\n<CascadingValue Value=\"context\" IsFixed=\"false\">\n    <div class=\"relative inline-block\" @attributes=\"AdditionalAttributes\">\n        @ChildContent\n    </div>\n</CascadingValue>\n\n@code {\n    [Parameter] public RenderFragment? ChildContent { get; set; }\n    [Parameter] public bool Open { get; set; }\n    [Parameter] public EventCallback<bool> OpenChanged { get; set; }\n\n    [Obsolete(\"Use Open instead. IsOpen will be removed in a future release.\")]\n    [Parameter] public bool IsOpen { get => Open; set => Open = value; }\n\n    [Obsolete(\"Use OpenChanged instead. IsOpenChanged will be removed in a future release.\")]\n    [Parameter] public EventCallback<bool> IsOpenChanged { get => OpenChanged; set => OpenChanged = value; }\n\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private double _x;\n    private double _y;\n\n    private async Task SetOpen(bool value)\n    {\n        Open = value;\n        await OpenChanged.InvokeAsync(value);\n    }\n\n    private Task SetPosition((double X, double Y) pos)\n    {\n        _x = pos.X;\n        _y = pos.Y;\n        return Task.CompletedTask;\n    }\n\n    public record ContextMenuContext(bool IsOpen, double X, double Y, EventCallback<bool> SetOpen, EventCallback<(double, double)> SetPosition);\n}\n"
    },
    {
      "path": "UI/ContextMenu/ContextMenuCheckboxItem.razor",
      "content": "@namespace Lumeo\n\n<button type=\"button\" role=\"menuitemcheckbox\" aria-checked=\"@Checked.ToString().ToLower()\" class=\"@CssClass\" @onclick=\"Toggle\" @attributes=\"AdditionalAttributes\">\n    <span class=\"flex h-3.5 w-3.5 items-center justify-center\">\n        @if (Checked)\n        {\n            <Blazicon Svg=\"Lucide.Check\" class=\"h-3.5 w-3.5\" />\n        }\n    </span>\n    @ChildContent\n</button>\n\n@code {\n    [Parameter] public RenderFragment? ChildContent { get; set; }\n    [Parameter] public bool Checked { get; set; }\n    [Parameter] public EventCallback<bool> CheckedChanged { get; set; }\n    [Parameter] public string? Class { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private string CssClass => $\"relative flex w-full cursor-default select-none items-center gap-2 rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground {Class}\".Trim();\n\n    private async Task Toggle()\n    {\n        Checked = !Checked;\n        await CheckedChanged.InvokeAsync(Checked);\n    }\n}\n"
    },
    {
      "path": "UI/ContextMenu/ContextMenuContent.razor",
      "content": "@namespace Lumeo\n@implements IAsyncDisposable\n\n@if (Context.IsOpen)\n{\n    <div id=\"@_contentId\" class=\"@CssClass\" style=\"@PositionStyle\" @onkeydown=\"HandleKeyDown\" tabindex=\"-1\" @attributes=\"AdditionalAttributes\">\n        @ChildContent\n    </div>\n}\n\n@inject ComponentInteropService Interop\n\n@code {\n    [CascadingParameter] public ContextMenu.ContextMenuContext Context { get; set; } = default!;\n    [Parameter] public RenderFragment? ChildContent { get; set; }\n    [Parameter] public string? Class { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private readonly string _contentId = $\"context-menu-content-{Guid.NewGuid():N}\";\n    private bool _registered;\n    private int _focusedIndex = -1;\n\n    private string CssClass => $\"fixed z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg animate-fade-in {Class}\".Trim();\n\n    private string PositionStyle => string.Create(System.Globalization.CultureInfo.InvariantCulture, $\"position: fixed; left: {Context.X}px; top: {Context.Y}px\");\n\n    protected override async Task OnAfterRenderAsync(bool firstRender)\n    {\n        if (Context.IsOpen && !_registered)\n        {\n            await Interop.RegisterClickOutside(_contentId, null, async () =>\n            {\n                await Context.SetOpen.InvokeAsync(false);\n                await InvokeAsync(StateHasChanged);\n            });\n            _registered = true;\n            _focusedIndex = -1;\n        }\n        else if (!Context.IsOpen && _registered)\n        {\n            await Cleanup();\n        }\n    }\n\n    private async Task HandleKeyDown(KeyboardEventArgs e)\n    {\n        switch (e.Key)\n        {\n            case \"Escape\":\n                await Context.SetOpen.InvokeAsync(false);\n                break;\n            case \"ArrowDown\":\n                {\n                    var count = await Interop.GetMenuItemCount(_contentId);\n                    if (count > 0)\n                    {\n                        _focusedIndex = (_focusedIndex + 1) % count;\n                        await Interop.FocusMenuItemByIndex(_contentId, _focusedIndex);\n                    }\n                    break;\n                }\n            case \"ArrowUp\":\n                {\n                    var count = await Interop.GetMenuItemCount(_contentId);\n                    if (count > 0)\n                    {\n                        _focusedIndex = _focusedIndex <= 0 ? count - 1 : _focusedIndex - 1;\n                        await Interop.FocusMenuItemByIndex(_contentId, _focusedIndex);\n                    }\n                    break;\n                }\n            case \"Home\":\n                {\n                    var count = await Interop.GetMenuItemCount(_contentId);\n                    if (count > 0)\n                    {\n                        _focusedIndex = 0;\n                        await Interop.FocusMenuItemByIndex(_contentId, _focusedIndex);\n                    }\n                    break;\n                }\n            case \"End\":\n                {\n                    var count = await Interop.GetMenuItemCount(_contentId);\n                    if (count > 0)\n                    {\n                        _focusedIndex = count - 1;\n                        await Interop.FocusMenuItemByIndex(_contentId, _focusedIndex);\n                    }\n                    break;\n                }\n        }\n    }\n\n    private async Task Cleanup()\n    {\n        try\n        {\n            await Interop.UnregisterClickOutside(_contentId);\n        }\n        catch (JSDisconnectedException) { }\n        _registered = false;\n    }\n\n    public async ValueTask DisposeAsync()\n    {\n        if (_registered) await Cleanup();\n    }\n}\n"
    },
    {
      "path": "UI/ContextMenu/ContextMenuGroup.razor",
      "content": "@namespace Lumeo\n\n<div role=\"group\" class=\"@CssClass\" @attributes=\"AdditionalAttributes\">\n    @ChildContent\n</div>\n\n@code {\n    [Parameter] public RenderFragment? ChildContent { get; set; }\n    [Parameter] public string? Class { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private string CssClass => $\"{Class}\".Trim();\n}\n"
    },
    {
      "path": "UI/ContextMenu/ContextMenuItem.razor",
      "content": "@namespace Lumeo\n\n<button class=\"@CssClass\" role=\"menuitem\" @onclick=\"HandleClick\" disabled=\"@Disabled\" @attributes=\"AdditionalAttributes\">\n    @ChildContent\n</button>\n\n@code {\n    [CascadingParameter] public ContextMenu.ContextMenuContext? Context { get; set; }\n    [Parameter] public RenderFragment? ChildContent { get; set; }\n    [Parameter] public EventCallback OnClick { get; set; }\n    [Parameter] public bool Disabled { get; set; }\n    [Parameter] public string? Class { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private string CssClass => $\"relative flex w-full cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 min-h-11 sm:min-h-0 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed {Class}\".Trim();\n\n    private async Task HandleClick()\n    {\n        await OnClick.InvokeAsync();\n        if (Context is not null)\n        {\n            await Context.SetOpen.InvokeAsync(false);\n        }\n    }\n}\n"
    },
    {
      "path": "UI/ContextMenu/ContextMenuLabel.razor",
      "content": "@namespace Lumeo\n\n<div class=\"@CssClass\" @attributes=\"AdditionalAttributes\">\n    @ChildContent\n</div>\n\n@code {\n    [Parameter] public RenderFragment? ChildContent { get; set; }\n    [Parameter] public string? Class { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private string CssClass => $\"px-2 py-1.5 text-sm font-semibold {Class}\".Trim();\n}\n"
    },
    {
      "path": "UI/ContextMenu/ContextMenuRadioGroup.razor",
      "content": "@namespace Lumeo\n\n@{\n    var context = new ContextMenuRadioGroupContext(Value, EventCallback.Factory.Create<string>(this, OnValueSelected));\n}\n\n<CascadingValue Value=\"context\" IsFixed=\"false\">\n    <div role=\"group\" @attributes=\"AdditionalAttributes\">\n        @ChildContent\n    </div>\n</CascadingValue>\n\n@code {\n    [Parameter] public RenderFragment? ChildContent { get; set; }\n    [Parameter] public string? Value { get; set; }\n    [Parameter] public EventCallback<string> ValueChanged { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private async Task OnValueSelected(string value)\n    {\n        Value = value;\n        await ValueChanged.InvokeAsync(value);\n    }\n\n    public record ContextMenuRadioGroupContext(string? Value, EventCallback<string> OnSelect);\n}\n"
    },
    {
      "path": "UI/ContextMenu/ContextMenuRadioItem.razor",
      "content": "@namespace Lumeo\n\n<button type=\"button\" role=\"menuitemradio\" aria-checked=\"@IsSelected.ToString().ToLower()\" class=\"@CssClass\" @onclick=\"Select\" @attributes=\"AdditionalAttributes\">\n    <span class=\"flex h-3.5 w-3.5 items-center justify-center\">\n        @if (IsSelected)\n        {\n            <Blazicon Svg=\"Lucide.Circle\" class=\"h-2 w-2 fill-current\" />\n        }\n    </span>\n    @ChildContent\n</button>\n\n@code {\n    [CascadingParameter] public ContextMenuRadioGroup.ContextMenuRadioGroupContext Context { get; set; } = default!;\n    [Parameter] public RenderFragment? ChildContent { get; set; }\n    [Parameter, EditorRequired] public string Value { get; set; } = \"\";\n    [Parameter] public string? Class { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private bool IsSelected => Context.Value == Value;\n\n    private string CssClass => $\"relative flex w-full cursor-default select-none items-center gap-2 rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground {Class}\".Trim();\n\n    private async Task Select() => await Context.OnSelect.InvokeAsync(Value);\n}\n"
    },
    {
      "path": "UI/ContextMenu/ContextMenuSeparator.razor",
      "content": "@namespace Lumeo\n\n<div class=\"@CssClass\" role=\"none\" @attributes=\"AdditionalAttributes\"></div>\n\n@code {\n    [Parameter] public string? Class { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private string CssClass => $\"-mx-1 my-1 h-px bg-border {Class}\".Trim();\n}\n"
    },
    {
      "path": "UI/ContextMenu/ContextMenuTrigger.razor",
      "content": "@namespace Lumeo\n@implements IAsyncDisposable\n\n<div id=\"@_triggerId\"\n     class=\"@CssClass\"\n     style=\"@InlineStyle\"\n     aria-expanded=\"@Context.IsOpen\"\n     @oncontextmenu=\"HandleContextMenu\" @oncontextmenu:preventDefault\n     @onkeydown=\"HandleKeyDown\"\n     @ontouchstart=\"HandleTouchStart\"\n     @ontouchmove=\"HandleTouchMove\"\n     @ontouchend=\"HandleTouchEnd\"\n     @ontouchcancel=\"HandleTouchEnd\"\n     @attributes=\"AdditionalAttributes\">\n    @ChildContent\n</div>\n\n@inject ComponentInteropService Interop\n\n@code {\n    [CascadingParameter] public ContextMenu.ContextMenuContext Context { get; set; } = default!;\n    [Parameter] public RenderFragment? ChildContent { get; set; }\n    [Parameter] public string? Class { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    /// <summary>\n    /// Duration in milliseconds a touch must remain on the trigger (without\n    /// significant movement) before the context menu opens. Default: 500ms.\n    /// </summary>\n    [Parameter] public int LongPressMs { get; set; } = 500;\n\n    /// <summary>\n    /// Pixel threshold for touch movement that cancels the long-press timer.\n    /// Above this, the user is interpreted as scrolling, not long-pressing.\n    /// </summary>\n    [Parameter] public double MoveTolerancePx { get; set; } = 10.0;\n\n    private readonly string _triggerId = $\"ctx-trigger-{Guid.NewGuid():N}\";\n\n    private CancellationTokenSource? _longPressCts;\n    private double _touchStartX;\n    private double _touchStartY;\n\n    // touch-action: manipulation keeps native scroll working but disables\n    // double-tap-to-zoom. -webkit-touch-callout: none + user-select: none\n    // suppresses iOS Safari's default long-press callout (share/select sheet)\n    // so our 500ms timer is the only thing that fires.\n    private string InlineStyle => \"touch-action: manipulation; -webkit-touch-callout: none; user-select: none;\";\n\n    private string CssClass => $\"inline-flex {Class}\".Trim();\n\n    private async Task HandleContextMenu(MouseEventArgs e)\n    {\n        await Context.SetPosition.InvokeAsync((e.ClientX, e.ClientY));\n        await Context.SetOpen.InvokeAsync(true);\n    }\n\n    // Keyboard activation: Shift+F10 or the dedicated Menu key opens the context\n    // menu positioned at the bottom-left corner of the trigger element.\n    private async Task HandleKeyDown(KeyboardEventArgs e)\n    {\n        if (e.Key == \"ContextMenu\" || (e.ShiftKey && e.Key == \"F10\"))\n        {\n            var rect = await Interop.GetElementRect(_triggerId);\n            var x = rect?.X ?? 0;\n            var y = rect is not null ? rect.Y + rect.Height : 0;\n            await Context.SetPosition.InvokeAsync((x, y));\n            await Context.SetOpen.InvokeAsync(true);\n        }\n    }\n\n    // --- Long-press touch path (iOS Safari fix) ---\n    //\n    // iOS Safari does not reliably fire `contextmenu` for non-text content on\n    // long-press, so we run a parallel touch timer. On touchstart we record the\n    // initial position and start a 500ms delay. If the user moves more than\n    // MoveTolerancePx pixels (= scrolling) or lifts the finger (= tap) before\n    // the timer elapses, we cancel. When the timer fires uncancelled, we open\n    // the menu at the touch coordinates and emit a short haptic pulse.\n\n    private async Task HandleTouchStart(TouchEventArgs e)\n    {\n        if (e.Touches.Length == 0) return;\n\n        var touch = e.Touches[0];\n        _touchStartX = touch.ClientX;\n        _touchStartY = touch.ClientY;\n\n        CancelLongPress();\n        _longPressCts = new CancellationTokenSource();\n        var token = _longPressCts.Token;\n        var x = touch.ClientX;\n        var y = touch.ClientY;\n\n        try\n        {\n            await Task.Delay(LongPressMs, token);\n        }\n        catch (TaskCanceledException)\n        {\n            return;\n        }\n\n        if (token.IsCancellationRequested) return;\n\n        // Long-press fired. Open at the original touch position and pulse haptics.\n        await Context.SetPosition.InvokeAsync((x, y));\n        await Context.SetOpen.InvokeAsync(true);\n        await Interop.Vibrate(50);\n    }\n\n    private void HandleTouchMove(TouchEventArgs e)\n    {\n        if (_longPressCts is null || e.Touches.Length == 0) return;\n\n        var touch = e.Touches[0];\n        var dx = touch.ClientX - _touchStartX;\n        var dy = touch.ClientY - _touchStartY;\n        if ((dx * dx + dy * dy) > (MoveTolerancePx * MoveTolerancePx))\n        {\n            CancelLongPress();\n        }\n    }\n\n    private void HandleTouchEnd(TouchEventArgs e) => CancelLongPress();\n\n    private void CancelLongPress()\n    {\n        if (_longPressCts is null) return;\n        try { _longPressCts.Cancel(); } catch (ObjectDisposedException) { }\n        _longPressCts.Dispose();\n        _longPressCts = null;\n    }\n\n    public ValueTask DisposeAsync()\n    {\n        CancelLongPress();\n        return ValueTask.CompletedTask;\n    }\n}\n"
    }
  ],
  "slots": [
    {
      "name": "ChildContent",
      "owner": "ContextMenu",
      "type": "RenderFragment?",
      "description": null
    },
    {
      "name": "ChildContent",
      "owner": "ContextMenuCheckboxItem",
      "type": "RenderFragment?",
      "description": null
    },
    {
      "name": "ChildContent",
      "owner": "ContextMenuContent",
      "type": "RenderFragment?",
      "description": null
    },
    {
      "name": "ChildContent",
      "owner": "ContextMenuGroup",
      "type": "RenderFragment?",
      "description": null
    },
    {
      "name": "ChildContent",
      "owner": "ContextMenuItem",
      "type": "RenderFragment?",
      "description": null
    },
    {
      "name": "ChildContent",
      "owner": "ContextMenuLabel",
      "type": "RenderFragment?",
      "description": null
    },
    {
      "name": "ChildContent",
      "owner": "ContextMenuRadioGroup",
      "type": "RenderFragment?",
      "description": null
    },
    {
      "name": "ChildContent",
      "owner": "ContextMenuRadioItem",
      "type": "RenderFragment?",
      "description": null
    },
    {
      "name": "ChildContent",
      "owner": "ContextMenuTrigger",
      "type": "RenderFragment?",
      "description": null
    }
  ],
  "serviceDependencies": [
    {
      "service": "ComponentInteropService",
      "namespace": ""
    }
  ],
  "cascadingDependencies": [
    {
      "name": "Context",
      "type": "ContextMenu.ContextMenuContext",
      "required": true
    }
  ],
  "relatedComponents": [
    {
      "name": "ContextMenuCheckboxItem",
      "reason": "sub-component"
    },
    {
      "name": "ContextMenuContent",
      "reason": "sub-component"
    },
    {
      "name": "ContextMenuGroup",
      "reason": "sub-component"
    },
    {
      "name": "ContextMenuItem",
      "reason": "sub-component"
    },
    {
      "name": "ContextMenuLabel",
      "reason": "sub-component"
    },
    {
      "name": "ContextMenuRadioGroup",
      "reason": "sub-component"
    },
    {
      "name": "ContextMenuRadioItem",
      "reason": "sub-component"
    },
    {
      "name": "ContextMenuSeparator",
      "reason": "sub-component"
    },
    {
      "name": "ContextMenuTrigger",
      "reason": "sub-component"
    }
  ],
  "keyboardInteractions": [
    {
      "key": "ContextMenu",
      "action": "HandleKeyDown handles ContextMenu in ContextMenuTrigger.razor"
    },
    {
      "key": "F10",
      "action": "HandleKeyDown handles F10 in ContextMenuTrigger.razor"
    }
  ],
  "tests": [
    "tests/Lumeo.Tests/Components/ContextMenu/ContextMenuTests.cs"
  ],
  "mdSummary": "# ContextMenu\n\nRight-click menu tied to a container element.\n\n**Package:** `Lumeo` · **Category:** Overlay\n**Docs:** https://lumeo.nativ.sh/components/context-menu\n\n## Parameters\n| Owner | Name | Type | Default | Description |\n|---|---|---|---|---|\n| ContextMenu | ChildContent | `RenderFragment?` | `` |  |\n| ContextMenu | Open | `bool` | `` |  |\n| ContextMenu | OpenChanged | `EventCallback<bool>` | `` |  |\n| ContextMenu | IsOpen | `bool` | `` |  |\n| ContextMenu | IsOpenChanged | `EventCallback<bool>` | `` |  |\n| ContextMenu | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| ContextMenuCheckboxItem | ChildContent | `RenderFragment?` | `` |  |\n| ContextMenuCheckboxItem | Checked | `bool` | `` |  |\n| ContextMenuCheckboxItem | CheckedChanged | `EventCallback<bool>` | `` |  |\n| ContextMenuCheckboxItem | Class | `string?` | `` |  |\n| ContextMenuCheckboxItem | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| ContextMenuContent | Context | `ContextMenu.ContextMenuContext` | `default!` |  |\n| ContextMenuContent | ChildContent | `RenderFragment?` | `` |  |\n| ContextMenuContent | Class | `string?` | `` |  |\n| ContextMenuContent | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| ContextMenuGroup | ChildContent | `RenderFragment?` | `` |  |\n| ContextMenuGroup | Class | `string?` | `` |  |\n| ContextMenuGroup | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| ContextMenuItem | Context | `ContextMenu.ContextMenuContext?` | `` |  |\n| ContextMenuItem | ChildContent | `RenderFragment?` | `` |  |\n| ContextMenuItem | OnClick | `EventCallback` | `` |  |\n| ContextMenuItem | Disabled | `bool` | `` |  |\n| ContextMenuItem | Class | `string?` | `` |  |\n| ContextMenuItem | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| ContextMenuLabel | ChildContent | `RenderFragment?` | `` |  |\n| ContextMenuLabel | Class | `string?` | `` |  |\n| ContextMenuLabel | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| ContextMenuRadioGroup | ChildContent | `RenderFragment?` | `` |  |\n| ContextMenuRadioGroup | Value | `string?` | `` |  |\n| ContextMenuRadioGroup | ValueChanged | `EventCallback<string>` | `` |  |\n| ContextMenuRadioGroup | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| ContextMenuRadioItem | Context | `ContextMenuRadioGroup.ContextMenuRadioGroupContext` | `default!` |  |\n| ContextMenuRadioItem | ChildContent | `RenderFragment?` | `` |  |\n| ContextMenuRadioItem | Value | `string` | `\"\"` |  |\n| ContextMenuRadioItem | Class | `string?` | `` |  |\n| ContextMenuRadioItem | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| ContextMenuSeparator | Class | `string?` | `` |  |\n| ContextMenuSeparator | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| ContextMenuTrigger | Context | `ContextMenu.ContextMenuContext` | `default!` |  |\n| ContextMenuTrigger | ChildContent | `RenderFragment?` | `` |  |\n| ContextMenuTrigger | Class | `string?` | `` |  |\n| ContextMenuTrigger | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| ContextMenuTrigger | LongPressMs | `int` | `500` | Duration in milliseconds a touch must remain on the trigger (without significant movement) before the context menu opens. Default: 500ms. |\n| ContextMenuTrigger | MoveTolerancePx | `double` | `10.0` | Pixel threshold for touch movement that cancels the long-press timer. Above this, the user is interpreted as scrolling, not long-pressing. |\n\n## Slots\n- `ChildContent` (ContextMenu) — RenderFragment slot\n- `ChildContent` (ContextMenuCheckboxItem) — RenderFragment slot\n- `ChildContent` (ContextMenuContent) — RenderFragment slot\n- `ChildContent` (ContextMenuGroup) — RenderFragment slot\n- `ChildContent` (ContextMenuItem) — RenderFragment slot\n- `ChildContent` (ContextMenuLabel) — RenderFragment slot\n- `ChildContent` (ContextMenuRadioGroup) — RenderFragment slot\n- `ChildContent` (ContextMenuRadioItem) — RenderFragment slot\n- `ChildContent` (ContextMenuTrigger) — RenderFragment slot\n\n## Events\n- `OpenChanged` (`EventCallback<bool>`)\n- `IsOpenChanged` (`EventCallback<bool>`)\n\n## Sub-components\n- `ContextMenuCheckboxItem`, `ContextMenuContent`, `ContextMenuGroup`, `ContextMenuItem`, `ContextMenuLabel`, `ContextMenuRadioGroup`, `ContextMenuRadioItem`, `ContextMenuSeparator`, `ContextMenuTrigger`\n\n## Cascading values it expects\n- `Context` (`ContextMenu.ContextMenuContext`)\n\n## Services it injects\n- `ComponentInteropService` ()\n\n## Keyboard\n- `ContextMenu` — HandleKeyDown handles ContextMenu in ContextMenuTrigger.razor\n- `F10` — HandleKeyDown handles F10 in ContextMenuTrigger.razor\n\n## Example\n```razor\n<ContextMenu />\n```\n\n## CSS variables\n`--color-accent`, `--color-accent-foreground`, `--color-border`, `--color-popover`, `--color-popover-foreground`, `--radius`\n\n## Files\n- `UI/ContextMenu/ContextMenu.razor`\n- `UI/ContextMenu/ContextMenuCheckboxItem.razor`\n- `UI/ContextMenu/ContextMenuContent.razor`\n- `UI/ContextMenu/ContextMenuGroup.razor`\n- `UI/ContextMenu/ContextMenuItem.razor`\n- `UI/ContextMenu/ContextMenuLabel.razor`\n- `UI/ContextMenu/ContextMenuRadioGroup.razor`\n- `UI/ContextMenu/ContextMenuRadioItem.razor`\n- `UI/ContextMenu/ContextMenuSeparator.razor`\n- `UI/ContextMenu/ContextMenuTrigger.razor`\n"
}