JointJS - Playground

prevent contextmenu. https://github.com/clientIO/joint/issues/2658

by Roman Bruckner

HTML

<link rel="stylesheet" type="text/css" href="sample.css" />

    <div class="toolbar">
        <button id="btn-create-part">Create Element</button>
        <button id="btn-create-port">Create Port (Square)</button>
        <span id="status"></span>
    </div>
    <div id="paper"></div>


    <script src="https://cdn.jsdelivr.net/npm/@joint/[email protected]/dist/joint.min.js"></script>
    
    <!-- Sample Logic -->
    <script src="sample.js"></script>

CSS

body {
    font-family: sans-serif;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    height: 100vh;
    overflow: hidden;
}

.toolbar {
    padding: 10px;
    background: #f0f0f0;
    border-bottom: 1px solid #ccc;
    display: flex;
    gap: 10px;
    align-items: center;
}

#paper {
    flex: 1;
    overflow: auto;
    background: #f7f7f7;
    position: relative;
}

button {
    padding: 8px 12px;
    cursor: pointer;
}

/* Port Styling */
.joint-port-body {
    stroke: #333;
    fill: #fff;
    stroke-width: 1;
}

.joint-port-label {
    font-size: 10px;
    font-family: Arial, sans-serif;
    fill: #333;
}

/* Resize Handle */
.resize-handle {
    fill: #333;
    stroke: #fff;
    stroke-width: 1;
    cursor: nwse-resize;
    display: none;
    /* Hidden by default, shown on selection */
}

.joint-element.selected .resize-handle {
    display: block;
}

JavaScript

const { dia, shapes, util } = joint;

// 1. Initialize Graph and Paper
const graph = new dia.Graph();

const paper = new dia.Paper({
  model: graph,
  el: document.getElementById("paper"),
  width: "100%",
  height: "100%",
  background: { color: "#F8F9FA" },
  gridSize: 10,
  drawGrid: true,
  async: true,
  defaultConnectionPoint: {
    name: "rectangle",
    // args: { useModelGeometry: true },
  },
  interactive: { linkMove: false },

  linkPinning: false,

  validateConnection: function (
    cellViewS,
    magnetS,
    cellViewT,
    magnetT,
    end,
    linkView,
  ) {
    if (cellViewS === cellViewT) return false;
    return cellViewT.model.isElement() || magnetT !== undefined;
  },

  // defaultConnectionPoint: { name: 'rectangle', args: { useModelGeometry: true }},
  interactive: { linkMove: false },
  measureNode: function (el, view) {
    if (el === view.el) {
      return { x: 0, y: 0, ...view.model.size() };
    }
    return V(el).getBBox();
  },
});

// 2. State Management
let selectedView = null;
let selectedPortId = null;
let selectedPortLabelId = null; // New: Track selected label
let drawingLink = null;
let isResizing = false;
let resizeStartPosition = null;
let resizeStartSize = null;
let draggingPort = null;
let draggingPortLabel = null;
let selectedPortView = null;
let resizingPort = null;
let resizingPortStart = null;

const defaultPortGroups = {
  "left-port-group": {
    position: { name: "absolute", args: { x: 0, y: 0 } },
    attrs: {
      // should not be here (custom attributes should be one level up)
      // z: "auto",
      // show: true,
      // direction: "",

      // Connect directly to the body (don't include the label)
      portRoot: {
          magnetSelector: 'portBody',
      },

      portLabel: {
        fontSize: 12,
        dominantBaseline: "hanging",
        lineHeight: 16,
        displayEmpty: true,
      },
      portBody: {
       ...