Skip to content
D
Documentation

Edit, copy, and remove nodes

how-to
4 min readUpdated

Use the canvas for the user's selection and the instance's engine for command-based edits. This lets you edit a node label in place, copy or duplicate selected nodes, paste clipboard contents, and remove selected nodes without rebuilding the diagram.

Edit a label in place

Enable in-place editing on the mounted instance. A double-click opens an editor on the node label; Enter or blur commits the new label as one undoable step, while Escape abandons it. The JavaScript, Angular, React, and Vue examples also add toolbar actions for copy, paste, duplicate, and delete; the Qwik example follows the mounted-canvas pattern and uses its keyboard shortcuts for clipboard actions.

For the typed node data and mounting patterns across JavaScript, Angular, React, and Vue, see Build runnable workflows. This page adds the engine actions for editing, copying, pasting, duplicating, and deleting selected nodes; click a node before using Copy, Duplicate, or Delete. Paste uses the engine clipboard rather than the operating system clipboard.

ts
import { render } from '@grafloria/element';
import type { NodeSpec } from '@grafloria/renderer';

const nodes: NodeSpec[] = [
  { id: 'draft', position: { x: 80, y: 110 }, size: { width: 150, height: 60 }, label: 'Draft' },
  { id: 'review', position: { x: 360, y: 110 }, size: { width: 150, height: 60 }, label: 'Review' },
];

const root = document.createElement('main');
root.style.height = '620px';
document.body.append(root);
const toolbar = document.createElement('div');
const canvas = document.createElement('div');
canvas.style.height = '570px';
root.append(toolbar, canvas);

const instance = render({ nodes }, canvas);
instance.getEngine().setInteractionConfig({ enableInPlaceTextEdit: true });

async function act(action: 'copy' | 'paste' | 'duplicate' | 'delete'): Promise<void> {
  const engine = instance.getEngine();
  const hasSelection = instance.getModel().getSelectedNodes().length > 0;
  if (action === 'copy' && hasSelection) await engine.copy();
  if (action === 'paste' && engine.hasClipboardData()) await engine.paste();
  if (action === 'duplicate' && hasSelection) await engine.duplicate();
  if (action === 'delete' && hasSelection) await engine.deleteSelection();
  instance.renderNow();
}

for (const action of ['copy', 'paste', 'duplicate', 'delete'] as const) {
  const button = document.createElement('button');
  button.textContent = action[0].toUpperCase() + action.slice(1);
  button.addEventListener('click', () => { void act(action); });
  toolbar.append(button);
}

On the canvas, double-click Draft or Review to edit its label. Enter and blur commit the edit; Escape cancels it. Click a node to select it, then use Copy followed by Paste to create a separate node; repeat Paste to place further copies at cascading offsets. Duplicate creates a copy directly from the selection, and Delete removes the selected node. The JavaScript, Angular, React, and Vue examples expose these actions as toolbar buttons that run only when their required selection or clipboard content exists. Command-based changes participate in the engine's undo history; ⌘Z or Ctrl+Z undoes the last edit.

Copy and paste versus duplicate

copy() snapshots the current selection to the engine clipboard; paste() creates new entities from that clipboard. duplicate() creates copies directly from the current selection. Both operations create nodes with new IDs and positions, so editing one copy does not change its source. Paste can be repeated from the same clipboard contents. These are engine operations, not browser clipboard integration.

OperationOptionTypeDefaultEffect
copy()includeGroupsbooleanfalseIncludes groups containing selected nodes.
copy()includeLinksbooleantrueIncludes links between copied nodes.
paste()offsetPointFirst paste: { x: 20, y: 20 }; later pastes cascadeShifts pasted entities to a new position.
paste()selectPastedbooleantrueSelects the pasted entities.
duplicate()offsetPoint{ x: 20, y: 20 }Shifts duplicated entities to a new position.
duplicate()selectDuplicatedbooleantrueSelects the duplicated entities.
deleteSelection()deleteChildrenbooleantrueRecursively deletes child nodes.
deleteSelection()deleteLinksbooleantrueRemoves links before deleting nodes; links attached to a removed node are removed either way.

The default paste offset starts at 20 pixels down and right and advances for subsequent pastes of the same clipboard contents. The copy and paste demo shows the rendered result: each paste has a different ID and position, and moving one copy leaves the others in place.

Try Copy / paste while you click Original, copy it, and paste more than once. The demo source is copy-paste.html.

Remove selected nodes

deleteSelection() acts on the current selection. When you remove a node, its connected links are removed too, so the diagram does not retain dangling edges. The deletion is undoable with the engine's command history. This is ordinary deletion; reconnecting neighbors across a removed middle node is an extra behavior implemented by the separate delete-middle-node demo, not an effect of deleteSelection() itself.

See Delete a middle node for the custom variant that adds an undoable A-to-C bridge after removing B; its source explicitly reads the neighbors before deleting.

Add a context menu

A context menu is host UI: the context-menu demo builds a Rename / Duplicate / Delete menu, listens for a real contextmenu event, anchors its menu at the pointer, and calls model or engine operations for the targeted node. Its source is a starting point when you need that interaction rather than the toolbar used above.

For plain JavaScript, the same task is also shown in the edit-label demo, where double-click opens the editor and Enter commits the label. See its source.

Options that affect the edit

OptionTypeDefaultWhat it does
enableInPlaceTextEditbooleanfalseEnables double-click editing of node labels.
offset on paste()PointFirst paste: { x: 20, y: 20 }; later pastes cascadeSets the position shift applied to pasted entities. Pass an explicit point to control placement.
offset on duplicate()Point{ x: 20, y: 20 }Sets the position shift applied to duplicated entities.
selectPastedbooleantrueSelects the pasted entities.
selectDuplicatedbooleantrueSelects the duplicated entities.

Pitfalls

  • The engine clipboard is separate from navigator.clipboard; connect it to the operating-system clipboard yourself if cross-tab or system paste is required.
  • In-place label editing is opt-in. Leave enableInPlaceTextEdit off if your app handles the node's double-click event with its own editor.
  • Do not put user-supplied node data into innerHTML in a custom renderer. Use textContent for untrusted values to avoid interpreting them as markup. See Create custom nodes.
  • For a custom delete that reconnects neighboring nodes, use the delete-middle-node demo's separate read-neighbors, remove, and bridge steps; node deletion alone does not create that bridge.

Was this page helpful?