JointJS - Playground

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

HTML

<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/jointjs/3.7.5/joint.min.css" />
    <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>

    <!-- Dependencies -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.4.1/backbone-min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jointjs/3.7.5/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: 'boundary' },
    linkPinning: false,
    validateConnection: function (cellViewS, magnetS, cellViewT, magnetT, end, linkView) {
        if (cellViewS === cellViewT) return false;
        return cellViewT.model.isElement() || magnetT !== undefined;
    }
});

// 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: 'manual', args: { x: 0, y: 0 } },
        attrs: {
            z: 'auto',
            show: true,
            direction: '',
            portLabel: {
                fontSize: 12,
                dominantBaseline: 'hanging',
                lineHeight: 16,
                displayEmpty: true
            },
            portBody: {
                magnet: 'passive',
                x: -11.5,
                y: -11.5,
                width: 23,
                height: 23,
                fill: '#ffffff',
                rx: 3,
                ry: 3,
                stroke: '#2a2a2a'
            },
            portLabelRect: {
                fill: 'transparent',
                stroke: 'transparent',
                strokeWidth: 1
            },
            portLabelLine: {
                stroke: '#2a2a2a',
                strokeWidth:...