JavaScript is required

Node Context Menu via Slots

A compact relation-graph example that opens a node action menu from custom slotted node DOM and renders that overlay through `RGSlotOnView`. It demonstrates pointer-relative menu positioning, native context-menu suppression, click-away dismissal, and shared success feedback without turning the graph into an editor.

Node Context Menus with relation-graph Slots

What This Example Builds

This example builds a read-only relation graph whose nodes are rendered as green circular icon badges instead of the default node body. Clicking or right-clicking a node opens a floating action menu at the pointer position inside the graph scene, and choosing one of the fixed actions shows a success message before the menu closes.

The main lesson is the split of responsibilities between two slot layers: RGSlotOnNode provides the interactive trigger surface, and RGSlotOnView renders the overlay menu inside the graph viewport.

How the Data Is Organized

The graph data is declared inline inside initializeGraph as a single RGJsonData object. It uses rootId: '2', a nodes array with id, text, and data.myicon, and a lines array where every record has explicit id, from, to, and text fields.

There is no preprocessing step before setJsonData beyond assembling this object in code. In a production graph, data.myicon could be replaced with role, status, type, or category metadata that drives both the node appearance and the available node actions.

How relation-graph Is Used

RGProvider wraps the demo so RGHooks.useGraphInstance() can read the active graph instance. A useEffect waits for that instance and then calls setJsonData(), moveToCenter(), and zoomToFit() to load the sample network and fit it into view.

The local graphOptions object only adjusts default presentation: node color is green, node shape is circular, and junction points connect on the border. The reviewed files do not set an explicit layout option, so the final arrangement depends on relation-graph defaults outside this example.

RGSlotOnNode replaces the default node body with custom DOM. That slot reads node.data.myicon, renders a Lucide icon, adds a caption block under the node, and binds both onClick and onContextMenu to the same menu-opening handler.

RGSlotOnView renders the menu panel inside the graph view layer. React state tracks the active node, whether the panel is visible, and the menu coordinates relative to the wrapping element. The registered onNodeClick and onLineClick handlers are secondary here; in the reviewed source they only log to the console. The example does not implement graph editing or data mutation.

Styling is split between SCSS and inline styles. SCSS defines the circular node shell and the white floating menu card, while inline styles place the label block below each node.

Key Interactions

Left-clicking a custom node opens the same menu as right-clicking it.

Right-click does not show the browser’s native context menu because the node handler calls preventDefault().

The menu is positioned from wrapper-relative pointer coordinates, so it appears where the interaction happened without leaving the graph scene.

Clicking outside the panel closes it, while clicking inside the panel does not, because the menu stops propagation and the wrapper handles click-away dismissal.

Selecting an action does not change graph data. It sends a success toast through SimpleGlobalMessage and then hides the menu.

Key Code Fragments

This options object shows that the example changes node appearance and junction behavior without introducing a custom layout configuration.

const graphOptions: RGOptions = {
  defaultNodeColor: 'rgba(66,187,66,1)',
  defaultNodeShape: RGNodeShape.circle,
  defaultJunctionPoint: RGJunctionPoint.border
};

This handler proves that the menu position is computed from the pointer location relative to the wrapper element, not from graph model coordinates.

const showNodeMenus = (node: RGNode, $event: React.MouseEvent<HTMLDivElement>) => {
  setCurrentNode(node);
  if (myPage.current) {
    const _base_position = myPage.current.getBoundingClientRect();
    setIsShowNodeMenuPanel(true);
    setNodeMenuPanelPosition({
      x: $event.clientX - _base_position.x,
      y: $event.clientY - _base_position.y
    });
  }
  $event.stopPropagation();
  $event.preventDefault();
};

This node slot is the real trigger surface: both click and context-menu events are attached to custom node DOM instead of relying on a graph-wide context-menu hook.

<RGSlotOnNode>
  {({ node }: RGNodeSlotProps) => (
    <div
      className="c-my-rg-node"
      onClick={(event) => showNodeMenus(node, event)}
      onContextMenu={(event) => showNodeMenus(node, event)}
    >
      <NodeIcon name={node.data?.myicon} />
      <div>
        {node.data?.myicon}
      </div>
    </div>
  )}
</RGSlotOnNode>

This view slot shows that the menu is rendered inside the graph layer and only exists while the local visibility state is true.

<RGSlotOnView>
  {isShowNodeMenuPanel && (
    <div
      className="pointer-events-auto context-menu-panel"
      style={{ left: nodeMenuPanelPosition.x, top: nodeMenuPanelPosition.y }}
      onClick={(e) => e.stopPropagation()}
    >
      <div className="py-1 px-2 text-gray-400 text-xs border-b">
        Node Actions:
      </div>
      {/* menu items */}
    </div>
  )}
</RGSlotOnView>

This action handler confirms that menu selections only emit shared success feedback and close the panel.

const doAction = (actionName: string) => {
  SimpleGlobalMessage.showMessage({
    message: `Performed action ${actionName} on node: ${currentNode?.text}`,
    type: 'success'
  });
  setIsShowNodeMenuPanel(false);
};

What Makes This Example Distinct

Comparison data places this example near node-menu, node-tips, simple, node-content-lines, and node, but its emphasis is narrower. Its standout combination is RGSlotOnNode plus RGSlotOnView, pointer-positioned menu state, browser context-menu suppression, outside-click dismissal, and toast-confirmed actions in an otherwise read-only viewer.

Compared with node-menu, this is not a graph-wide context-menu system for nodes, lines, and canvas. Only the custom node DOM opens the menu, and the same trigger surface supports both left-click and right-click. Compared with node-tips, the floating panel is operational rather than informational because users can click actions and receive feedback instead of only seeing hover detail. Compared with simple and node, the view slot is used for transient contextual UI rather than persistent navigation or utility widgets.

That makes this example a stronger starting point when the requirement is per-node actions on a mostly fixed graph, not a broader editor or dashboard.

Where Else This Pattern Applies

This pattern can be transferred to organization charts where a node should open actions such as view profile, assign owner, or jump to a detail page.

It also fits service maps, asset topology viewers, and knowledge-graph explorers where the graph remains read-only but each node needs lightweight operational commands.

The same structure can be extended for workflow monitoring, case-management, or dependency-analysis tools by replacing the hardcoded action list with permission-aware commands and replacing data.myicon with domain metadata.