{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "context-menu",
  "title": "Context Menu",
  "type": "registry:ui",
  "description": "Right-click menu — same primitives as Dropdown Menu but triggered by `contextmenu` events. Drop on any element you want to attach row actions, file-system style operations, or copy/paste menus to.",
  "categories": [
    "overlay"
  ],
  "files": [
    {
      "path": "packages/elements-lit/src/components/context-menu/context-menu.ts",
      "content": "import { html, type PropertyDeclarations } from 'lit'\nimport { MenuBase, setAttr, type MenuClasses, type Placement } from '../dropdown-menu/menu-core'\n\n// React's class strings, verbatim (packages/registry-react/components/context-menu).\nconst classes: MenuClasses = {\n  content:\n    'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-context-menu-content-available-height) min-w-[8rem] overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md',\n  subContent:\n    'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg',\n  item: \"focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/40 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus-visible:ring-2 focus-visible:ring-inset data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n  checkboxItem:\n    \"focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus-visible:ring-2 focus-visible:ring-inset data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n  radioItem:\n    \"focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus-visible:ring-2 focus-visible:ring-inset data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n  label: 'text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:pl-8',\n  separator: 'bg-border -mx-1 my-1 h-px',\n  shortcut: 'text-muted-foreground ml-auto text-xs tracking-widest',\n  subTrigger:\n    \"focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus-visible:ring-2 focus-visible:ring-inset data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n  subChevron: 'ml-auto',\n  originVar: '--radix-context-menu-content-transform-origin',\n  availableHeightVar: '--radix-context-menu-content-available-height',\n}\n\n/**\n * <uip-context-menu> — the registry ContextMenu as ONE web component.\n *\n * The right-click target is the element in `slot=\"trigger\"` (it keeps its own\n * page classes, like React's ContextMenuTrigger className). The menu is\n * declared with the same data tags as <uip-dropdown-menu>, prefixed\n * `uip-context-menu-` (content, item, checkbox-item, radio-group, radio-item,\n * label, separator, group, shortcut, sub/sub-trigger/sub-content), and opens\n * at the pointer on `contextmenu` (right click, Shift+F10 / the menu key, or a\n * 700ms touch long-press), flipping/shifting to stay in the viewport.\n *\n *   <uip-context-menu class=\"[&::part(content)]:w-48\">\n *     <div slot=\"trigger\" class=\"…\">Right-click here</div>\n *     <uip-context-menu-content>\n *       <uip-context-menu-item>Back</uip-context-menu-item>\n *     </uip-context-menu-content>\n *   </uip-context-menu>\n *\n * Events: `open-change` { open } on the host; `select` (cancelable),\n * `checked-change` { checked } and `value-change` { value } on the data tags.\n * Styling: same parts as <uip-dropdown-menu> (`content`, `sub-content`,\n * `item`, `label`, `separator`, `shortcut`, plus a data tag's own `part`),\n * reached from the host: `class=\"[&::part(content)]:w-48\"`.\n * The trigger gets `data-state`, `data-uipkge` and\n * `data-slot=\"context-menu-trigger\"`.\n * Like Radix's ContextMenu, there is no `open` prop (state is internal).\n */\nexport class UipContextMenu extends MenuBase {\n  static properties: PropertyDeclarations = { open: { state: true } }\n\n  protected readonly menuPrefix = 'context-menu' as const\n  protected readonly C = classes\n  protected readonly defaults: Placement = { side: 'right', align: 'start', sideOffset: 2, alignOffset: 0 }\n  private longPress?: ReturnType<typeof setTimeout>\n\n  private get trigger() {\n    const slot = this.renderRoot?.querySelector<HTMLSlotElement>('slot[name=\"trigger\"]')\n    return (slot?.assignedElements()[0] as HTMLElement | undefined) ?? null\n  }\n\n  private openAt(x: number, y: number) {\n    this.anchor = new DOMRect(x, y, 0, 0)\n    this.focusOnOpen = 'content'\n    if (this.open) this.reposition()\n    else this.setOpen(true)\n  }\n\n  private onContextMenu(e: MouseEvent) {\n    e.preventDefault()\n    // Keyboard-invoked (Shift+F10 / menu key) events carry no pointer position.\n    if (e.clientX === 0 && e.clientY === 0 && this.trigger) {\n      const r = this.trigger.getBoundingClientRect()\n      this.openAt(r.left, r.top)\n    } else this.openAt(e.clientX, e.clientY)\n  }\n\n  private onPointerDown(e: PointerEvent) {\n    if (e.pointerType === 'mouse') return\n    clearTimeout(this.longPress)\n    this.longPress = setTimeout(() => this.openAt(e.clientX, e.clientY), 700)\n  }\n\n  private cancelLongPress() {\n    clearTimeout(this.longPress)\n  }\n\n  protected isTriggerEvent(path: EventTarget[], e: Event) {\n    // A right-click on the trigger re-opens at the new point instead of closing.\n    const t = this.trigger\n    return !!t && path.includes(t) && (e as PointerEvent).button === 2\n  }\n\n  protected onOpenStateRendered() {\n    const t = this.trigger\n    if (!t) return\n    setAttr(t, 'data-uipkge', '')\n    setAttr(t, 'data-slot', 'context-menu-trigger')\n    setAttr(t, 'data-state', this.open ? 'open' : 'closed')\n  }\n\n  protected renderTrigger() {\n    return html`<slot\n      name=\"trigger\"\n      @slotchange=${() => this.onOpenStateRendered()}\n      @contextmenu=${this.onContextMenu}\n      @pointerdown=${this.onPointerDown}\n      @pointermove=${this.cancelLongPress}\n      @pointerup=${this.cancelLongPress}\n      @pointercancel=${this.cancelLongPress}\n    ></slot>`\n  }\n}\n\ncustomElements.get('uip-context-menu') || customElements.define('uip-context-menu', UipContextMenu)\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'uip-context-menu': UipContextMenu\n  }\n}\n",
      "type": "registry:file",
      "target": "src/uip/components/context-menu/context-menu.ts"
    }
  ],
  "dependencies": [
    "lit"
  ],
  "registryDependencies": [
    "https://uipkge.dev/r/lit/dropdown-menu.json",
    "https://uipkge.dev/r/lit/lit-core.json"
  ]
}
