{"owner":"clientIO","repo":"joint","hasSkills":true,"totalSkillsCount":22,"totalTokensCount":23120,"categories":["claude-rule","plugin-manifest","anthropic-skill"],"hasMcp":false,"mcpConfig":null,"found":["CLAUDE.md","packages/joint-cli/README.md","packages/joint-core/README.md","packages/joint-core/demo/custom-embedding/README.md","packages/joint-core/demo/custom-shapes/README.md","packages/joint-core/demo/devs/README.md","packages/joint-core/demo/elk/README.md","packages/joint-core/demo/rough/README.md","packages/joint-core/demo/ts-demo/README.md","packages/joint-core/demo/vuejs/README.md","packages/joint-decorators/README.md","packages/joint-eslint-config/README.md","packages/joint-layout-directed-graph/README.md","packages/joint-layout-elk/README.md","packages/joint-layout-msagl/README.md","packages/joint-mock-svg/README.md","packages/joint-react/CLAUDE.md","packages/joint-react/README.md","packages/joint-router-avoid/README.md","packages/joint-shapes-general-tools/README.md","packages/joint-shapes-general/README.md","packages/joint-vitest-plugin-mock-svg/README.md"],"skills":{"CLAUDE.md":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with\ncode in this repository.\n\n## Project Overview\n\nJointJS is a JavaScript/TypeScript diagramming library for building visual and\nno-code/low-code applications. It's a Yarn workspace monorepo with multiple\npackages.\n\n**Stack:** Node 22.14.0, Yarn 4.18.0, TypeScript 5.8 (5.9 in `joint-react`),\nGrunt (build), QUnit/Karma/Mocha/Jest (testing)\n\n## Common Commands\n\n```bash\n# Install dependencies\nyarn install\n\n# Build distribution files (runs Grunt)\nyarn dist\n\n# Run all tests (QUnit + Jest + TypeScript checks)\nyarn test\n\n# Run specific test types\nyarn test-server       # Server-side tests (Node.js)\nyarn test-client       # Client-side tests (Browser with Karma)\nyarn test-ts           # TypeScript type definition tests\n\n# Linting\nyarn lint              # Check all packages\nyarn lint-fix          # Auto-fix errors\n\n# Build bundles\nyarn build\nyarn build-bundles\n```\n\n### Running Single Tests\n\nFor joint-react Jest tests:\n```bash\ncd packages/joint-react\nyarn jest --testPathPatterns=\"ComponentName\"\n```\n\n## Architecture\n\n### Monorepo Packages (`/packages`)\n\n- **@joint/core** - Main diagramming library (MVC architecture, SVG rendering)\n- **@joint/react** - React bindings and hooks (Rollup + esbuild, Storybook)\n- **@joint/layout-directed-graph** - Graph layout algorithms\n- **@joint/layout-msagl** - Microsoft MSAGL layout integration\n- **@joint/shapes-general** - General-purpose diagram shapes\n- **@joint/eslint-config** - Shared ESLint configuration\n\n### Core Library Structure (`/packages/joint-core/src`)\n\nThe library follows an MVC pattern:\n\n- **`mvc/`** - Base MVC components: Model, View, Collection, Events, Dom\n- **`dia/`** - Diagramming API built on MVC:\n  - `Graph.mjs` - Graph data structure and cell management\n  - `Paper.mjs` - SVG canvas and rendering engine\n  - `Element.mjs` / `ElementView.mjs` - Diagram nodes\n  - `Link.mjs` / `LinkView.mjs` - Connections between elements\n- **`g/`** - Geometry library (points, lines, curves, polygons, matrices)\n- **`V/`** - Vectorizer for SVG manipulation\n- **`connectors/`** - Link path strategies (line, curve, bezier, jumpover)\n- **`routers/`** - Link routing algorithms (manhattan, orthogonal, metro)\n- **`anchors/`** - Connection point anchors on elements\n- **`highlighters/`** - Visual highlighting mechanisms\n- **`linkTools/`**, **`elementTools/`** - Interactive manipulation tools\n- **`shapes/`** - Built-in shapes (standard)\n- **`layout/`** - Built-in layouts (port, port label)\n- **`alg/`** - Graph algorithms (DFS, BFS, shortest path)\n\n### Main Export (`/packages/joint-core/src/core.mjs`)\n\n```javascript\nexport { anchors, linkAnchors, config, connectionPoints, connectionStrategies,\n         connectors, dia, env, highlighters, layout, mvc, routers, setTheme,\n         util, version, linkTools, elementTools, V, Vectorizer, g };\n```\n\n## Test Organization\n\n- **`/packages/joint-core/test/jointjs/`** - QUnit tests for core functionality\n- **`/packages/joint-core/test/jointjs-nodejs/`** - Mocha tests run by\n  `test-server`\n- **`/packages/joint-core/test/geometry/`** - Geometry library tests\n- **`/packages/joint-core/test/vectorizer/`** - SVG vectorizer tests\n- **`/packages/joint-core/test/ts/`**, **`test/ts-exports/`** - TypeScript\n  definition validation\n- **`/packages/joint-react/src/`** - Jest tests with @testing-library/react,\n  colocated in `__tests__` folders\n\n## Changesets\n\nReleasable changes need a changeset in `/.changeset`. Before writing one, read\nthe **Changeset format** section of `CONTRIBUTING.md` - it defines the required\nfrontmatter, the `scope - description` changelog style (`dia.Paper`, `anchors`,\n`<Paper />`, ...), and the backtick/`()` conventions - and follow it exactly.\n\n## Key Patterns\n\n- **Change tracking:** Models emit `change:*` events; `changeId` event triggers\n  only when ID actually changes\n- **Workspace commands:** Use `yarn workspaces foreach --all -tvv run <cmd>`\n  for cross-package operations\n- **TypeScript:** Strict mode enabled; the settings are per package, not\n  repo-wide. `tsconfig.common.json` adds `noUnusedLocals`,\n  `noUnusedParameters` and `noImplicitReturns`, and is used by `joint-cli`,\n  `joint-decorators` and the `shapes-general` packages - `joint-core` and\n  `joint-react` each have their own\n- **ESLint:** Uses flat config format (v9) via `@joint/eslint-config`\n","packages/joint-cli/README.md":"# @joint/cli\n\nCommand-line tool for [JointJS](https://jointjs.com).\n\n## Quick Start\n\n```bash\nnpx @joint/cli list\nnpx @joint/cli download kitchen-sink/js\n```\n\n## Installation\n\n```bash\nnpm install -g @joint/cli\n```\n\nOnce installed globally, the `joint` command is available:\n\n```bash\njoint list\njoint download kitchen-sink/js\n```\n\n## Commands\n\n### `joint list`\n\nList available examples from the [joint-demos](https://github.com/clientIO/joint-demos) repository.\n\n```bash\njoint list\n```\n\n### `joint download <name> [dest]`\n\nDownload an example into the current working directory.\n\n```bash\n# Downloads into ./kitchen-sink-js/\njoint download kitchen-sink/js\n\n# Downloads into ./my-app/\njoint download kitchen-sink/js my-app\n\n# Downloads into the current directory (must be empty or use --force)\njoint download kitchen-sink/js .\n```\n\nIf the destination directory already exists, use `--force` to overwrite:\n\n```bash\njoint download kitchen-sink/js --force\njoint download kitchen-sink/js . --force\n```\n\n## Options\n\n| Option | Description |\n|---|---|\n| `--help`, `-h` | Show help message |\n| `--version`, `-v` | Show version number |\n| `--owner <name>` | GitHub repo owner (default: `clientIO`) |\n| `--branch <name>` | GitHub repo branch (default: `main`) |\n| `--force` | Overwrite existing files when downloading |\n\n### Working with forks\n\nUse `--owner` and `--branch` to list and download examples from a fork:\n\n```bash\njoint list --owner myGitHubUser\njoint download kitchen-sink/js --owner myGitHubUser --branch dev\n```\n\n## Environment Variables\n\n| Variable | Description |\n|---|---|\n| `GITHUB_TOKEN` | Optional GitHub token to avoid API rate limiting |\n\n## Requirements\n\n- Node.js >= 22\n- Git\n","packages/joint-core/README.md":"# JointJS — The JavaScript diagramming library you don't replace at scale\n\n[JointJS](https://jointjs.com) is a proven JavaScript/TypeScript diagramming library for building complex, production-scale diagramming applications.\n\nTeams use it to build AI workflow builders, flowchart and process editors, BPMN modelers, SCADA/HMI and monitoring dashboards, data modeling tools, energy network diagrams, electronic design tools, and org chart builders.\n\nJointJS is framework-agnostic, with first-class support for React.\n\n[![Use Cases](https://user-images.githubusercontent.com/3967880/200360293-808f148c-32af-4f46-bec1-b4ae4e1592a0.jpg)](https://user-images.githubusercontent.com/3967880/200360293-808f148c-32af-4f46-bec1-b4ae4e1592a0.jpg)\n\nFurther information, examples and documentation can be found at [jointjs.com](https://jointjs.com).\n\n- 🚀 [Getting started guide](https://docs.jointjs.com)\n- 💡 Ask a question or share feedback in our [GitHub discussions](https://github.com/clientIO/joint/discussions)\n- 🖊️ [180+ demo apps](https://www.jointjs.com/demos) to kickstart your development\n- 🤖 [MCP server integration](https://mcp.jointjs.com/mcp) for AI coding agents\n- ⚛️ **New:** [JointJS for React](https://www.jointjs.com/react-diagrams) — build diagramming UIs in your React app\n\n## Use cases\n\nJointJS is used to build production diagramming applications across many domains:\n\n| Domain | Examples |\n|---|---|\n| Workflow & process | Flowchart editors, process automation, activity and sequence diagrams |\n| AI workflow builders | Node-based AI pipeline and agent builders, marketing automation studios |\n| BPMN | Business process modelers, swimlanes, Visio BPMN import/export |\n| Data modeling | ER diagrams, database schema designers, data mapping tools |\n| UML & software modeling | Class, sequence, statechart and use-case diagrams |\n| Industrial / SCADA & HMI | P&ID diagrams, monitoring and control dashboards |\n| Energy networks | Power and telecom network diagrams, DWDM circuits |\n| Electronic design | Logic circuits, wiring and cable diagrams |\n| Org charts | Organizational charts, hierarchy and tree builders |\n| Timelines | Project and milestone timelines, Gantt and PERT charts |\n\nExplore all [180+ demo apps](https://www.jointjs.com/demos) that serve as boilerplates for your project.\n\n## Framework integrations\n\nJointJS is framework-agnostic and works with any frontend stack. React has first-class, native support; the others have integration guides and examples:\n\n- [**React**](https://docs.jointjs.com/react) — native components and hooks (JointJS for React)\n- [Angular](https://docs.jointjs.com/learn/integration/angular/)\n- [Vue](https://docs.jointjs.com/learn/integration/vue)\n- [Svelte](https://docs.jointjs.com/learn/integration/svelte)\n- [Salesforce Lightning](https://docs.jointjs.com/learn/integration/lightning)\n\n## Features\n\n- essential diagram elements (rect, circle, ellipse, text, image, path)\n- ready-to-use diagram elements of well-known diagrams (ERD, Org chart, FSA, UML, PN, DEVS, ...)\n- custom diagram elements based on SVG or programmatically rendered\n- connecting diagram elements with links or links with links\n- customizable links, their arrowheads and labels\n- configurable link shapes (anchors, connection points, vertices, routers, connectors)\n- custom element properties and data\n- import/export from/to JSON format\n- customizable element ports (look and position, distributed around shapes or manually positioned)\n- rich graph API (traversal, dfs, bfs, find neighbors, predecessors, elements at point, ...)\n- granular interactivity\n- hierarchical diagrams (containers, embedded elements, child-parent relationships)\n- element & link tools (buttons, status icons, tools to change the shape of links, ...)\n- highlighters (provide visual emphasis to your elements)\n- automatic layouts (arrange the elements and links automatically)\n- highly event driven (react on any event that happens inside the diagram)\n- zoom in/out\n- touch support\n- MVC architecture\n- SVG based\n- ... a lot more\n\n## Using JointJS with AI coding agents\n\nJointJS is built to work well with AI-assisted development.\n\n**MCP server** — connect your AI coding agent (Claude Code, Cursor, and other MCP-compatible tools) to the JointJS Model Context Protocol server so it can search the official docs and demos while you build.\n\n```\nhttps://mcp.jointjs.com/mcp\n```\n\nExposes `search_docs` and `search_demos` so your agent writes correct JointJS code against the real API instead of guessing.\n\n## Supported browsers\n\n- Latest Google Chrome (including mobile)\n- Latest Firefox\n- Latest Safari (including mobile)\n- Latest Microsoft's Edge\n- Latest Opera\n\n## Commercial version\n\nThe open-source library covers core diagramming. **[JointJS+](https://www.jointjs.com/jointjs-plus)** adds production-ready plugins (stencils, inspectors, toolbars, advanced layouts, import/export, and more), professional demo apps (including source code), JointJS for React, and direct support from the team that builds the library. [Start a free 30-day trial.](https://www.jointjs.com/free-trial)\n\n## Development Environment\n\nIf you want to work on *JointJS* locally, use the following guidelines to get started.\n\n### Dependencies\n\nMake sure you have the following dependencies installed on your system:\n\n- [Node.js](https://nodejs.org/)\n- [grunt-cli](http://gruntjs.com/using-the-cli)\n- [git](https://git-scm.com/)\n- [yarn](https://yarnpkg.com/getting-started/install)\n\nThe installation requires Node version 22.14.0, to avoid syntax errors during installation.\n\nMake sure that you are using Yarn version >= 2.0.0, so that you have access to [Yarn workspace ranges](https://yarnpkg.com/features/workspaces#workspace-ranges-workspace) functionality. If you are using [Volta](https://volta.sh/), it will automatically read this restriction from `package.json`.\n\n### Setup\n\nClone this git repository:\n\n```\ngit clone https://github.com/clientIO/joint.git\n```\n\nNavigate to the `joint` directory:\n\n```\ncd joint\n```\n\nInstall all dependencies:\n\n```\nyarn install\n```\n\nGenerate distribution files from the source code:\n\n```\nyarn run dist\n```\n\nYou are now ready to browse our example applications, which combine functionality from multiple JointJS packages:\n\n```\ncd examples\n```\n\nRefer to each application's `README.md` file for additional instructions.\n\nYou can also browse the demo applications of our JointJS Core package:\n\n```\ncd packages/joint-core/demo\n```\n\nMost demos can be run by simply opening the `index.html` file in your browser. Some demos have additional instructions, which you can find in their respective `README.md` files.\n\n### Tests\n\nTo run all tests:\n\n```\nyarn run test\n```\n\nTo run only the server-side tests:\n\n```\nyarn run test-server\n```\n\nTo run only the client-side tests:\n\n```\nyarn run test-client\n```\n\nTo run only TypeScript tests:\n\n```\nyarn run test-ts\n```\n\n### Lint\n\nTo check for linting errors in `src` and `types` directories:\n\n```\nyarn run lint\n```\n\nTo auto fix errors, run eslint for `src` and `types` directories:\n\n```\nyarn run lint-fix\n```\n\n### Code Coverage Reports\n\nTo output a code coverage report in HTML:\n\n```\nyarn run test-coverage\n```\n\nTo output a code coverage report in [lcov format](http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php):\n\n```\nyarn run test-coverage-lcov\n```\n\nThe output for all unit tests will be saved in the `packages/joint-core/coverage` directory.\n\n## Contributors\n\n[![](https://contrib.rocks/image?repo=clientIO/joint)](https://github.com/clientIO/joint/graphs/contributors)\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n","packages/joint-core/demo/custom-embedding/README.md":"# JointJS Custom Embedding Demo\n\n## Setup\n\nUse Yarn to run this demo.\n\nYou need to build *JointJS* first. Navigate to the root folder and run:\n```bash\nyarn install\nyarn run build\n```\n\nThen navigate to this directory and open `index.html` in your browser:\n```bash\nopen index.html\n```\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n","packages/joint-core/demo/custom-shapes/README.md":"# JointJS Custom Shapes Demo\n\n## Setup\n\nUse Yarn to run this demo.\n\nYou need to build *JointJS* first. Navigate to the root folder and run:\n```bash\nyarn install\nyarn run build\n```\n\nNavigate to this directory, then run:\n```bash\nyarn install\nyarn start\n```\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n","packages/joint-core/demo/devs/README.md":"The **Devs** demo introduces a ready-to-use shape with predefined input & output port groups and simplified API.\n\n### joint.shapes.devs.Model\n\nThe Model shape implements simple API on top of the JointJS built-in ports. It splits ports into two semantic groups (**in** and **out**) and adds convenient methods for adding and removing ports.\n\n| Attribute | Description |\n| --- | --- |\n| inPorts | an array of all input ports |\n| outPorts | an array of all output ports |\n\n##### shapes.devs.Model.addInPort\n\n    element.addInPort(port, [opt])\n\nAdd a single port into the \\`inPorts\\` array, where \\`port\\` is a name of the port.\n\n##### shapes.devs.Model.addOutPort\n\n    element.addOutPort(port, [opt])\n\nAdd a single port into the \\`outPorts\\` array, where \\`port\\` is a name of the port.\n\n##### shapes.devs.Model.changeInGroup\n\n    element.changeInGroup(properties, [opt])\n\nChange the settings for the input ports, where \\`properties\\` is an object with a [group configuration](https://resources.jointjs.com/docs/jointjs/v3.7/joint.html#dia.Element.ports.interface). It extends the previous settings with the new configuration by default. Pass `{ rewrite: true }` via `opt` to invalidate the previous settings.\n\n##### shapes.devs.Model.changeOutGroup\n\n    element.changeOutGroup(properties, [opt])\n\nChange the settings for the output ports, where \\`properties\\` is an object with a [group configuration](https://resources.jointjs.com/docs/jointjs/v3.7/joint.html#dia.Element.ports.interface). It extends the previous settings with the new configuration by default. Pass `{ rewrite: true }` via `opt` to invalidate the previous settings.\n\n##### shapes.devs.Model.removeInPort\n\n    element.removeInPort(port, [opt])\n\nRemove a port from an element from the \\`inPorts\\` array, where \\`port\\` is a name of the port.\n\n##### shapes.devs.Model.removeOutPort\n\n    element.removeOutPort(port, [opt])\n\nRemove a port from an element from the \\`outPorts\\` array, where \\`port\\` is a name of the port.\n\n### joint.shapes.devs.Link\n\nThe `devs.Link` extends the `joint.shapes.standard.Link` and changes the link appearance.\n\n#### Example usage\n\n    const shape = new joint.shapes.devs.Model({\n        position: {\n            x: 100,\n            y: 100\n        },\n        inPorts: ['in1', 'in2'],\n        outPorts: ['out1', 'out2']\n    });\n\n    shape.addTo(graph);\n\n    // adding/removing ports dynamically\n    shape.addInPort('in3');\n    shape.removeOutPort('out1').addOutPort('out3');\n\n    const link = new joint.shapes.devs.Link({\n        source: {\n            id: shape.id,\n            port: 'out3'\n        },\n        target: {\n            x: 200,\n            y: 300\n        }\n    });\n    link.addTo(graph);\n\n    // moving the input ports from `left` to `top`\n    shape.changeInGroup({ position: 'top' });\n    // moving the output ports 'right' to 'bottom'\n    shape.changeOutGroup({ position: 'bottom' });\n\n\n#### Hierarchical diagrams\n\nThere are two more shapes designed for hierarchical diagrams as part of the demo - `devs.Atomic` and `devs.Coupled`. They inherit from the `devs.Model` and differ only in the color and size. The purpose of these shapes is to enable you to implement a custom logic in your application based on their type. For instance a `devs.Coupled` can embed `devs.Atomic`'s but not the other way round as shown in the demo below.\n\nWhen working with hierarchical diagrams there is a few methods, that might come in handy.\n\n[`coupled.embed(atomic)`](https://resources.jointjs.com/docs/jointjs/v3.7/joint.html#dia.Element.prototype.embed) that can put the \\`atomic\\` shape into the \\`coupled\\`.\n\n[`coupled.fitToChildren()`](https://resources.jointjs.com/docs/jointjs/v3.7/joint.html#dia.Element.prototype.fitToChildren) that resizes the \\`coupled\\` shape such that all embedded elements are visually contained within it.\n\n[`link.reparent()`](https://resources.jointjs.com/docs/jointjs/v3.7/joint.html#dia.Link.prototype.reparent) that finds the best parent for the \\`link\\` based on the source and target element.\n","packages/joint-core/demo/elk/README.md":"# JointJS Eclipse Layout Kernel Graph Demo\n\n## Setup\n\nUse Yarn to run this demo.\n\nYou need to build *JointJS* first. Navigate to the root folder and run:\n```bash\nyarn install\nyarn run build\n```\n\nNavigate to this directory, then run:\n```bash\nyarn install\nyarn start\n```\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n","packages/joint-core/demo/rough/README.md":"# JointJS RoughJS Demo\n\n## Setup\n\nUse Yarn to run this demo.\n\nYou need to build *JointJS* first. Navigate to the root folder and run:\n```bash\nyarn install\nyarn run build\n```\n\nNavigate to this directory, then run:\n```bash\nyarn install\n```\n\nThen open `index.html` in your browser:\n```bash\nopen index.html\n```\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n","packages/joint-core/demo/ts-demo/README.md":"# JointJS TypeScript Demo\n\n## Setup\n\nUse Yarn to run this demo.\n\nYou need to build *JointJS* first. Navigate to the root folder and run:\n```bash\nyarn install\nyarn run build\n```\n\nNavigate to this directory, then run:\n```bash\nyarn install\nyarn start\n```\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n","packages/joint-core/demo/vuejs/README.md":"# JointJS VueJS Demo\n\n## Setup\n\nUse Yarn to run this demo.\n\nYou need to build *JointJS* first. Navigate to the root folder and run:\n```bash\nyarn install\nyarn run build\n```\n\nNavigate to this directory, then run:\n```bash\nyarn install\n```\n\nThen open `index.html` in your browser:\n```bash\nopen index.html\n```\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n","packages/joint-decorators/README.md":"# JointJS Decorators\n\nECMAScript / TypeScript decorator for defining *[JointJS](https://www.jointjs.com/)* shapes.\n\nThis library fully depends on [JointJS](https://github.com/clientio/joint) (*>=3.5*), so please read its README before using this library.\n\n## Setup\n\nEnable the [experimentalDecorators](https://www.typescriptlang.org/docs/handbook/decorators.html) compiler option in your `tsconfig.json`.\n\nThen install JointJS Decorators from NPM:\n```bash\nnpm i -S @joint/decorators\n```\n\n## License\n\n[Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/)\n\n## Usage\n\nThere are a few class decorators:\n\n- [`@Model`](#Model)\n- [`@View`](#View)\n\nAnd several class member decorators:\n\n- [`@Function`](#Function)\n- [`@On`](#On)\n- [`@SVGAttribute`](#SVGAttribute)\n\n---\n\n## <a id=\"Model\"></a> @Model(options: ModelOptions)\n\nThe decorator allows you to:\n- paste an existing SVG and use it as the model's markup\n- keep your SVG attributes in sync with your model's attributes\n- transform data using functions\n- introduce a new SVG attribute or change the behavior of an existing one\n\n```ts\nimport { dia } from '@joint/core';\nimport { Model } from '@joint/decorators';\n\n@Model({\n```\n```handlebars\n    template: `\n        <g>\n            <rect\n                width=\"calc(w)\"\n                height=\"calc(h)\"\n                :fill=\"{{color}}\"\n                stroke=\"black\"\n            />\n            <text\n                x=\"calc(0.5*w)\"\n                y=\"calc(0.5*h)\"\n                text-anchor=\"middle\"\n                text-vertical-anchor=\"middle\"\n                font-size=\"14\"\n                font-family=\"sans-serif\"\n                fill=\"black\"\n            >{{firstName}} {{lastName}}</text>\n        </g>\n    `,\n```\n```ts\n    attributes: {\n        color: 'red',\n        firstName: 'John',\n        lastName: 'Doe'\n    }\n})\nclass MyElement extends dia.Element {\n\n}\n```\n\n### ModelOptions\n\n| Option | Description | Optional\n|---|---|---|\n| [template](#Model.template) | the SVG string markup of the model | No |\n| [attributes](#Model.attributes) | the default attributes of the model | Yes |\n| [namespace](#Model.namespace) | the namespace for the model class to be added to | Yes |\n\n### <a id=\"Model.template\"></a> **template**\n\nThe decorator uses an SVG-based template syntax that allows you to declaratively bind the rendered DOM to the underlying model's data.\nAll templates are syntactically valid SVG that can be parsed by spec-compliant browsers and SVG parsers.\n\n> While using the SVG XML string in the markup attribute is not recommended (every cell view needs to parse the string and it might affect the performance), the parsing of the decorator's template runs only once per class (translating it into [JSON markup](https://resources.jointjs.com/docs/jointjs/v3.5/joint.html#dia.Cell.markup) and defining event listeners needed for reactivity).\n\n#### <a id=\"Model.template.text-interpolation\"></a> Text Interpolation\n\nThe most basic form of data binding is text interpolation using the \"Mustache\" syntax (double curly braces):\n\n```handlebars\n<text font-size=\"14\">{{ label }}</text>\n```\n\nThe mustache tag will be replaced with the value of the `label` property from the corresponding model's instance. It will also be updated whenever the `label` property changes.\n\n#### <a id=\"Model.template.attributes\"></a> Attributes Binding\n\nTo bind to an SVG attribute to a model's attribute, add a colon symbol (`:`) before the attribute's name.\n\n```handlebars\n<rect :fill=\"color\" />\n```\n\nThe colon symbol, `:`, instructs the decorator to keep the SVG attribute in sync with the model's attribute.\nThe model's `color` value can be set this way.\n\n```ts\nmodel.set('color', 'red');\n```\n\nIf the bound value is `null`, then the SVG attribute will be removed from the rendered element.\n\n```ts\nmodel.set('color', null);\n```\n\nIt's possible to use mustaches inside binding expressions\n    to combine multiple model's attributes into a single result.\n\n```handlebars\n<rect :fill=\"rgb({{red}},{{green}},{{blue}})\" />\n```\n\n#### <a id=\"Model.template.functions\"></a> Calling Functions\n\nThe value of an attribute can be modified with [functions](#Function) before set/display.\n\n```handlebars\n<rect :stroke=\"color\" :fill=\"lighten(color)\"/>\n<text>{{ capitalize(label) }}</text>\n```\n> Functions called inside binding expressions will be called every time the cell view updates, so they should not have any side effects, such as changing data or triggering asynchronous operations.\n\nIt is possible to call a component-exposed method inside a binding expression only if the method is decorated with the [Function](#Function) decorator.\n\nThe function can accept any number of additional arguments. Every such argument shall be parsable with the [JSON.parse](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) function.\n\n```ts\n@Model({\n    template: `\n        <text y=\"10\">{{ maxLength(label1, 20) }}</text>\n        <text y=\"30\">{{ maxLength(label2, 10) }}</text>\n    `\n})\nclass MyElement extends dia.Element {\n\n    @Function()\n    maxLength(value: string, max: number) {\n        return value.substr(0, max);\n    }\n}\n```\n\nMultiple dependencies can be defined using an array.\n\n```handlebars\n<path :d=\"data([param1, param2])\"/>\n```\n\n> The function is run every time one or more attributes in the dependency array are changed.\n\n```ts\n@Model({\n    attributes: {\n        param1: 20,\n        param2: 30\n    },\n    template: `\n        <path :d=\"data([param1, param2])\" stroke=\"white\" fill=\"transparent\" />\n    `\n})\nclass Arrow extends dia.Element {\n    @Function()\n    data(param1: number, param2: number): string {\n        return `\n                M ${param1} 0\n                L calc(w) calc(0.5*h)\n                L ${param1}  calc(h)\n                V calc(h/2 + ${param2 / 2})\n                H 0\n                v -${param2}\n                H ${param1}\n                Z\n        `;\n    }\n}\n```\n\n#### <a id=\"Model.template.selectors\"></a> Selectors\n\nIf you want to modify any of the template attributes programmatically, you must add the `@selector` attribute to the SVG element.\n\n```handlebars\n<rect @selector=\"body\" stroke=\"black\" fill=\"red\">\n```\n\n```ts\nmodel.attr(['body', 'fill'], 'blue');\n```\n\nThe `<g>` wrapper in the template is added automatically and always has a `@selector` equal to `root`. In case you want to add SVG attributes to the root group, wrap the template with one of them.\n\n```handlebars\n<g @selector=\"root\" data-tooltip=\"My Tooltip\">\n    <rect @selector=\"body\" stroke=\"black\" fill=\"red\">\n</g>\n```\n\nTo create selectors pointing to multiple SVG elements at once, use `@group-selector`.\n\n```handlebars\n<rect @group-selector=\"rectangles\" fill=\"red\">\n<rect @group-selector=\"rectangles\" fill=\"blue\">\n<rect @group-selector=\"rectangles\" fill=\"green\">\n```\n\n```ts\n// Change the stroke of all rectangles\nmodel.attr(['rectangles', 'stroke'], 'black');\n```\n\n#### <a id=\"Model.template.caveats\"></a> Caveats\n\nSome JointJS attributes expect their value to be an object ([fill](https://resources.jointjs.com/docs/jointjs/v3.5/joint.html#dia.attributes.textWrap) & [stroke](https://resources.jointjs.com/docs/jointjs/v3.5/joint.html#dia.attributes.stroke) gradient, [filters](https://resources.jointjs.com/docs/jointjs/v3.5/joint.html#dia.attributes.filter), [markers](https://resources.jointjs.com/docs/jointjs/v3.5/joint.html#dia.attributes.sourceMarker) and [textWrap](https://resources.jointjs.com/docs/jointjs/v3.5/joint.html#dia.attributes.textWrap)).\n\nThe solution is to define the property inside the [attributes](#Model.attributes) (mixing the template attributes with explicit model attributes).\n\n```ts\nconst selector = 'label';\n\n@Model({\n    attributes: {\n        title: 'My Title',\n        attrs: {\n            [selector]: {\n                textWrap: {\n                    maxLineCount: 1,\n                    ellipsis: true\n                }\n            }\n        }\n    },\n    template: `\n        <text @selector=\"${selector}\">{{title}}</text>\n    `\n})\nclass MyElement extends dia.Element {\n\n}\n```\n\n### <a id=\"Model.attributes\"></a> **attributes**\n\nThe default attributes of the model. When creating an instance of the model, any unspecified attributes will be set to their default value.\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { Model } from '@joint/decorators';\n\n@Model({\n    attributes: {\n        color: 'red'\n    }\n})\nclass MyElement extends dia.Element {\n\n}\n```\n\nis equivalent to\n\n```ts\nimport { dia, shapes } from '@joint/core';\n\nclass MyElement extends dia.Element {\n\n    defaults() {\n        const attributes = {\n            color: 'red'\n        };\n        return {\n            ...super.defaults,\n            ...attributes,\n            type: 'MyElement'\n        }\n    }\n}\n```\n\n###  <a id=\"Model.namespace\"></a>  **namespace**\n\nSyntactic sugar for adding a model to the namespace.\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { Model } from '@joint/decorators';\n\n@Model({\n    namespace: shapes\n})\nclass MyElement extends dia.Element {\n\n}\n```\n\nis equivalent to\n\n```ts\nimport { dia, shapes } from '@joint/core';\n\nclass MyElement extends dia.Element {\n\n}\n\nObject.assign(shapes, {\n    'MyElement': MyElement\n});\n```\n\n---\n\n## <a id=\"View\"></a> @View(options: ViewOptions)\n\n### ViewOptions\n\n| Option | Description | Optional |\n|---|---|---|\n| namespace | the namespace for the view class to be added to | Yes\n| models | an array of model classes this view is to be used with | Yes\n\nDefine a new cell view, which is automatically used by 2 different models.\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { View } from '@joint/decorators';\n\n@View({\n    namespace: shapes\n    models: [MyElement, MyOtherElement]\n})\nclass MyElementView extends dia.ElementView {\n\n}\n```\n\nis equivalent to\n\n```ts\nimport { dia, shapes } from '@joint/core';\n\nclass MyElementView extends dia.ElementView {\n\n}\n\nObject.assign(shapes, {\n    'MyElementView': MyElementView,\n    'MyOtherElementView': MyElementView\n});\n```\n---\n## <a id=\"Function\"></a> @Function(name?: string)\n\nDefine functions to transform data (e.g strings, amounts, dates) to be used within the [template](#Model.template).\n\n```ts\n@Model({\n    template: `\n        <text>{{ capitalize(name) }}</text>\n    `,\n    attributes: {\n        name: 'john',\n    }\n})\nclass MyElement extends dia.Element {\n\n    @Function()\n    capitalize(value: string) {\n        return value.charAt(0).toUpperCase() + value.slice(1);\n    }\n}\n```\n\n---\n\n## <a id=\"SVGAttribute\"></a> @SVGAttribute(attributeName: string)\n\nIntroduce new SVG attributes or redefine existing ones.\n\n```ts\nimport { dia, g, attributes } from '@joint/core';\nimport { Model, SVGAttribute } from '@joint/decorators';\n\n@Model({\n    attributes: {\n        width: 140,\n        height: 100\n    },\n    template: `\n        <rect\n            line-style=\"dashed\"\n            stroke-width=\"2\"\n        />\n    `,\n})\nclass MyElement extends dia.Element {\n\n    /* `stroke-dasharray` that adjusts based on the current node's `stroke-width` */\n    @SVGAttribute('line-style')\n    setStrokeDasharray(\n        this: dia.CellView,\n        value: string,\n        rect: g.Rect,\n        node: SVGElement,\n        nodeAttrs: attributes.SVGAttributes\n    ) {\n        const { strokeWidth = 1 } = nodeAttrs;\n        let pattern;\n        switch (value) {\n            case 'dashed': {\n                pattern = `${4 * strokeWidth},${2 * strokeWidth}`;\n                break;\n            }\n            case 'dotted': {\n                pattern = `${strokeWidth},${strokeWidth}`;\n                break;\n            }\n            case 'solid': {\n                pattern = 'none';\n                break;\n            }\n            default: {\n                throw new Error('Invalid line-style value.');\n            }\n        }\n        node.setAttribute('stroke-dasharray', pattern);\n    }\n}\n```\n### SVGAttribute function signature\n\n| Argument | Description | Example |\n|---|---|---|\n| value | the right-hand side of the template's attribute | `\"dashed\"` |\n| rect | a rectangle describing the coordinate system the node is rendered in (if no `ref` attribute is in use, the value is the model's bounding box relative to the model's position, otherwise it is the relative bounding box of the node referenced by the `ref` attribute) | `new g.Rect(0, 0, 140, 100)` |\n| node | a rendered DOM [SVGElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement) | `<rect/> as SVGElement` |\n| nodeAttrs | an object with all defined attributes of the node | `{ lineStyle: \"dashed\", strokeWidth: \"2\" }` |\n\n---\n## <a id=\"On\"></a> @On(eventName: string)\n\n Decorate an event handler in the context of the method it refers to.\n\n```ts\nclass MyElementView extends dia.ElementView {\n\n    @On('click')\n    onClick() {\n        console.log('click!', this.model.id);\n    }\n}\n```\n\n---\n\nCopyright © 2013-2026 client IO\n","packages/joint-eslint-config/README.md":"# Shared package for using eslint config.\n\nConfiguring ESLint in multiple projects is a repetitive task, so we created this package to share a common ESLint configuration across all JointJS packages.\n\n### Issue with running `yarn lint`\n\nIf there is an issue with running `yarn lint`, try removing `node_modules/` and re-installing from monorepo root! This should fix the issue, as sometimes, when `yarn install` is used in a monorepo package directly, it **overwrites** default `eslint-configs`.\n\nTo avoid these issues, **use `yarn install` directly from monorepo root folder**, instead of per package.\n","packages/joint-layout-directed-graph/README.md":"# JointJS Directed Graph Layout\n\nA module for automatic layout of directed graphs for *[JointJS](https://www.jointjs.com/)*.\n\nThis library fully depends on [JointJS](https://github.com/clientio/joint) (*>=4.0*), so please read its `README.md` before using this library.\n\n### Tests\n\nGenerate distribution files from the source code:\n```bash\nyarn run dist\n```\n\nTo run all tests:\n```bash\nyarn run test\n```\n\n## License\n\n[Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/)\n\nCopyright © 2013-2026 client IO\n","packages/joint-layout-elk/README.md":"# JointJS ELK Layout\n\nA module for automatic layout of *[JointJS](https://www.jointjs.com)* graphs using the [Eclipse Layout Kernel (ELK)](https://www.eclipse.org/elk/), via its JavaScript port [elkjs](https://github.com/kieler/elkjs).\n\nThis library depends on [JointJS](https://github.com/clientio/joint) (*>=4.0*), so please read its `README.md` before using this library.\n\n`layout()` is a one-off, asynchronous transform: it builds an ELK graph from a JointJS graph (embedded elements become nested containers at any depth, ports and link labels are included), runs ELK's layout algorithm, and writes the result (positions, sizes, link vertices/anchors, label positions) back onto the graph. It does not keep the graph laid out afterwards - call it again after further changes.\n\n## 🚀 Quick Start\n\n### Installation\n\n```bash\nnpm install @joint/layout-elk\n```\n\n#### UMD build (script tags)\n\n`dist/umd/index.js` keeps `@joint/core` and `elkjs` external - load both first: `@joint/core` as the `joint` global, and `elkjs/lib/elk.bundled.js` as the `ELK` global, which `layout()` runs on the main thread. The package then is `joint.layout.ELK`.\n\n```html\n<script src=\"node_modules/@joint/core/dist/joint.min.js\"></script>\n<script src=\"node_modules/elkjs/lib/elk.bundled.js\"></script>\n<script src=\"node_modules/@joint/layout-elk/dist/umd/index.min.js\"></script>\n<script>\n    joint.layout.ELK.layout(graph).then(({ bbox }) => { /* ... */ });\n</script>\n```\n\n### Basic Usage\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { layout } from '@joint/layout-elk';\n\nconst graph = new dia.Graph({}, { cellNamespace: shapes });\nconst paper = new dia.Paper({\n    model: graph,\n    cellViewNamespace: shapes,\n    el: document.getElementById('paper'),\n});\n\nconst rect1 = new shapes.standard.Rectangle({ id: 'a', size: { width: 80, height: 40 }, attrs: { label: { text: 'A' }}});\nconst rect2 = new shapes.standard.Rectangle({ id: 'b', size: { width: 80, height: 40 }, attrs: { label: { text: 'B' }}});\nconst link = new shapes.standard.Link({ source: { id: 'a' }, target: { id: 'b' }});\n\ngraph.addCells([rect1, rect2, link]);\n\nconst { bbox } = await layout(graph, {\n    elkLayoutOptions: {\n        'elk.algorithm': 'layered',\n        'elk.direction': 'RIGHT',\n        'elk.edgeRouting': 'ORTHOGONAL'\n    }\n});\n```\n\n## 📖 API Reference\n\n### `layout(graphOrCells, options?): Promise<LayoutResult>`\n\n- `graphOrCells`: `dia.Graph | dia.Cell[]` - the graph to lay out, or the cells to lay out.\n- `options?`: `LayoutOptions` - layout configuration (see below).\n\nA list of cells is both the selection and the order:\n\n- **Selection** - only the given elements are laid out. An element whose parent isn't given is laid out as a top-level element.\n- **Links** - a link is laid out only if both of the elements it connects are given too. One that isn't - including a link connected to a point, to another link, or one that is in no graph, whose ends cannot be resolved at all - is left out of the layout entirely: no `exportLink` call, no route, and its vertices, anchors and label positions are left as they are.\n- **Order** - the top-level elements follow the order they are given in, and so do each container's children. The links follow their order too, inside each container.\n- Elements and links go in the same list, in any order - a link may come before the elements it connects. A cell listed more than once is laid out once.\n- ELK identifies everything by a string, while a JointJS id may be a string or a number - so two cells, or two ports of one element, whose ids differ only in type (`5` and `'5'`) would share one id in the ELK graph. `layout()` rejects rather than lay one of them out and leave the other where it was.\n\nThe layout is applied in a single `'layout'` batch on the graph the cells belong to, so it emits one combined change rather than one per element, port and link. An element that is in no graph is laid out and positioned like any other, but contributes no change events - which makes it useful as a container that only exists to group others for the layout.\n\n```ts\n// Lay out a selection, with its embedded elements and the links between its elements.\n// Everything else in the graph is left alone.\nawait layout(graph.getSubgraph(selectedElements, { deep: true }));\n```\n\n```ts\ninterface LayoutResult {\n    bbox: g.Rect;      // Tight bounding box of the laid out graph\n    elkGraph: ElkNode;  // The raw ELK layout result (e.g. for junction points)\n}\n```\n\n### `LayoutOptions`\n\n```ts\ninterface LayoutOptions {\n    // The ELK instance to lay out with, e.g. one running in a Web Worker (see `createWorkerElk()` below).\n    elk?: WorkerElk | ELK; // Default: a shared instance running on the main thread\n    // ELK layout options, in either spelling (see \"Layout option values\" below).\n    elkLayoutOptions?: ElkLayoutOptions; // Default: { 'elk.algorithm': 'layered', 'elk.hierarchyHandling': 'INCLUDE_CHILDREN', 'elk.layered.considerModelOrder.portModelOrder': 'true', 'elk.json.edgeCoords': 'ROOT' }\n    // Aborts the layout - `layout()` rejects with the signal's reason and applies nothing.\n    signal?: AbortSignal;\n\n    // Export callbacks (JointJS graph -> ELK graph) - see below.\n    exportElement?: ExportElementCallback;\n    exportPort?: ExportPortCallback;\n    exportPortLabel?: ExportPortLabelCallback;\n    exportLink?: ExportLinkCallback;\n    exportLinkLabel?: ExportLinkLabelCallback;\n\n    // Import callbacks (ELK layout result -> JointJS graph) - see below.\n    setElementAttributes?: SetElementAttributesCallback;\n    setPortAttributes?: SetPortAttributesCallback;\n    setLinkAttributes?: SetLinkAttributesCallback;\n}\n```\n\n### Layout option values\n\nELK takes every option value as a string. This package takes that spelling and the JointJS one, per option, so one object can mix them:\n\n```ts\nelkLayoutOptions: {\n    'elk.spacing.nodeNode': 6,                  // or '6'\n    'elk.rectpacking.orderBySize': true,        // or 'true'\n    'elk.padding': { left: 10, vertical: 40 },  // or '[top=40,left=10,bottom=40,right=0]'\n    'elk.position': { x: 10, y: 20 },           // or '10,20'\n    'elk.nodeLabels.placement': ['INSIDE', 'V_TOP', 'H_LEFT'],  // or 'INSIDE V_TOP H_LEFT'\n    'elk.direction': 'RIGHT'                    // enums are strings either way\n}\n```\n\nThe string spelling stays supported: it is what the ELK reference page linked from each option uses, and what options that arrive as data - a saved diagram, a backend's config - are already shaped like.\n\n- **Numbers and booleans** take `6` / `true` on any option ELK types as one.\n- **Paddings and margins** (`elk.padding`, `elk.margins`, `elk.spacing.portsSurrounding`, `elk.nodeLabels.padding`) take a [`dia.Sides`](https://docs.jointjs.com/api/util/normalizeSides/): a number for every side, or an object naming `top`/`right`/`bottom`/`left`/`horizontal`/`vertical`. **A side the value does not name is `0`**, not the option's own default - ELK's own string parser reads `'[top=40]'` the same way. That default applies only when the option is not set at all.\n- **Points** (`elk.position`, `elk.nodeSize.minimum`, `elk.port.anchor`) take `{ x, y }`, `g.Point` included.\n- **Sets of enum values** (`elk.nodeLabels.placement`, `elk.portLabels.placement`, `elk.contentAlignment`, `elk.nodeSize.constraints`, `elk.nodeSize.options`) take an array of the option's values, typed against its enum - `[]` is the empty set. ELK itself takes them only as one string of the values; elkjs throws on an array. **ELK ignores the whole value, without an error, if any one of its values is not one it knows** - `'INSIDE BOGUS'` does not even apply `INSIDE` - so the array, which is type-checked, is the safer spelling.\n- Anything else goes to ELK as it is, so ELK reports a value it cannot use rather than this package guessing at it.\n\nThe same applies to the `layoutOptions` set on a draft in an [export callback](#export-callbacks).\n\nTo be held to the JointJS spelling alone, annotate your own options with the `Strict` type - there is no flag on this package to switch:\n\n```ts\nimport type { StrictElkLayoutOptions } from '@joint/layout-elk';\n\nconst options: StrictElkLayoutOptions = {\n    'elk.spacing.nodeNode': 6,\n    // Type error: the strict spelling takes `6`, not `'6'`.\n    'elk.layered.spacing.nodeNodeBetweenLayers': '40'\n};\n```\n\n`Native*` is the other direction - ELK's own spelling alone - and is what `toNativeElkNode()` produces. Each has a per-element counterpart: `StrictElkNodeLayoutOptions`, `NativeElkEdgeLayoutOptions`, and so on.\n\n#### `toNativeElkNode(node: ElkNode): NativeElkNode`\n\nReturns a new graph with every layout option written out as the string ELK takes - throughout: the node's own options, its labels', its ports' (and theirs), the edges filed under it (and theirs), and each of its children. The graph passed in is left as it is.\n\n`layout()` does this for you. Reach for it when you build an ELK graph of your own and want to write its options in the JointJS spelling too.\n\n### Seeing what ELK is given\n\n`layout()` builds the ELK graph, runs ELK on it and applies the result back. All three are one step - there is no public way to do them separately, so that what the ids in between stand for stays this package's business.\n\nTo see the graph itself, pass an `elk` of your own: it is any object with `layout()` and `terminate()` (see [Running ELK in a Web Worker](#running-elk-in-a-web-worker)), and it is handed exactly what ELK is handed - every option already written out as a string, so it pastes straight into [elk-live](https://rtsys.informatik.uni-kiel.de/elklive/json.html).\n\n```ts\nimport ELK from 'elkjs/lib/elk.bundled.js';\n\nconst realElk = new ELK();\n\nawait layout(graph, {\n    elk: {\n        layout: (elkGraph) => {\n            console.log(JSON.stringify(elkGraph, null, 2));\n            return realElk.layout(elkGraph);\n        },\n        terminate: () => {}\n    }\n});\n```\n\nThe same hook is how you run ELK somewhere else - a Web Worker, a server, a cache - or change the graph on its way there. `layout()` still applies the result for you.\n\nOne thing to know if you also use [`signal`](#aborting-a-layout): a `terminate()` is how an `elk` says it can be stopped, so `layout()` hands it the signal instead of merely ignoring a late result. The wrapper above cannot stop `elkjs`, so an abort only takes effect once ELK settles. Leave the wrapper out of the abort path, or forward the signal to something that honours it.\n\n### Export callbacks\n\nEach receives an ELK draft already populated with what this package computed for that element/port/link/label, to mutate in place (e.g. to set `elk.*` `layoutOptions`). Returning `false` instead drops it from the ELK graph entirely - for `exportElement`, its whole subtree (embeds, ports, any connected edge) goes with it, and for `exportPort`, the links connected to that port, which ELK can no longer route to. A link left out this way keeps the route it already had.\n\n```ts\ntype ExportElementCallback = (params: { element: dia.Element; elkNode: ElkNodeDraft }) => void | false;\ntype ExportPortCallback = (params: { portId: string; element: dia.Element; elkPort: ElkPortDraft }) => void | false;\ntype ExportPortLabelCallback = (params: { portId: string; element: dia.Element; elkPortLabel: ElkLabelDraft }) => void | false;\ntype ExportLinkCallback = (params: { link: dia.Link; elkEdge: ElkEdgeDraft }) => void | false;\ntype ExportLinkLabelCallback = (params: { link: dia.Link; labelIndex: number; elkEdgeLabel: ElkLabelDraft }) => void | false;\n```\n\n### Import callbacks\n\nEach applies the ELK-computed attributes itself, in place of the package's own default (`element.set(...)`/`element.portProp(...)`/`link.set(...)`) - use one to redirect where the result goes, e.g. into a `transition()`.\n\n```ts\ntype SetElementAttributesCallback = (params: { element: dia.Element; attributes: { position: dia.Point; size?: dia.Size }; elkNode: ElkNode }) => void;\ntype SetPortAttributesCallback = (params: { element: dia.Element; portId: dia.Cell.ID; attributes: { position: { args: dia.Point }; label?: { position: { args: dia.Point } } }; elkPort: ElkPort; hasMoved: boolean }) => void;\ntype SetLinkAttributesCallback = (params: { link: dia.Link; attributes: { vertices: dia.Point[]; source?: dia.Link.EndJSON; target?: dia.Link.EndJSON; labels?: dia.Link.Label[] }; elkEdge: ElkExtendedEdge }) => void;\n```\n\n### Running ELK in a Web Worker\n\nBy default, `layout()` runs ELK on the main thread - nothing to set up with a bundler or in Node, and it works anywhere (browsers, Node/SSR, tests, and the UMD build once `elkjs` is loaded - see \"UMD build\" above). ELK blocks the page while it runs, though: a few milliseconds for a small graph, but up to seconds for one with thousands of elements. To keep the page responsive, run ELK in a Web Worker instead - start one with `createWorkerElk()` and pass it to `layout()` as `elk`:\n\n```ts\nimport { layout, createWorkerElk } from '@joint/layout-elk';\n\nconst elk = createWorkerElk(() => new Worker(new URL('@joint/layout-elk/worker', import.meta.url), { type: 'module' }));\n\nawait layout(graph, { elk });\n\n// Once no longer needed.\nelk.terminate();\n```\n\n#### `createWorkerElk(createWorker: () => Worker): WorkerElk`\n\n`createWorker` starts the worker - running `@joint/layout-elk/worker`, the package's worker script (ELK's own, `elkjs/lib/elk-worker.min.js`). Starting it is up to you, since only your bundler knows where the script ends up:\n\n- **webpack 5, Vite** (dev server and builds) - `new Worker(new URL('@joint/layout-elk/worker', import.meta.url), { type: 'module' })`, as above. Both bundle the worker script as a file of its own. With webpack, keep `output.publicPath` at `'auto'` (the default), so the file is found wherever the app is served from.\n- **Vite** - also `import ElkWorker from '@joint/layout-elk/worker?worker'`, then `createWorkerElk(() => new ElkWorker())`.\n- **No bundler** (e.g. the UMD build) - serve a copy of `elkjs/lib/elk-worker.min.js`, then `createWorkerElk(() => new Worker('/path/to/elk-worker.min.js'))`.\n\nThe returned `WorkerElk`:\n\n- **Starts the worker on its first layout**, then lays out every graph in it, one at a time - share one instance between layouts.\n- **Stops an aborted layout** (see `signal` below) by terminating the worker, if it is busy with it - a new worker takes over the layouts still waiting.\n- **Rejects the layout the worker crashes during** (e.g. out of memory) - a new worker takes over the layouts still waiting.\n- **Rejects every layout when the worker fails to load** (e.g. its script isn't served where `createWorker` starts it from) or can't be started (e.g. a CSP `worker-src` that blocks it) - with an error saying so, rather than running ELK on the main thread. The next layout tries again with a new worker.\n- **`terminate()`** terminates the worker - layouts not settled yet are rejected, and a later layout starts a new worker.\n\nAny other ELK instance works as `elk` too, e.g. `elkjs`'s own `new ELK({ workerUrl })` (`elkjs/lib/elk-api.js`) - without stopping an aborted layout, though (see below).\n\n### Aborting a layout\n\n`layout()` is asynchronous, so the graph may change while ELK is still computing - pass an `AbortSignal` to drop a layout that is no longer wanted (or takes too long). An aborted `layout()` rejects with the signal's reason (an `AbortError` `DOMException` by default) and applies nothing to the graph.\n\n```ts\nlet controller: AbortController | undefined;\n\nasync function runLayout() {\n    // Only the latest layout is applied.\n    controller?.abort();\n    controller = new AbortController();\n    try {\n        await layout(graph, { signal: controller.signal });\n    } catch (error) {\n        if ((error as Error).name !== 'AbortError') throw error;\n    }\n}\n\n// Give up after 5 seconds.\nawait layout(graph, { signal: AbortSignal.timeout(5000) });\n```\n\nELK can't stop a layout in progress, so a layout a `createWorkerElk()` worker is busy with is stopped by terminating the worker - a new one takes over the layouts still waiting. A layout on the main thread, or in any other `elk` instance, keeps running - only its result is ignored.\n\n## ⚠️ Caveats & Known Limitations\n\n- **Edge coordinates are graph-absolute** - `layout()` sets `elk.json.edgeCoords: 'ROOT'`, so ELK returns every edge's route points and labels relative to the root, whichever container the edge is in, and the default import applies them as they are. Overriding it (e.g. `'CONTAINER'`) is allowed, but the default import then misplaces vertices, end anchors and labels of edges inside containers - convert them yourself in `setLinkAttributes` (from `elkEdge`). The same applies to the raw `elkGraph` in `layout()`'s result.\n- **Node labels are not supported** - ELK's node-label placement assumes labels are layout participants, whereas JointJS labels are attrs inside the shape. Link labels are supported.\n- **Element rotation is not taken into account** - an ELK node is a box with a position and a size, with no rotation, so an element is laid out by the geometry it would have at `angle: 0` and ELK reserves the wrong area for a rotated one. `layout()` warns the first time it sees one.\n- **Ports keep their JointJS-computed position by default** - every element with ports is exported with `elk.portConstraints: 'FIXED_POS'`, so ELK keeps each port where the element's port groups place it and edges route to/from that exact spot. Opt into ELK repositioning/reordering them by overriding it (e.g. `'FIXED_SIDE'`/`'FREE'`) in `exportElement` - setting it in `elkLayoutOptions` has no effect, since the per-node value takes precedence. `layout()` also defaults `elk.layered.considerModelOrder.portModelOrder` to `'true'`, so that a port freed this way keeps the order of the element's `ports.items`; override it in `elkLayoutOptions` to let ELK reorder them to reduce edge crossings.\n- **Asynchronous** - unlike `@joint/layout-directed-graph`, `layout()` returns a `Promise`, since `elkjs` computes layouts asynchronously - even on the main thread.\n- **Main thread by default** - without an `elk` option, ELK runs on the main thread and blocks the page while it runs - see \"Running ELK in a Web Worker\" above. In the ESM build, the main-thread copy of ELK (`elkjs/lib/elk.bundled.js`) is imported dynamically, so bundlers split it into a chunk of its own, only loaded by the first layout without an `elk` option. The UMD build can't load a chunk - it uses the `ELK` global instead, which the page has to load first (see \"UMD build\" above).\n\n## 📄 License\n\n[Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/)\n\nThe code in this package is licensed under the Mozilla Public License 2.0, same as the rest of JointJS. It contains no ELK code: it only calls ELK through its API, and its TypeScript option types link to [ELK's option reference](https://eclipse.dev/elk/reference/options.html) instead of reproducing it.\n\nIt depends on [`elkjs`](https://github.com/kieler/elkjs), which is dual-licensed under the [Eclipse Public License 2.0](https://github.com/kieler/elkjs/blob/master/LICENSE.md) or GPL-3.0-or-later (`EPL-2.0 OR GPL-3.0-or-later`) - you can use it under the EPL-2.0. `elkjs` is installed as a regular dependency and kept external to this package's own builds (ESM and UMD) - it is never copied or inlined into them. An application that bundles this package does ship `elkjs` code though (the main-thread chunk, and the Web Worker file if it uses `@joint/layout-elk/worker`), under that license: keep `elkjs`'s license notice with it (e.g. with your bundler's license extraction), and note where its source is available (it is published on [GitHub](https://github.com/kieler/elkjs) and [npm](https://www.npmjs.com/package/elkjs)).\n\nCopyright © 2013-2026 client IO\n","packages/joint-layout-msagl/README.md":"# JointJS MSAGL Layout\n\nA module for automatic layout of *[JointJS](https://www.jointjs.com)* graphs using the [Microsoft Automatic Graph Layout (MSAGL)](https://github.com/microsoft/msagljs).\n\nThis library fully depends on [JointJS](https://github.com/clientio/joint) (*>=4.0*), so please read its `README.md` before using this library.\n\nThis library provides hierarchical (Sugiyama) layout with support for nested subgraphs and multiple edge routing modes.\n\n## 🚀 Quick Start\n\n### Installation\n\n```bash\nnpm install @joint/layout-msagl\n```\n\n### Basic Usage\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { layout, EdgeRoutingMode } from '@joint/layout-msagl';\n\n// Create your JointJS graph\nconst graph = new dia.Graph({}, { cellNamespace: shapes });\nconst paper = new dia.Paper({\n    model: graph,\n    cellViewNamespace: shapes,\n    el: document.getElementById('paper'),\n});\nconst rect1 = new shapes.standard.Rectangle({\n    id: 'a',\n    size: { width: 80, height: 40 },\n    attrs: { label: { text: 'A' } }\n});\nconst rect2 = new shapes.standard.Rectangle({\n    id: 'b',\n    size: { width: 80, height: 40 },\n    attrs: { label: { text: 'B' } }\n});\nconst link = new shapes.standard.Link({ source: { id: 'a' }, target: { id: 'b' } });\n\ngraph.addCells([rect1, rect2, link]);\n\n// Or with custom options\nlayout(graph, {\n    layerSeparation: 60,\n    nodeSeparation: 30,\n    edgeRoutingMode: EdgeRoutingMode.Rectilinear\n});\n```\n\n## 📖 API Reference\n\n### Main Functions\n\n#### `layout(graphOrCells, options?): LayoutResult`\n\nMain layout function with full customization options.\n\n**Parameters:**\n- `graphOrCells`: `dia.Graph | dia.Cell[]` - JointJS graph or array of cells to layout\n- `options?`: `Options` - Layout configuration (see below)\n\n**Returns:** `LayoutResult`\n\n```ts\ninterface LayoutResult {\n    bbox: g.Rect;           // Tight bounding box of the laid out graph\n    msGraph: Graph;         // Underlying MSAGL graph data structure\n    msGeomGraph: GeomGraph; // Geometry instance used by MSAGL to layout graph\n}\n```\n\n### Options Interface\n\n```ts\n// Callback type definitions\ntype GetSizeCallback = (element: dia.Element) => dia.Size;\ntype GetLabelSizeCallback = (cell: dia.Cell) => dia.Size | undefined;\ntype SetPositionCallback = (element: dia.Element, position: dia.Point) => void;\ntype SetVerticesCallback = (link: dia.Link, vertices: dia.Point[]) => void;\ntype SetLabelsCallback = (link: dia.Link, labelBBox: dia.BBox, points: dia.Point[]) => void;\ntype SetAnchorCallback = (link: dia.Link, linkEndPoint: dia.Point, bbox: dia.BBox, endType: 'source' | 'target') => void;\ntype SetClusterSizeCallback = (element: dia.Element, size: dia.Size) => void;\n\ninterface Options {\n    // Layout direction\n    layerDirection?: LayerDirectionEnum; // Default: LayerDirectionEnum.TB\n    // Spacing\n    layerSeparation?: number; // Default: 40\n    nodeSeparation?: number; // Default: 20\n    // Edge routing\n    edgeRoutingMode?: EdgeRoutingMode; // Default: EdgeRoutingMode.Rectilinear\n    polylinePadding?: number; // Default: 1\n    // Rectilinear self-loop offset\n    // Vertical pixel offset applied to the two inner vertices of a self-edge\n    // when edgeRoutingMode is Rectilinear. Default: 10\n    rectilinearSelfEdgeOffset?: number;\n    // Grid and origin\n    gridSize?: number; // Default: 0\n    x?: number; // Default: 0\n    y?: number; // Default: 0\n    clusterPadding?: dia.Sides; // Default: 10\n    // Element sizing callbacks\n    getSize?: GetSizeCallback; // Default: element.size()\n    getLabelSize?: GetLabelSizeCallback; // Default: element.get('labelSize')\n    // Callbacks for customizing how the layout is applied\n    setPosition?: SetPositionCallback; // Default: element.position(x, y)\n    setVertices?: boolean | SetVerticesCallback; // Default: true\n    setLabels?: boolean | SetLabelsCallback; // Default: true\n    setAnchor?: boolean | SetAnchorCallback; // Default: true\n    setClusterSize?: SetClusterSizeCallback; // Default: element.size(size)\n}\n```\n\n### Enums\n\n#### `LayerDirectionEnum`\n- `TB` - Top to Bottom (default)\n- `BT` - Bottom to Top\n- `LR` - Left to Right\n- `RL` - Right to Left\n\n#### `EdgeRoutingMode`\n- `Rectilinear` - Orthogonal edges with right angles (default)\n- `SplineBundling` - Smooth curved edges\n\n## 🎯 Examples\n\n### Basic Hierarchical Layout\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { layout, LayerDirectionEnum, EdgeRoutingMode } from '@joint/layout-msagl';\n\nconst graph = new dia.Graph({}, { cellNamespace: shapes });\nconst paper = new dia.Paper({\n    model: graph,\n    cellViewNamespace: shapes,\n    el: document.getElementById('paper'),\n});\n\n// Create elements\nconst elements = ['A', 'B', 'C', 'D'].map(id =>\n    new shapes.standard.Rectangle({\n        id,\n        size: { width: 80, height: 40 },\n        attrs: { label: { text: id } }\n    })\n);\n\n// Create links to form a hierarchy: A -> B, A -> C, B -> D, C -> D\nconst links = [\n    new shapes.standard.Link({ source: { id: 'A' }, target: { id: 'B' } }),\n    new shapes.standard.Link({ source: { id: 'A' }, target: { id: 'C' } }),\n    new shapes.standard.Link({ source: { id: 'B' }, target: { id: 'D' } }),\n    new shapes.standard.Link({ source: { id: 'C' }, target: { id: 'D' } })\n];\n\ngraph.addCells([...elements, ...links]);\n\n// Apply layout\nconst bbox = layout(graph, {\n    layerDirection: LayerDirectionEnum.TB,\n    layerSeparation: 50,\n    nodeSeparation: 30,\n    edgeRoutingMode: EdgeRoutingMode.Rectilinear\n});\n```\n\n### Nested Subgraphs\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { layout } from '@joint/layout-msagl';\n\nconst graph = new dia.Graph({}, { cellNamespace: shapes });\nconst paper = new dia.Paper({\n    model: graph,\n    cellViewNamespace: shapes,\n    el: document.getElementById('paper'),\n});\n\nconst parent = new shapes.standard.Rectangle({\n    id: 'parent',\n    size: { width: 200, height: 150 }\n});\n\nconst child1 = new shapes.standard.Rectangle({\n    id: 'child1',\n    size: { width: 60, height: 30 }\n});\n\nconst child2 = new shapes.standard.Rectangle({\n    id: 'child2',\n    size: { width: 60, height: 30 }\n});\n\n// Embed children in parent\nparent.embed(child1);\nparent.embed(child2);\n\n// Add internal link between children\nconst internalLink = new shapes.standard.Link({\n    source: { id: 'child1' },\n    target: { id: 'child2' }\n});\n\ngraph.addCells([parent, child1, child2, internalLink]);\n\n// Layout will automatically handle the subgraph\nlayout(graph);\n```\n\n### Edge Routing with Labels\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { layout } from '@joint/layout-msagl';\n\nconst graph = new dia.Graph({}, { cellNamespace: shapes });\nconst paper = new dia.Paper({\n    model: graph,\n    cellViewNamespace: shapes,\n    el: document.getElementById('paper'),\n});\n\nconst rect1 = new shapes.standard.Rectangle({\n    id: 'A',\n    size: { width: 80, height: 40 },\n    attrs: { label: { text: 'A' } }\n});\n\nconst rect2 = new shapes.standard.Rectangle({\n    id: 'B',\n    size: { width: 80, height: 40 },\n    attrs: { label: { text: 'B' } }\n});\n\n// Set label size on links for proper label positioning\nconst link = new shapes.standard.Link({\n    source: { id: 'A' },\n    target: { id: 'B' },\n    labels: [\n        {\n            attrs: {\n                text: {\n                    text: 'Label'\n                }\n            }\n        }\n    ],\n    labelSize: { width: 80, height: 20 } // Required for label layout\n});\n\ngraph.addCells([rect1, rect2, link]);\n\nlayout(graph);\n```\n\n### Custom Callbacks\n\n```ts\nimport { dia, util } from '@joint/core';\n\nlayout(graph, {\n    // Custom positioning with animation\n    setPosition: (element: dia.Element, position: dia.Point) => {\n        element.transition('position', position, {\n            duration: 500,\n            timingFunction: util.timing.cubic,\n            valueFunction: util.interpolate.object\n        });\n    }\n});\n```\n\n## 🔧 Advanced Features\n\n### Label Positioning\n\nThe layout automatically positions labels on links and reserves space for element labels in subgraphs. Label sizing is controlled by the `getLabelSize` callback:\n\n**Default behavior** - reads from the `labelSize` property:\n```ts\nlink.set('labelSize', { width: 100, height: 20 });\nparentElement.set('labelSize', { width: 120, height: 25 });\n```\n\n**Custom label sizing** - provide your own callback:\n```ts\nlayout(graph, {\n    getLabelSize: (cell) => {\n        if (cell.isLink()) {\n            // Calculate link label size based on text content\n            const text = cell.label(0)?.attrs?.text?.text || '';\n            return { width: text.length * 8, height: 20 };\n        } else {\n            // Calculate element label size\n            const text = cell.attr('label/text') || '';\n            return { width: text.length * 10, height: 25 };\n        }\n    }\n});\n```\n\nIf `getLabelSize` returns `undefined` for a cell, no space is reserved for its label.\n\n## ⚠️ Caveats & Known Limitations\n\n- **Rectilinear self‑loops** – When `edgeRoutingMode` is set to `Rectilinear`, self‑edges use a configurable vertical offset controlled by `rectilinearSelfEdgeOffset` (default `10`). This is a stop‑gap until upstream `msagljs` provides native rectilinear self‑loop geometry.\n- **Subgraph resizing** – Parent elements that embed other elements are resized by the layout to tightly pack all their children.\n- **Subgraph layout direction** - Layout inside subgraphs is always set to `TB` (Top-to-Bottom) direction, as other directions can cause layout issues.\n- **Link labels in subgraphs** – Link labels within subgraphs may be positioned incorrectly, despite the layout correctly reserving space for them.\n- **Obstacle padding (first‑bend distance)** – MSAGL exposes an `edgeRoutingSettings.Padding` option intended to keep edges a minimum distance away from obstacles (nodes) and thus control where the first bend occurs. In msagljs this setting is currently not working.\n- **ID handling** - MSAGL expects IDs to be string values so `id: 1` and `id: '1'` are considered the same.\n\n## 📄 License\n\n[Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/)\n\nCopyright © 2013-2026 client IO\n","packages/joint-mock-svg/README.md":"# JointJS Mock SVG\n\nMocks of those SVG globals that JSDOM does not implement but *[JointJS](https://www.jointjs.com/)* relies on, including `SVGAngle`, `createSVGMatrix()`, `getBBox()`, and `transform.baseVal`.\n\nThis package is **test-runner agnostic**: it depends on no test runner and uses no spies, so it can be consumed from any of them.\n\n## Usage\n\nImport it once, before your tests run, in a DOM environment:\n\n```js\nimport '@joint/mock-svg';\n```\n\nThe import is side-effectful: it installs the mocks on `globalThis` and exports no value. Note that a DOM is required - without it the import fails.\n\nWhere that import belongs depends on your runner - it needs to run before any test file touches the SVG globals, which usually means a setup-file option rather than an import in the test itself.\n\n## TypeScript\n\n`getBBox()`, `getScreenCTM()`, `getComputedTextLength()` and `transform` are mocked on `SVGElement`, one level wider than where DOM types (`lib.DOM`) declare them, because JSDOM makes `<rect>`, `<path>` and `<text>` plain `SVGElement`s. Reading one of them off `SVGElement.prototype` - like when spying on the mocks - therefore needs the exported `MockedSVGElement` type. For example:\n\n```ts\nimport type { MockedSVGElement } from '@joint/mock-svg';\n\njest.spyOn(SVGElement.prototype as MockedSVGElement, 'getBBox')\n    .mockReturnValue({ x: 0, y: 0, width: 100, height: 50 } as DOMRect);\n```\n\n## License\n\nThe *@joint/mock-svg* library is licensed under the [MIT License](https://github.com/clientIO/joint/blob/master/packages/joint-mock-svg/LICENSE).\n\nCopyright © 2013-2026 client IO\n","packages/joint-react/CLAUDE.md":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with\ncode in this repository.\n\n## Project Overview\n\n`@joint/react` is the React bindings library for JointJS. It provides React\ncomponents and hooks for building diagramming applications with JointJS,\nrendering diagram elements as React components via SVG foreignObject portals.\n\n**Stack:** TypeScript 5.9, React 19 (peer: >=18), Rollup + esbuild (build), Vite\n(storybook/dev), Jest 30 + @testing-library/react (testing), ESLint 9 flat\nconfig, Storybook 10\n\n## Common Commands\n\n```bash\n# Build (Rollup + esbuild → dist/cjs, dist/esm, dist/types)\nyarn build\n\n# Run all checks (typecheck + knip + jest, once against React 19 and once\n# against React 18)\nyarn test\n\n# Run individual checks\nyarn typecheck              # tsc --noEmit\nyarn lint                   # ESLint\nyarn lint-fix               # ESLint with auto-fix\nyarn jest                   # Jest tests only\n\n# Run a single test file\nyarn jest --testPathPatterns=\"use-elements\"\n\n# Run tests in watch mode\nyarn jest --watch\n\n# Storybook (dev server on :6006)\nyarn storybook\n\n# Build storybook\nyarn build-storybook\n```\n\n## Changesets\n\nReleasable changes need a changeset - the format is defined in the **Changeset\nformat** section of the repository's `CONTRIBUTING.md`. Two things are specific\nto this package:\n\n- The changelog scope is the exported component or hook rather than a JointJS\n  namespace - components as JSX tags, hooks by name:\n\n  ```markdown\n  <Paper /> - fix the visual grid to redraw reactively when `drawGrid` changes\n  useCells - fix ghost cells reported after `resetCells()`\n  ```\n\n- `@joint/react` is versioned independently. It is not part of the\n  `@joint/core` linked group, and because it depends on core via `workspace:^`,\n  a `@joint/core` minor release does not drag it along - only a major does.\n\n# Clean Code & Style Guidelines\n\n## General\n\n- Prefer clear, explicit code over clever one-liners. Maintainability > brevity.\n- When editing existing code, preserve the existing style unless I explicitly\n  ask for a refactor.\n- Leave code cleaner than you found it. Continuously refactor small things\n  instead of accumulating technical debt.\n\n## Constants Over Magic Numbers\n\n- Replace hard-coded values with named constants.\n- Use descriptive constant names that explain the value's purpose.\n- Keep constants at the top of the file\n\n## Meaningful Names\n\n- Variables, functions, and classes should reveal their purpose.\n- Names should explain **why** something exists and **how** it is used.\n- Avoid abbreviations unless they are truly universal (e.g. `id`, `URL`,\n  `HTML`).\n- Boolean names must be self-descriptive and read like conditions:\n  - Use prefixes: `is`, `has`, `should`, `can`, `must`, `needs` (e.g. `isOpen`,\n    `hasError`, `shouldRetry`).\n  - Avoid generic names like `flag`, `ok`, `state`, `enabled` without context.\n- Function names should describe behavior, not implementation details.\n\n## File Naming (TypeScript / JavaScript)\n\n- All `.ts`, `.tsx`, `.js`, `.jsx` files must use **kebab-case**:\n  - Examples: `user-profile.ts`, `something-table.tsx`\n- React component files:\n  - File name: kebab-case (e.g. `user-profile-card.tsx`).\n  - Component name inside: `PascalCase` (e.g. `UserProfileCard`).\n\n## Smart Comments\n\n- Do **not** comment on what the code does if the code can be made\n  self-explanatory instead.\n- Prefer to improve the code so it reads clearly without comments.\n- Use comments to explain:\n  - Why something is done in a non-obvious way.\n  - Important constraints, assumptions, or business rules.\n  - Non-obvious side effects.\n- Document APIs, complex algorithms, and tricky edge cases.\n\n## Single Responsibility\n\n- Each function should do exactly one thing.\n- Functions should be small and focused.\n- If a function needs a long comment to explain what it does, consider\n  splitting it into smaller functions.\n- JSDoc is mandatory for all exported functions.\n\n## DRY (Don't Repeat Yourself)\n\n- Extract repeated code into reusable functions or utilities.\n- Share common logic through proper abstractions.\n- Maintain single sources of truth for configuration, constants, and shared\n  logic.\n\n## Clean Structure\n\n- Keep related code together.\n- Organize code in a logical hierarchy (modules, domains, features).\n- Use consistent file and folder naming conventions.\n- Avoid \"god\" files that own too many responsibilities.\n\n## Encapsulation\n\n- Hide implementation details behind clear interfaces.\n- Expose only what is needed from a module.\n- Move nested conditionals or complex logic into well-named helper functions.\n\n## TypeScript\n\n- Never use `any` in new code:\n  - Prefer proper types, generics, unions, or `unknown` + narrowing.\n  - If `any` is unavoidable at a boundary (e.g. 3rd-party library), isolate it\n    and add a clear `TODO` explaining why.\n- Write `strict`-friendly code:\n  - No implicit `any`.\n  - Avoid sloppy or unnecessary `as` casts.\n  - Avoid `as any` and non-null assertions (`!`) unless absolutely necessary;\n    justify them with a short comment.\n- Use `interface`/`type` aliases for data structures instead of loose objects.\n- Keep function signatures explicit and well-typed, especially for exported\n  functions and public APIs.\n- Prefer readonly/immutable data where reasonable (e.g. `readonly` props, avoid\n  mutating function arguments).\n\n## Performance Mindset\n\n- Avoid unnecessary allocations and copies:\n  - No pointless spreading (`{ ...obj }`) or `JSON.parse(JSON.stringify(...))`\n    in hot paths.\n- Avoid creating new functions/objects inside tight loops or very frequently\n  rendered components unless memoized.\n- Use `useMemo` / `useCallback` **only** when they prevent real, recurring work\n  or re-renders, not everywhere by default.\n- Be careful not to introduce extra re-renders by changing prop shapes\n  unnecessarily.\n- Prefer algorithmic improvements and proper data structures over\n  micro-optimizations when performance is a concern.\n\n## Code Quality Maintenance\n\n- Refactor continuously in small, safe steps.\n- Fix technical debt early when you touch a piece of code.\n- Prefer explicit, predictable behavior over clever tricks.\n- Keep the codebase internally consistent.\n\n## Testing & Safety\n\n- For non-trivial logic changes, prefer adding or updating tests.\n- Write tests before fixing bugs when possible (regression tests).\n- Keep tests readable, deterministic, and maintainable.\n- Test edge cases, error conditions, and boundary values.\n- Avoid relying on implementation details; test behavior and contracts.\n- Performance is must have, so test it.\n","packages/joint-react/README.md":"<p align=\"center\">\n  <img src=\"https://cdn.prod.website-files.com/63061d4ee85b5a18644f221c/633045c1d726c7116dcbe582_JJS_logo.svg\" alt=\"JointJS\" height=\"56\" />\n</p>\n\n<h1 align=\"center\">JointJS for React</h1>\n\n<p align=\"center\">\n  <strong>Production-scale diagramming for React.</strong><br/>\n  A first-class React integration for <a href=\"https://jointjs.com\">JointJS</a> — real components and hooks for building complex diagramming apps, with full control over the data model, rendering, and interactions, using the React patterns you already know.\n</p>\n\n<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/@joint/react\"><img src=\"https://img.shields.io/npm/v/@joint/react?style=flat-square&color=0EA5E9\" alt=\"npm\" /></a>\n  <a href=\"https://www.npmjs.com/package/@joint/react\"><img src=\"https://img.shields.io/npm/types/@joint/react?style=flat-square&color=3178C6\" alt=\"types\" /></a>\n  <a href=\"https://www.npmjs.com/package/@joint/react\"><img src=\"https://img.shields.io/npm/dm/@joint/react?style=flat-square&color=F59E0B\" alt=\"downloads\" /></a>\n  <a href=\"./LICENSE\"><img src=\"https://img.shields.io/npm/l/@joint/react?style=flat-square&color=10B981\" alt=\"license\" /></a>\n</p>\n\n<p align=\"center\">\n  🚀 <a href=\"#quick-start\">Quick start</a> ·\n  🧩 <a href=\"https://www.jointjs.com/demos/ai-workflow-builder\">Live Example</a> ·\n  📖 <a href=\"#learn-more\">Learn more</a>\n</p>\n\n[![Use Cases](https://github.com/user-attachments/assets/fc6de738-3fad-48f7-99aa-ba8c3729d9b1)](https://github.com/user-attachments/assets/fc6de738-3fad-48f7-99aa-ba8c3729d9b1)\n\n---\n\nBuilt on the proven JointJS core, you get a mature diagramming UI library and an idiomatic React layer in one. Like JointJS, it's open source.\n\n## Why JointJS for React\n\n- **A real data model.** A serializable graph you can query, transform, and persist — the backbone for complex, stateful diagram applications, not just visual nodes.\n- **Full control.** Complete control over how diagrams render and behave — shapes, ports, links, routing, and custom interactions.\n- **Production-grade UX.** The interaction details end users expect come built in: connection snapping, link routing that avoids obstacles, element and link tools, highlighters for hover and selection states. Good UX is designed into every feature, not left for you to assemble.\n- **Performance at scale.** Smooth interaction on large graphs with thousands of nodes.\n- **Idiomatic React.** Real components and hooks that fit naturally into your existing React app and tooling.\n\n## Installation\n\n```bash\nnpm install @joint/react     # or pnpm add / yarn add / bun add\n```\n\nPeer dependency: **React 18 or 19**.\n\n## Quick start\n\n```tsx\nimport { GraphProvider, Paper, HTMLBox, type CellRecord } from '@joint/react';\n\ninterface NodeData {\n  label: string;\n}\n\nconst initialCells: ReadonlyArray<CellRecord<NodeData>> = [\n  { id: '1', type: 'element', position: { x: 40, y: 40 }, data: { label: 'Hello' } },\n  { id: '2', type: 'element', position: { x: 280, y: 180 }, data: { label: 'World' } },\n  { id: '1 → 2', type: 'link', source: { id: '1' }, target: { id: '2' } },\n];\n\nfunction renderElement(data: NodeData) {\n  return <HTMLBox>{data.label}</HTMLBox>;\n}\n\nexport default function Diagram() {\n  return (\n    <GraphProvider initialCells={initialCells}>\n      <Paper style={{ width: '100%', height: 600 }} renderElement={renderElement} />\n    </GraphProvider>\n  );\n}\n```\n\n`GraphProvider` holds the graph state, `Paper` renders it, and `renderElement` turns each node's data into a React component. See [Learn more](#learn-more) for the full getting-started guide.\n\n## When is JointJS for React the right choice?\n\nJointJS for React is built for production-scale diagramming — applications where the diagram is a core part of the product and has to handle real complexity: a rich data model, large graphs, domain-specific shapes, and deep control over interactions and rendering.\n\nProduction-scale is not only about node count. Most workflow tools top out at tens of nodes, yet their users still expect a polished editing experience: precise connections, clear feedback on hover and selection, links that route cleanly around obstacles. JointJS for React treats that end-user UX as part of the library, so you ship it instead of building it.\n\nIt's designed to be the layer you build on as your requirements grow, from the first prototype through to a mature, scaling application.\n\n## Use cases\n\nJointJS for React is used to build production diagramming applications across many domains:\n\n| Domain | Examples |\n|---|---|\n| Workflow & process | Flowchart editors, process automation, activity and sequence diagrams |\n| AI workflow builders | Node-based AI pipeline and agent builders, marketing automation studios |\n| BPMN | Business process modelers, swimlanes, Visio BPMN import/export |\n| Data modeling | ER diagrams, database schema design, data lineage and mapping |\n| UML & software modeling | Class, sequence, statechart and use-case diagrams |\n| Industrial / SCADA & HMI | SCADA interfaces, HMI control panels, P&ID and process diagrams |\n| Energy networks | Power grid and substation diagrams, telecom and fiber (DWDM) network design |\n| Electronic design | Circuit and logic design, wiring and cable harness diagrams, schematic capture |\n| Org charts | Org charts, reporting hierarchies, team and headcount planning |\n| Timelines | Project and milestone timelines, Gantt and PERT charts |\n\nExplore the [demos](https://www.jointjs.com/demos) to see them in action.\n\n## Learn more\n\n- 📖 [Learn JointJS for React](https://docs.jointjs.com/react)\n- 🧾 [API reference](https://docs.jointjs.com/api-react)\n- 🖼️ [Examples](https://www.jointjs.com/demos)\n- 📚 [Storybook](https://react.jointjs.com/learn/)\n- 🤖 [MCP server](#using-jointjs-for-react-with-ai-coding-agents)\n- 💬 [Ask a question or share feedback](https://github.com/clientIO/joint/discussions)\n\n## Using JointJS for React with AI coding agents\n\nJointJS is built to work well with AI-assisted development.\n\n**MCP server** — connect your AI coding agent (Claude Code, Cursor, and other MCP-compatible tools) to the JointJS Model Context Protocol server so it can search the official docs and demos while you build:\n\n```\nhttps://mcp.jointjs.com/mcp\n```\n\nIt exposes `search_docs` and `search_demos`, so your agent writes correct JointJS for React code against the real API instead of guessing.\n\n## Commercial version\n\nThe open-source library covers core diagramming. **JointJS+ for React** is the commercial extension that takes the end-user experience further with production-ready plugins — stencils, toolbars, snaplines, keyboard shortcuts, undo/redo, advanced layouts, import/export, and more — plus professional demo apps (including source code) and direct support from the team that builds the library. [Start a free 30-day trial.](https://www.jointjs.com/free-trial)\n\n## License\n\nJointJS for React is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n","packages/joint-router-avoid/README.md":"# JointJS Avoid Router\n\nA module that routes *[JointJS](https://www.jointjs.com)* links using [libavoid](https://github.com/mjwybrow/libavoid), a C++ library for automatic, obstacle-avoiding orthogonal connector routing, compiled to WebAssembly via [libavoid-js](https://github.com/Aksem/libavoid-js).\n\nThis library fully depends on [JointJS](https://github.com/clientio/joint) (*>=4.0*), so please read its `README.md` before using this library.\n\nlibavoid maintains a single incremental router shared by the whole graph: it tracks every element as an obstacle and every link as a connector, and reroutes affected connectors whenever obstacles move. This package wraps that behavior in a `RouterService` that listens to a `dia.Graph` and, whenever libavoid computes a new route for a link, applies it directly to that link's `vertices` and source/target anchors - there is no `router: { name: ... }` attribute to set on links; once a link is connected to two elements on a routed graph, it is routed automatically. See [`examples/avoid-router-ts`](https://github.com/clientIO/joint/tree/master/examples/avoid-router-ts) for a full demo.\n\n## 🚀 Quick Start\n\n### Installation\n\n```bash\nnpm install @joint/router-avoid\n```\n\n### Basic Usage\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { initAvoidRouter } from '@joint/router-avoid';\n\nconst graph = new dia.Graph({}, { cellNamespace: shapes });\nconst paper = new dia.Paper({\n    model: graph,\n    cellViewNamespace: shapes,\n    el: document.getElementById('paper'),\n});\n\n// Loads the libavoid-js WebAssembly module (if not already loaded), then\n// registers every element/link currently in the graph as an avoid\n// obstacle/connector and keeps them in sync with future graph changes.\nconst routerService = await initAvoidRouter(graph, {\n    shapeBufferDistance: 20,\n    idealNudgingDistance: 10,\n});\n\n// ... add elements and links to the graph - links connected to two\n// elements are picked up and routed automatically, no `router`\n// attribute needed:\n// new shapes.standard.Link({ source: { id: a.id }, target: { id: b.id } });\n\n// Stop routing this graph and release the underlying resources\n// (e.g. terminate a Worker thread) when they're no longer needed.\n// routerService.destroy();\n```\n\n`initAvoidRouter()` resolves once the module is loaded and the current graph content has been registered with libavoid; the routes themselves are still computed asynchronously and applied as they come in - listen for the `link:routed` event to know when a specific link's route has settled (see below).\n\nLoaded as a script tag rather than via ESM, the same functions are available as `joint.routers.avoid.initAvoidRouter` / `joint.routers.avoid.loadAvoidRouter` on the UMD build.\n\n## 📖 API Reference\n\n### `loadAvoidRouter(filePath?): Promise<void>`\n\nLoads the `libavoid-js` WebAssembly module. `initAvoidRouter()` calls this automatically if the module isn't loaded yet, so calling it directly is only needed to preload the module ahead of time. Accepts an optional path to the `libavoid.wasm` file if it is not served from its default location. Safe to call multiple times - later calls resolve once the first load completes.\n\n### `initAvoidRouter(graph, options?): Promise<RouterService>`\n\nLoads the module (via `loadAvoidRouter()`, if needed) and creates a `RouterService` for `graph`. The returned service is not started - call `start()` to keep the graph continuously routed, or `routeAll()`/`routeSubgraph()` for a one-shot routing pass.\n\n- `graph`: `dia.Graph` - the graph to route.\n- `options?`: `InitAvoidOptions`\n\n```ts\ninterface InitAvoidOptions {\n    shapeBufferDistance?: number;  // Default: 10 - spacing added around shapes when computing obstacles, and used as the fallback route's margin around elements\n    idealNudgingDistance?: number; // Default: 5 - spacing used to nudge apart overlapping connector segments\n    worker?: boolean | WorkerOptions; // Default: false - run libavoid inside a Web Worker instead of the main thread; pass an object to configure the Worker\n    libavoidFilePath?: string;     // Forwarded to loadAvoidRouter()\n    trackLink?: (params: { link: dia.Link }) => boolean;          // Which links to route; return false to exclude one. Default: all\n    trackElement?: (params: { element: dia.Element }) => boolean; // Which elements to track as avoid obstacles; return false to exclude one. Default: all\n    // First refusal on a link avoid can't route because one of its ends\n    // isn't connected to a cell ('unconnected'), is connected to another\n    // link ('unsupported'), or is connected to an element excluded via\n    // `trackElement` ('untracked'). Return true to take over routing that\n    // link yourself; otherwise the built-in `rightAngle`-based fallback\n    // route is applied.\n    interceptUnroutableLink?: (params: { link: dia.Link, reason: 'unconnected' | 'untracked' | 'unsupported' }) => boolean;\n    // Override how computed route attributes are applied to a link, e.g. to\n    // route the update through a command manager. Default: link.set()\n    setRouteAttributes?: (params: { link, attributes, origin, routing, unroutableReason }) => void;\n    changeFlag?: string;           // Default: 'avoidRouter' - name of the opt flag marking this router's own link.set() calls\n}\n\ninterface WorkerOptions {\n    debounceTime?: number; // Default: 100 - ms the Worker waits after the last received change before recomputing routes in a single batch; 0 applies every change immediately\n}\n```\n\n### `RouterService` (returned by `initAvoidRouter()`)\n\nThe object responsible for keeping libavoid's internal obstacle/connector graph in sync with the `dia.Graph`, applying computed routes to links, and emitting routing events. It mixes in JointJS's `mvc.Events` (`on`/`off`/`trigger`).\n\n- `start()` / `stop()` - start/stop listening to graph changes (added/removed cells, moved/resized elements, reconnected links). `start()` also syncs every cell the graph already holds.\n- `isStarted` - whether the service is currently listening to its graph.\n- `routeAll()` - one-shot: routes every cell currently in the graph, resolving once all routes are applied. No graph listener is attached. Throws while started. Resolves with a `RoutingResult` - `{ status: 'done' }`, or `{ status: 'cancelled' }` if the pass was interrupted by `destroy()`. Overlapping calls are queued and run one after another.\n- `routeSubgraph(cells)` - one-shot: routes only `cells`, independently of the rest of the graph (cells outside the array are neither routed nor obstacles). Throws while started. Resolves with the same `RoutingResult` as `routeAll()`.\n- `changeFlag` - the `opt` flag name marking this router's own `link.set()` calls, for filtering them out of your own `change` listeners.\n- `destroy()` - stops routing the graph, cancels open routing cycles, and releases the resources held by the service and its provider (e.g. terminates a Worker thread). Do not use the instance afterwards.\n\n**Events:**\n\n- `link:routing` `(link)` - emitted when a link's route is (re-)computing.\n- `link:routed` `(link, { origin, reason })` - emitted once a link's route has been applied. `origin` is `'avoid'` or `'fallback'`; `reason` is set when the fallback route was applied because the link was unroutable.\n- `link:routing:cancelled` `(link)` - emitted when a link with an open `link:routing` cycle becomes unroutable (e.g. disconnected) before libavoid produced a route for it.\n- `idle` `()` - emitted when there are no more open routing cycles.\n\n## ⚠️ Caveats & Known Limitations\n\n- **Asynchronous setup** - `initAvoidRouter()` loads the WebAssembly module and performs the initial graph sync asynchronously; it must be awaited before the graph is considered routed.\n- **Bundler configuration** - `libavoid-js` ships its logic and the `libavoid.wasm` binary as separate files, and `worker: true` additionally loads this package's own worker script as a module. Consuming applications are responsible for ensuring both are served alongside the rest of the bundle (e.g. via a copy plugin for your bundler of choice).\n- **Fallback routing** - libavoid does not expose a way to check whether a computed route is valid, so a heuristic is used. When the route is deemed invalid, or when a link isn't connected to elements on both ends, the built-in `rightAngle`-based fallback route is applied instead (unless `interceptUnroutableLink` takes over).\n- **One `RouterService` per graph** - constructing more than one `RouterService` for the same `dia.Graph` results in both instances applying routes to the same links; call `destroy()` on a previous instance before creating a new one for the same graph.\n- **Custom vertices (checkpoints)** are not currently supported.\n\n## 📄 License\n\nThe code in this package is licensed under the [Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/), same as the rest of JointJS.\n\nIt depends on [`libavoid-js`](https://www.npmjs.com/package/libavoid-js), a WebAssembly build of [libavoid](https://github.com/mjwybrow/adaptagrams), which is licensed under the **GNU LGPL 2.1 or later** - applications shipping this package also ship the `libavoid.wasm` binary under that license. Keep the binary a separately served file (as described under bundler configuration above); don't configure your bundler to inline it into your application bundle.\n\nCopyright © 2013-2026 client IO\n\nThis package depends on [`libavoid-js`](https://github.com/Aksem/libavoid-js), which is licensed under the [LGPL-2.1-or-later license](https://github.com/Aksem/libavoid-js?tab=LGPL-2.1-1-ov-file#readme).\n","packages/joint-shapes-general-tools/README.md":"# JointJS General Shapes Tools\n\nA collection of ECMAScript / TypeScript tools for use with general *[JointJS](https://www.jointjs.com/)* shapes.\n\nThis library fully depends on [JointJS](https://github.com/clientio/joint) (*>=3.5*), so please read its `README.md` before using this library.\n\n## License\n\n[Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/)\n\nCopyright © 2013-2026 client IO\n","packages/joint-shapes-general/README.md":"# JointJS General Shapes\n\nA collection of general ECMAScript / TypeScript shapes for *[JointJS](https://www.jointjs.com/)*.\n\nThis library fully depends on [JointJS](https://github.com/clientio/joint) (*>=3.5*), so please read its `README.md` before using this library.\n\n## License\n\n[Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/)\n\nCopyright © 2013-2026 client IO\n","packages/joint-vitest-plugin-mock-svg/README.md":"# JointJS Mock SVG plugin for Vitest\n\nA Vitest plugin to mock SVG methods not implemented in JSDOM used by *[JointJS](https://www.jointjs.com/)*.\n\n## Usage\n\nImport the plugin in your `vitest.config.js`:\n\n```js\nimport { defineConfig } from 'vitest/config';\nimport mockSVG from '@joint/vitest-plugin-mock-svg'\n\nexport default defineConfig({\n  plugins: [mockSVG()],\n})\n```\n\nThe mocks themselves come from [`@joint/mock-svg`](https://github.com/clientIO/joint/tree/master/packages/joint-mock-svg) as plain functions. If your test needs to assert on one of them, or change what the mock returns, use `vi.spyOn()`.\n\n### Changing what a mock returns\n\nThe default `getBBox()` returns an all-zero rect, which leaves `checkVisibility()` false and `util.breakText()` with nothing to measure. If your test needs one, give the mock a size object as a return value:\n\n```js\nvi.spyOn(SVGElement.prototype, 'getBBox')\n  .mockReturnValue({ x: 0, y: 0, width: 100, height: 20 });\n```\n\n`mockRestore()`, `vi.restoreAllMocks()` and `restoreMocks: true` all put the mock back afterwards, so an override stays local to its test and the suite needs no `beforeEach` to reinstall anything.\n\nThe same spy also records calls. This lets you assert whether JointJS measured anything at all:\n\n```js\nconst getBBox = vi.spyOn(SVGElement.prototype, 'getBBox');\nrender(<MyDiagram />);\nexpect(getBBox).toHaveBeenCalled();\n```\n\n`vi.spyOn` reaches nested members, as well - for example `vi.spyOn(SVGElement.prototype.transform.baseVal, 'appendItem')`.\n\nTo swap a mock implementation permanently, reassign it:\n\n```js\nSVGElement.prototype.getComputedTextLength = () => 42;\n```\n\nRestore the previous value yourself if the change should not outlive the file.\n\n### Driving `ResizeObserver`\n\nThe mocked observer accepts a callback and never calls it. To deliver entries, or to assert on `observe`, provide your own:\n\n```js\nglobalThis.ResizeObserver = class {\n  observe = vi.fn();\n  unobserve = vi.fn();\n  disconnect = vi.fn();\n  constructor(callback) { this.callback = callback; }\n  resize(entries) { this.callback(entries, this); }\n};\n```\n\n## TypeScript\n\n`getBBox()`, `getScreenCTM()`, `getComputedTextLength()` and `transform` are mocked on `SVGElement`, one level wider than where DOM types (`lib.DOM`) declare them, because JSDOM makes `<rect>`, `<path>` and `<text>` plain `SVGElement`s. Reading one of them off `SVGElement.prototype` - as every example above does - therefore needs the re-exported `MockedSVGElement` type. For example:\n\n```ts\nimport type { MockedSVGElement } from '@joint/vitest-plugin-mock-svg';\n\nvi.spyOn(SVGElement.prototype as MockedSVGElement, 'getBBox')\n  .mockReturnValue({ x: 0, y: 0, width: 100, height: 20 } as DOMRect);\n```\n\n## License\n\nThe *@joint/vitest-plugin-mock-svg* library is licensed under the [MIT License](https://github.com/clientIO/joint/blob/master/packages/joint-vitest-plugin-mock-svg/LICENSE).\n\nCopyright © 2013-2026 client IO\n"},"files":{"CLAUDE.md":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with\ncode in this repository.\n\n## Project Overview\n\nJointJS is a JavaScript/TypeScript diagramming library for building visual and\nno-code/low-code applications. It's a Yarn workspace monorepo with multiple\npackages.\n\n**Stack:** Node 22.14.0, Yarn 4.18.0, TypeScript 5.8 (5.9 in `joint-react`),\nGrunt (build), QUnit/Karma/Mocha/Jest (testing)\n\n## Common Commands\n\n```bash\n# Install dependencies\nyarn install\n\n# Build distribution files (runs Grunt)\nyarn dist\n\n# Run all tests (QUnit + Jest + TypeScript checks)\nyarn test\n\n# Run specific test types\nyarn test-server       # Server-side tests (Node.js)\nyarn test-client       # Client-side tests (Browser with Karma)\nyarn test-ts           # TypeScript type definition tests\n\n# Linting\nyarn lint              # Check all packages\nyarn lint-fix          # Auto-fix errors\n\n# Build bundles\nyarn build\nyarn build-bundles\n```\n\n### Running Single Tests\n\nFor joint-react Jest tests:\n```bash\ncd packages/joint-react\nyarn jest --testPathPatterns=\"ComponentName\"\n```\n\n## Architecture\n\n### Monorepo Packages (`/packages`)\n\n- **@joint/core** - Main diagramming library (MVC architecture, SVG rendering)\n- **@joint/react** - React bindings and hooks (Rollup + esbuild, Storybook)\n- **@joint/layout-directed-graph** - Graph layout algorithms\n- **@joint/layout-msagl** - Microsoft MSAGL layout integration\n- **@joint/shapes-general** - General-purpose diagram shapes\n- **@joint/eslint-config** - Shared ESLint configuration\n\n### Core Library Structure (`/packages/joint-core/src`)\n\nThe library follows an MVC pattern:\n\n- **`mvc/`** - Base MVC components: Model, View, Collection, Events, Dom\n- **`dia/`** - Diagramming API built on MVC:\n  - `Graph.mjs` - Graph data structure and cell management\n  - `Paper.mjs` - SVG canvas and rendering engine\n  - `Element.mjs` / `ElementView.mjs` - Diagram nodes\n  - `Link.mjs` / `LinkView.mjs` - Connections between elements\n- **`g/`** - Geometry library (points, lines, curves, polygons, matrices)\n- **`V/`** - Vectorizer for SVG manipulation\n- **`connectors/`** - Link path strategies (line, curve, bezier, jumpover)\n- **`routers/`** - Link routing algorithms (manhattan, orthogonal, metro)\n- **`anchors/`** - Connection point anchors on elements\n- **`highlighters/`** - Visual highlighting mechanisms\n- **`linkTools/`**, **`elementTools/`** - Interactive manipulation tools\n- **`shapes/`** - Built-in shapes (standard)\n- **`layout/`** - Built-in layouts (port, port label)\n- **`alg/`** - Graph algorithms (DFS, BFS, shortest path)\n\n### Main Export (`/packages/joint-core/src/core.mjs`)\n\n```javascript\nexport { anchors, linkAnchors, config, connectionPoints, connectionStrategies,\n         connectors, dia, env, highlighters, layout, mvc, routers, setTheme,\n         util, version, linkTools, elementTools, V, Vectorizer, g };\n```\n\n## Test Organization\n\n- **`/packages/joint-core/test/jointjs/`** - QUnit tests for core functionality\n- **`/packages/joint-core/test/jointjs-nodejs/`** - Mocha tests run by\n  `test-server`\n- **`/packages/joint-core/test/geometry/`** - Geometry library tests\n- **`/packages/joint-core/test/vectorizer/`** - SVG vectorizer tests\n- **`/packages/joint-core/test/ts/`**, **`test/ts-exports/`** - TypeScript\n  definition validation\n- **`/packages/joint-react/src/`** - Jest tests with @testing-library/react,\n  colocated in `__tests__` folders\n\n## Changesets\n\nReleasable changes need a changeset in `/.changeset`. Before writing one, read\nthe **Changeset format** section of `CONTRIBUTING.md` - it defines the required\nfrontmatter, the `scope - description` changelog style (`dia.Paper`, `anchors`,\n`<Paper />`, ...), and the backtick/`()` conventions - and follow it exactly.\n\n## Key Patterns\n\n- **Change tracking:** Models emit `change:*` events; `changeId` event triggers\n  only when ID actually changes\n- **Workspace commands:** Use `yarn workspaces foreach --all -tvv run <cmd>`\n  for cross-package operations\n- **TypeScript:** Strict mode enabled; the settings are per package, not\n  repo-wide. `tsconfig.common.json` adds `noUnusedLocals`,\n  `noUnusedParameters` and `noImplicitReturns`, and is used by `joint-cli`,\n  `joint-decorators` and the `shapes-general` packages - `joint-core` and\n  `joint-react` each have their own\n- **ESLint:** Uses flat config format (v9) via `@joint/eslint-config`\n","packages/joint-cli/README.md":"# @joint/cli\n\nCommand-line tool for [JointJS](https://jointjs.com).\n\n## Quick Start\n\n```bash\nnpx @joint/cli list\nnpx @joint/cli download kitchen-sink/js\n```\n\n## Installation\n\n```bash\nnpm install -g @joint/cli\n```\n\nOnce installed globally, the `joint` command is available:\n\n```bash\njoint list\njoint download kitchen-sink/js\n```\n\n## Commands\n\n### `joint list`\n\nList available examples from the [joint-demos](https://github.com/clientIO/joint-demos) repository.\n\n```bash\njoint list\n```\n\n### `joint download <name> [dest]`\n\nDownload an example into the current working directory.\n\n```bash\n# Downloads into ./kitchen-sink-js/\njoint download kitchen-sink/js\n\n# Downloads into ./my-app/\njoint download kitchen-sink/js my-app\n\n# Downloads into the current directory (must be empty or use --force)\njoint download kitchen-sink/js .\n```\n\nIf the destination directory already exists, use `--force` to overwrite:\n\n```bash\njoint download kitchen-sink/js --force\njoint download kitchen-sink/js . --force\n```\n\n## Options\n\n| Option | Description |\n|---|---|\n| `--help`, `-h` | Show help message |\n| `--version`, `-v` | Show version number |\n| `--owner <name>` | GitHub repo owner (default: `clientIO`) |\n| `--branch <name>` | GitHub repo branch (default: `main`) |\n| `--force` | Overwrite existing files when downloading |\n\n### Working with forks\n\nUse `--owner` and `--branch` to list and download examples from a fork:\n\n```bash\njoint list --owner myGitHubUser\njoint download kitchen-sink/js --owner myGitHubUser --branch dev\n```\n\n## Environment Variables\n\n| Variable | Description |\n|---|---|\n| `GITHUB_TOKEN` | Optional GitHub token to avoid API rate limiting |\n\n## Requirements\n\n- Node.js >= 22\n- Git\n","packages/joint-core/README.md":"# JointJS — The JavaScript diagramming library you don't replace at scale\n\n[JointJS](https://jointjs.com) is a proven JavaScript/TypeScript diagramming library for building complex, production-scale diagramming applications.\n\nTeams use it to build AI workflow builders, flowchart and process editors, BPMN modelers, SCADA/HMI and monitoring dashboards, data modeling tools, energy network diagrams, electronic design tools, and org chart builders.\n\nJointJS is framework-agnostic, with first-class support for React.\n\n[![Use Cases](https://user-images.githubusercontent.com/3967880/200360293-808f148c-32af-4f46-bec1-b4ae4e1592a0.jpg)](https://user-images.githubusercontent.com/3967880/200360293-808f148c-32af-4f46-bec1-b4ae4e1592a0.jpg)\n\nFurther information, examples and documentation can be found at [jointjs.com](https://jointjs.com).\n\n- 🚀 [Getting started guide](https://docs.jointjs.com)\n- 💡 Ask a question or share feedback in our [GitHub discussions](https://github.com/clientIO/joint/discussions)\n- 🖊️ [180+ demo apps](https://www.jointjs.com/demos) to kickstart your development\n- 🤖 [MCP server integration](https://mcp.jointjs.com/mcp) for AI coding agents\n- ⚛️ **New:** [JointJS for React](https://www.jointjs.com/react-diagrams) — build diagramming UIs in your React app\n\n## Use cases\n\nJointJS is used to build production diagramming applications across many domains:\n\n| Domain | Examples |\n|---|---|\n| Workflow & process | Flowchart editors, process automation, activity and sequence diagrams |\n| AI workflow builders | Node-based AI pipeline and agent builders, marketing automation studios |\n| BPMN | Business process modelers, swimlanes, Visio BPMN import/export |\n| Data modeling | ER diagrams, database schema designers, data mapping tools |\n| UML & software modeling | Class, sequence, statechart and use-case diagrams |\n| Industrial / SCADA & HMI | P&ID diagrams, monitoring and control dashboards |\n| Energy networks | Power and telecom network diagrams, DWDM circuits |\n| Electronic design | Logic circuits, wiring and cable diagrams |\n| Org charts | Organizational charts, hierarchy and tree builders |\n| Timelines | Project and milestone timelines, Gantt and PERT charts |\n\nExplore all [180+ demo apps](https://www.jointjs.com/demos) that serve as boilerplates for your project.\n\n## Framework integrations\n\nJointJS is framework-agnostic and works with any frontend stack. React has first-class, native support; the others have integration guides and examples:\n\n- [**React**](https://docs.jointjs.com/react) — native components and hooks (JointJS for React)\n- [Angular](https://docs.jointjs.com/learn/integration/angular/)\n- [Vue](https://docs.jointjs.com/learn/integration/vue)\n- [Svelte](https://docs.jointjs.com/learn/integration/svelte)\n- [Salesforce Lightning](https://docs.jointjs.com/learn/integration/lightning)\n\n## Features\n\n- essential diagram elements (rect, circle, ellipse, text, image, path)\n- ready-to-use diagram elements of well-known diagrams (ERD, Org chart, FSA, UML, PN, DEVS, ...)\n- custom diagram elements based on SVG or programmatically rendered\n- connecting diagram elements with links or links with links\n- customizable links, their arrowheads and labels\n- configurable link shapes (anchors, connection points, vertices, routers, connectors)\n- custom element properties and data\n- import/export from/to JSON format\n- customizable element ports (look and position, distributed around shapes or manually positioned)\n- rich graph API (traversal, dfs, bfs, find neighbors, predecessors, elements at point, ...)\n- granular interactivity\n- hierarchical diagrams (containers, embedded elements, child-parent relationships)\n- element & link tools (buttons, status icons, tools to change the shape of links, ...)\n- highlighters (provide visual emphasis to your elements)\n- automatic layouts (arrange the elements and links automatically)\n- highly event driven (react on any event that happens inside the diagram)\n- zoom in/out\n- touch support\n- MVC architecture\n- SVG based\n- ... a lot more\n\n## Using JointJS with AI coding agents\n\nJointJS is built to work well with AI-assisted development.\n\n**MCP server** — connect your AI coding agent (Claude Code, Cursor, and other MCP-compatible tools) to the JointJS Model Context Protocol server so it can search the official docs and demos while you build.\n\n```\nhttps://mcp.jointjs.com/mcp\n```\n\nExposes `search_docs` and `search_demos` so your agent writes correct JointJS code against the real API instead of guessing.\n\n## Supported browsers\n\n- Latest Google Chrome (including mobile)\n- Latest Firefox\n- Latest Safari (including mobile)\n- Latest Microsoft's Edge\n- Latest Opera\n\n## Commercial version\n\nThe open-source library covers core diagramming. **[JointJS+](https://www.jointjs.com/jointjs-plus)** adds production-ready plugins (stencils, inspectors, toolbars, advanced layouts, import/export, and more), professional demo apps (including source code), JointJS for React, and direct support from the team that builds the library. [Start a free 30-day trial.](https://www.jointjs.com/free-trial)\n\n## Development Environment\n\nIf you want to work on *JointJS* locally, use the following guidelines to get started.\n\n### Dependencies\n\nMake sure you have the following dependencies installed on your system:\n\n- [Node.js](https://nodejs.org/)\n- [grunt-cli](http://gruntjs.com/using-the-cli)\n- [git](https://git-scm.com/)\n- [yarn](https://yarnpkg.com/getting-started/install)\n\nThe installation requires Node version 22.14.0, to avoid syntax errors during installation.\n\nMake sure that you are using Yarn version >= 2.0.0, so that you have access to [Yarn workspace ranges](https://yarnpkg.com/features/workspaces#workspace-ranges-workspace) functionality. If you are using [Volta](https://volta.sh/), it will automatically read this restriction from `package.json`.\n\n### Setup\n\nClone this git repository:\n\n```\ngit clone https://github.com/clientIO/joint.git\n```\n\nNavigate to the `joint` directory:\n\n```\ncd joint\n```\n\nInstall all dependencies:\n\n```\nyarn install\n```\n\nGenerate distribution files from the source code:\n\n```\nyarn run dist\n```\n\nYou are now ready to browse our example applications, which combine functionality from multiple JointJS packages:\n\n```\ncd examples\n```\n\nRefer to each application's `README.md` file for additional instructions.\n\nYou can also browse the demo applications of our JointJS Core package:\n\n```\ncd packages/joint-core/demo\n```\n\nMost demos can be run by simply opening the `index.html` file in your browser. Some demos have additional instructions, which you can find in their respective `README.md` files.\n\n### Tests\n\nTo run all tests:\n\n```\nyarn run test\n```\n\nTo run only the server-side tests:\n\n```\nyarn run test-server\n```\n\nTo run only the client-side tests:\n\n```\nyarn run test-client\n```\n\nTo run only TypeScript tests:\n\n```\nyarn run test-ts\n```\n\n### Lint\n\nTo check for linting errors in `src` and `types` directories:\n\n```\nyarn run lint\n```\n\nTo auto fix errors, run eslint for `src` and `types` directories:\n\n```\nyarn run lint-fix\n```\n\n### Code Coverage Reports\n\nTo output a code coverage report in HTML:\n\n```\nyarn run test-coverage\n```\n\nTo output a code coverage report in [lcov format](http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php):\n\n```\nyarn run test-coverage-lcov\n```\n\nThe output for all unit tests will be saved in the `packages/joint-core/coverage` directory.\n\n## Contributors\n\n[![](https://contrib.rocks/image?repo=clientIO/joint)](https://github.com/clientIO/joint/graphs/contributors)\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n","packages/joint-core/demo/custom-embedding/README.md":"# JointJS Custom Embedding Demo\n\n## Setup\n\nUse Yarn to run this demo.\n\nYou need to build *JointJS* first. Navigate to the root folder and run:\n```bash\nyarn install\nyarn run build\n```\n\nThen navigate to this directory and open `index.html` in your browser:\n```bash\nopen index.html\n```\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n","packages/joint-core/demo/custom-shapes/README.md":"# JointJS Custom Shapes Demo\n\n## Setup\n\nUse Yarn to run this demo.\n\nYou need to build *JointJS* first. Navigate to the root folder and run:\n```bash\nyarn install\nyarn run build\n```\n\nNavigate to this directory, then run:\n```bash\nyarn install\nyarn start\n```\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n","packages/joint-core/demo/devs/README.md":"The **Devs** demo introduces a ready-to-use shape with predefined input & output port groups and simplified API.\n\n### joint.shapes.devs.Model\n\nThe Model shape implements simple API on top of the JointJS built-in ports. It splits ports into two semantic groups (**in** and **out**) and adds convenient methods for adding and removing ports.\n\n| Attribute | Description |\n| --- | --- |\n| inPorts | an array of all input ports |\n| outPorts | an array of all output ports |\n\n##### shapes.devs.Model.addInPort\n\n    element.addInPort(port, [opt])\n\nAdd a single port into the \\`inPorts\\` array, where \\`port\\` is a name of the port.\n\n##### shapes.devs.Model.addOutPort\n\n    element.addOutPort(port, [opt])\n\nAdd a single port into the \\`outPorts\\` array, where \\`port\\` is a name of the port.\n\n##### shapes.devs.Model.changeInGroup\n\n    element.changeInGroup(properties, [opt])\n\nChange the settings for the input ports, where \\`properties\\` is an object with a [group configuration](https://resources.jointjs.com/docs/jointjs/v3.7/joint.html#dia.Element.ports.interface). It extends the previous settings with the new configuration by default. Pass `{ rewrite: true }` via `opt` to invalidate the previous settings.\n\n##### shapes.devs.Model.changeOutGroup\n\n    element.changeOutGroup(properties, [opt])\n\nChange the settings for the output ports, where \\`properties\\` is an object with a [group configuration](https://resources.jointjs.com/docs/jointjs/v3.7/joint.html#dia.Element.ports.interface). It extends the previous settings with the new configuration by default. Pass `{ rewrite: true }` via `opt` to invalidate the previous settings.\n\n##### shapes.devs.Model.removeInPort\n\n    element.removeInPort(port, [opt])\n\nRemove a port from an element from the \\`inPorts\\` array, where \\`port\\` is a name of the port.\n\n##### shapes.devs.Model.removeOutPort\n\n    element.removeOutPort(port, [opt])\n\nRemove a port from an element from the \\`outPorts\\` array, where \\`port\\` is a name of the port.\n\n### joint.shapes.devs.Link\n\nThe `devs.Link` extends the `joint.shapes.standard.Link` and changes the link appearance.\n\n#### Example usage\n\n    const shape = new joint.shapes.devs.Model({\n        position: {\n            x: 100,\n            y: 100\n        },\n        inPorts: ['in1', 'in2'],\n        outPorts: ['out1', 'out2']\n    });\n\n    shape.addTo(graph);\n\n    // adding/removing ports dynamically\n    shape.addInPort('in3');\n    shape.removeOutPort('out1').addOutPort('out3');\n\n    const link = new joint.shapes.devs.Link({\n        source: {\n            id: shape.id,\n            port: 'out3'\n        },\n        target: {\n            x: 200,\n            y: 300\n        }\n    });\n    link.addTo(graph);\n\n    // moving the input ports from `left` to `top`\n    shape.changeInGroup({ position: 'top' });\n    // moving the output ports 'right' to 'bottom'\n    shape.changeOutGroup({ position: 'bottom' });\n\n\n#### Hierarchical diagrams\n\nThere are two more shapes designed for hierarchical diagrams as part of the demo - `devs.Atomic` and `devs.Coupled`. They inherit from the `devs.Model` and differ only in the color and size. The purpose of these shapes is to enable you to implement a custom logic in your application based on their type. For instance a `devs.Coupled` can embed `devs.Atomic`'s but not the other way round as shown in the demo below.\n\nWhen working with hierarchical diagrams there is a few methods, that might come in handy.\n\n[`coupled.embed(atomic)`](https://resources.jointjs.com/docs/jointjs/v3.7/joint.html#dia.Element.prototype.embed) that can put the \\`atomic\\` shape into the \\`coupled\\`.\n\n[`coupled.fitToChildren()`](https://resources.jointjs.com/docs/jointjs/v3.7/joint.html#dia.Element.prototype.fitToChildren) that resizes the \\`coupled\\` shape such that all embedded elements are visually contained within it.\n\n[`link.reparent()`](https://resources.jointjs.com/docs/jointjs/v3.7/joint.html#dia.Link.prototype.reparent) that finds the best parent for the \\`link\\` based on the source and target element.\n","packages/joint-core/demo/elk/README.md":"# JointJS Eclipse Layout Kernel Graph Demo\n\n## Setup\n\nUse Yarn to run this demo.\n\nYou need to build *JointJS* first. Navigate to the root folder and run:\n```bash\nyarn install\nyarn run build\n```\n\nNavigate to this directory, then run:\n```bash\nyarn install\nyarn start\n```\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n","packages/joint-core/demo/rough/README.md":"# JointJS RoughJS Demo\n\n## Setup\n\nUse Yarn to run this demo.\n\nYou need to build *JointJS* first. Navigate to the root folder and run:\n```bash\nyarn install\nyarn run build\n```\n\nNavigate to this directory, then run:\n```bash\nyarn install\n```\n\nThen open `index.html` in your browser:\n```bash\nopen index.html\n```\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n","packages/joint-core/demo/ts-demo/README.md":"# JointJS TypeScript Demo\n\n## Setup\n\nUse Yarn to run this demo.\n\nYou need to build *JointJS* first. Navigate to the root folder and run:\n```bash\nyarn install\nyarn run build\n```\n\nNavigate to this directory, then run:\n```bash\nyarn install\nyarn start\n```\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n","packages/joint-core/demo/vuejs/README.md":"# JointJS VueJS Demo\n\n## Setup\n\nUse Yarn to run this demo.\n\nYou need to build *JointJS* first. Navigate to the root folder and run:\n```bash\nyarn install\nyarn run build\n```\n\nNavigate to this directory, then run:\n```bash\nyarn install\n```\n\nThen open `index.html` in your browser:\n```bash\nopen index.html\n```\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n","packages/joint-decorators/README.md":"# JointJS Decorators\n\nECMAScript / TypeScript decorator for defining *[JointJS](https://www.jointjs.com/)* shapes.\n\nThis library fully depends on [JointJS](https://github.com/clientio/joint) (*>=3.5*), so please read its README before using this library.\n\n## Setup\n\nEnable the [experimentalDecorators](https://www.typescriptlang.org/docs/handbook/decorators.html) compiler option in your `tsconfig.json`.\n\nThen install JointJS Decorators from NPM:\n```bash\nnpm i -S @joint/decorators\n```\n\n## License\n\n[Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/)\n\n## Usage\n\nThere are a few class decorators:\n\n- [`@Model`](#Model)\n- [`@View`](#View)\n\nAnd several class member decorators:\n\n- [`@Function`](#Function)\n- [`@On`](#On)\n- [`@SVGAttribute`](#SVGAttribute)\n\n---\n\n## <a id=\"Model\"></a> @Model(options: ModelOptions)\n\nThe decorator allows you to:\n- paste an existing SVG and use it as the model's markup\n- keep your SVG attributes in sync with your model's attributes\n- transform data using functions\n- introduce a new SVG attribute or change the behavior of an existing one\n\n```ts\nimport { dia } from '@joint/core';\nimport { Model } from '@joint/decorators';\n\n@Model({\n```\n```handlebars\n    template: `\n        <g>\n            <rect\n                width=\"calc(w)\"\n                height=\"calc(h)\"\n                :fill=\"{{color}}\"\n                stroke=\"black\"\n            />\n            <text\n                x=\"calc(0.5*w)\"\n                y=\"calc(0.5*h)\"\n                text-anchor=\"middle\"\n                text-vertical-anchor=\"middle\"\n                font-size=\"14\"\n                font-family=\"sans-serif\"\n                fill=\"black\"\n            >{{firstName}} {{lastName}}</text>\n        </g>\n    `,\n```\n```ts\n    attributes: {\n        color: 'red',\n        firstName: 'John',\n        lastName: 'Doe'\n    }\n})\nclass MyElement extends dia.Element {\n\n}\n```\n\n### ModelOptions\n\n| Option | Description | Optional\n|---|---|---|\n| [template](#Model.template) | the SVG string markup of the model | No |\n| [attributes](#Model.attributes) | the default attributes of the model | Yes |\n| [namespace](#Model.namespace) | the namespace for the model class to be added to | Yes |\n\n### <a id=\"Model.template\"></a> **template**\n\nThe decorator uses an SVG-based template syntax that allows you to declaratively bind the rendered DOM to the underlying model's data.\nAll templates are syntactically valid SVG that can be parsed by spec-compliant browsers and SVG parsers.\n\n> While using the SVG XML string in the markup attribute is not recommended (every cell view needs to parse the string and it might affect the performance), the parsing of the decorator's template runs only once per class (translating it into [JSON markup](https://resources.jointjs.com/docs/jointjs/v3.5/joint.html#dia.Cell.markup) and defining event listeners needed for reactivity).\n\n#### <a id=\"Model.template.text-interpolation\"></a> Text Interpolation\n\nThe most basic form of data binding is text interpolation using the \"Mustache\" syntax (double curly braces):\n\n```handlebars\n<text font-size=\"14\">{{ label }}</text>\n```\n\nThe mustache tag will be replaced with the value of the `label` property from the corresponding model's instance. It will also be updated whenever the `label` property changes.\n\n#### <a id=\"Model.template.attributes\"></a> Attributes Binding\n\nTo bind to an SVG attribute to a model's attribute, add a colon symbol (`:`) before the attribute's name.\n\n```handlebars\n<rect :fill=\"color\" />\n```\n\nThe colon symbol, `:`, instructs the decorator to keep the SVG attribute in sync with the model's attribute.\nThe model's `color` value can be set this way.\n\n```ts\nmodel.set('color', 'red');\n```\n\nIf the bound value is `null`, then the SVG attribute will be removed from the rendered element.\n\n```ts\nmodel.set('color', null);\n```\n\nIt's possible to use mustaches inside binding expressions\n    to combine multiple model's attributes into a single result.\n\n```handlebars\n<rect :fill=\"rgb({{red}},{{green}},{{blue}})\" />\n```\n\n#### <a id=\"Model.template.functions\"></a> Calling Functions\n\nThe value of an attribute can be modified with [functions](#Function) before set/display.\n\n```handlebars\n<rect :stroke=\"color\" :fill=\"lighten(color)\"/>\n<text>{{ capitalize(label) }}</text>\n```\n> Functions called inside binding expressions will be called every time the cell view updates, so they should not have any side effects, such as changing data or triggering asynchronous operations.\n\nIt is possible to call a component-exposed method inside a binding expression only if the method is decorated with the [Function](#Function) decorator.\n\nThe function can accept any number of additional arguments. Every such argument shall be parsable with the [JSON.parse](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) function.\n\n```ts\n@Model({\n    template: `\n        <text y=\"10\">{{ maxLength(label1, 20) }}</text>\n        <text y=\"30\">{{ maxLength(label2, 10) }}</text>\n    `\n})\nclass MyElement extends dia.Element {\n\n    @Function()\n    maxLength(value: string, max: number) {\n        return value.substr(0, max);\n    }\n}\n```\n\nMultiple dependencies can be defined using an array.\n\n```handlebars\n<path :d=\"data([param1, param2])\"/>\n```\n\n> The function is run every time one or more attributes in the dependency array are changed.\n\n```ts\n@Model({\n    attributes: {\n        param1: 20,\n        param2: 30\n    },\n    template: `\n        <path :d=\"data([param1, param2])\" stroke=\"white\" fill=\"transparent\" />\n    `\n})\nclass Arrow extends dia.Element {\n    @Function()\n    data(param1: number, param2: number): string {\n        return `\n                M ${param1} 0\n                L calc(w) calc(0.5*h)\n                L ${param1}  calc(h)\n                V calc(h/2 + ${param2 / 2})\n                H 0\n                v -${param2}\n                H ${param1}\n                Z\n        `;\n    }\n}\n```\n\n#### <a id=\"Model.template.selectors\"></a> Selectors\n\nIf you want to modify any of the template attributes programmatically, you must add the `@selector` attribute to the SVG element.\n\n```handlebars\n<rect @selector=\"body\" stroke=\"black\" fill=\"red\">\n```\n\n```ts\nmodel.attr(['body', 'fill'], 'blue');\n```\n\nThe `<g>` wrapper in the template is added automatically and always has a `@selector` equal to `root`. In case you want to add SVG attributes to the root group, wrap the template with one of them.\n\n```handlebars\n<g @selector=\"root\" data-tooltip=\"My Tooltip\">\n    <rect @selector=\"body\" stroke=\"black\" fill=\"red\">\n</g>\n```\n\nTo create selectors pointing to multiple SVG elements at once, use `@group-selector`.\n\n```handlebars\n<rect @group-selector=\"rectangles\" fill=\"red\">\n<rect @group-selector=\"rectangles\" fill=\"blue\">\n<rect @group-selector=\"rectangles\" fill=\"green\">\n```\n\n```ts\n// Change the stroke of all rectangles\nmodel.attr(['rectangles', 'stroke'], 'black');\n```\n\n#### <a id=\"Model.template.caveats\"></a> Caveats\n\nSome JointJS attributes expect their value to be an object ([fill](https://resources.jointjs.com/docs/jointjs/v3.5/joint.html#dia.attributes.textWrap) & [stroke](https://resources.jointjs.com/docs/jointjs/v3.5/joint.html#dia.attributes.stroke) gradient, [filters](https://resources.jointjs.com/docs/jointjs/v3.5/joint.html#dia.attributes.filter), [markers](https://resources.jointjs.com/docs/jointjs/v3.5/joint.html#dia.attributes.sourceMarker) and [textWrap](https://resources.jointjs.com/docs/jointjs/v3.5/joint.html#dia.attributes.textWrap)).\n\nThe solution is to define the property inside the [attributes](#Model.attributes) (mixing the template attributes with explicit model attributes).\n\n```ts\nconst selector = 'label';\n\n@Model({\n    attributes: {\n        title: 'My Title',\n        attrs: {\n            [selector]: {\n                textWrap: {\n                    maxLineCount: 1,\n                    ellipsis: true\n                }\n            }\n        }\n    },\n    template: `\n        <text @selector=\"${selector}\">{{title}}</text>\n    `\n})\nclass MyElement extends dia.Element {\n\n}\n```\n\n### <a id=\"Model.attributes\"></a> **attributes**\n\nThe default attributes of the model. When creating an instance of the model, any unspecified attributes will be set to their default value.\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { Model } from '@joint/decorators';\n\n@Model({\n    attributes: {\n        color: 'red'\n    }\n})\nclass MyElement extends dia.Element {\n\n}\n```\n\nis equivalent to\n\n```ts\nimport { dia, shapes } from '@joint/core';\n\nclass MyElement extends dia.Element {\n\n    defaults() {\n        const attributes = {\n            color: 'red'\n        };\n        return {\n            ...super.defaults,\n            ...attributes,\n            type: 'MyElement'\n        }\n    }\n}\n```\n\n###  <a id=\"Model.namespace\"></a>  **namespace**\n\nSyntactic sugar for adding a model to the namespace.\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { Model } from '@joint/decorators';\n\n@Model({\n    namespace: shapes\n})\nclass MyElement extends dia.Element {\n\n}\n```\n\nis equivalent to\n\n```ts\nimport { dia, shapes } from '@joint/core';\n\nclass MyElement extends dia.Element {\n\n}\n\nObject.assign(shapes, {\n    'MyElement': MyElement\n});\n```\n\n---\n\n## <a id=\"View\"></a> @View(options: ViewOptions)\n\n### ViewOptions\n\n| Option | Description | Optional |\n|---|---|---|\n| namespace | the namespace for the view class to be added to | Yes\n| models | an array of model classes this view is to be used with | Yes\n\nDefine a new cell view, which is automatically used by 2 different models.\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { View } from '@joint/decorators';\n\n@View({\n    namespace: shapes\n    models: [MyElement, MyOtherElement]\n})\nclass MyElementView extends dia.ElementView {\n\n}\n```\n\nis equivalent to\n\n```ts\nimport { dia, shapes } from '@joint/core';\n\nclass MyElementView extends dia.ElementView {\n\n}\n\nObject.assign(shapes, {\n    'MyElementView': MyElementView,\n    'MyOtherElementView': MyElementView\n});\n```\n---\n## <a id=\"Function\"></a> @Function(name?: string)\n\nDefine functions to transform data (e.g strings, amounts, dates) to be used within the [template](#Model.template).\n\n```ts\n@Model({\n    template: `\n        <text>{{ capitalize(name) }}</text>\n    `,\n    attributes: {\n        name: 'john',\n    }\n})\nclass MyElement extends dia.Element {\n\n    @Function()\n    capitalize(value: string) {\n        return value.charAt(0).toUpperCase() + value.slice(1);\n    }\n}\n```\n\n---\n\n## <a id=\"SVGAttribute\"></a> @SVGAttribute(attributeName: string)\n\nIntroduce new SVG attributes or redefine existing ones.\n\n```ts\nimport { dia, g, attributes } from '@joint/core';\nimport { Model, SVGAttribute } from '@joint/decorators';\n\n@Model({\n    attributes: {\n        width: 140,\n        height: 100\n    },\n    template: `\n        <rect\n            line-style=\"dashed\"\n            stroke-width=\"2\"\n        />\n    `,\n})\nclass MyElement extends dia.Element {\n\n    /* `stroke-dasharray` that adjusts based on the current node's `stroke-width` */\n    @SVGAttribute('line-style')\n    setStrokeDasharray(\n        this: dia.CellView,\n        value: string,\n        rect: g.Rect,\n        node: SVGElement,\n        nodeAttrs: attributes.SVGAttributes\n    ) {\n        const { strokeWidth = 1 } = nodeAttrs;\n        let pattern;\n        switch (value) {\n            case 'dashed': {\n                pattern = `${4 * strokeWidth},${2 * strokeWidth}`;\n                break;\n            }\n            case 'dotted': {\n                pattern = `${strokeWidth},${strokeWidth}`;\n                break;\n            }\n            case 'solid': {\n                pattern = 'none';\n                break;\n            }\n            default: {\n                throw new Error('Invalid line-style value.');\n            }\n        }\n        node.setAttribute('stroke-dasharray', pattern);\n    }\n}\n```\n### SVGAttribute function signature\n\n| Argument | Description | Example |\n|---|---|---|\n| value | the right-hand side of the template's attribute | `\"dashed\"` |\n| rect | a rectangle describing the coordinate system the node is rendered in (if no `ref` attribute is in use, the value is the model's bounding box relative to the model's position, otherwise it is the relative bounding box of the node referenced by the `ref` attribute) | `new g.Rect(0, 0, 140, 100)` |\n| node | a rendered DOM [SVGElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement) | `<rect/> as SVGElement` |\n| nodeAttrs | an object with all defined attributes of the node | `{ lineStyle: \"dashed\", strokeWidth: \"2\" }` |\n\n---\n## <a id=\"On\"></a> @On(eventName: string)\n\n Decorate an event handler in the context of the method it refers to.\n\n```ts\nclass MyElementView extends dia.ElementView {\n\n    @On('click')\n    onClick() {\n        console.log('click!', this.model.id);\n    }\n}\n```\n\n---\n\nCopyright © 2013-2026 client IO\n","packages/joint-eslint-config/README.md":"# Shared package for using eslint config.\n\nConfiguring ESLint in multiple projects is a repetitive task, so we created this package to share a common ESLint configuration across all JointJS packages.\n\n### Issue with running `yarn lint`\n\nIf there is an issue with running `yarn lint`, try removing `node_modules/` and re-installing from monorepo root! This should fix the issue, as sometimes, when `yarn install` is used in a monorepo package directly, it **overwrites** default `eslint-configs`.\n\nTo avoid these issues, **use `yarn install` directly from monorepo root folder**, instead of per package.\n","packages/joint-layout-directed-graph/README.md":"# JointJS Directed Graph Layout\n\nA module for automatic layout of directed graphs for *[JointJS](https://www.jointjs.com/)*.\n\nThis library fully depends on [JointJS](https://github.com/clientio/joint) (*>=4.0*), so please read its `README.md` before using this library.\n\n### Tests\n\nGenerate distribution files from the source code:\n```bash\nyarn run dist\n```\n\nTo run all tests:\n```bash\nyarn run test\n```\n\n## License\n\n[Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/)\n\nCopyright © 2013-2026 client IO\n","packages/joint-layout-elk/README.md":"# JointJS ELK Layout\n\nA module for automatic layout of *[JointJS](https://www.jointjs.com)* graphs using the [Eclipse Layout Kernel (ELK)](https://www.eclipse.org/elk/), via its JavaScript port [elkjs](https://github.com/kieler/elkjs).\n\nThis library depends on [JointJS](https://github.com/clientio/joint) (*>=4.0*), so please read its `README.md` before using this library.\n\n`layout()` is a one-off, asynchronous transform: it builds an ELK graph from a JointJS graph (embedded elements become nested containers at any depth, ports and link labels are included), runs ELK's layout algorithm, and writes the result (positions, sizes, link vertices/anchors, label positions) back onto the graph. It does not keep the graph laid out afterwards - call it again after further changes.\n\n## 🚀 Quick Start\n\n### Installation\n\n```bash\nnpm install @joint/layout-elk\n```\n\n#### UMD build (script tags)\n\n`dist/umd/index.js` keeps `@joint/core` and `elkjs` external - load both first: `@joint/core` as the `joint` global, and `elkjs/lib/elk.bundled.js` as the `ELK` global, which `layout()` runs on the main thread. The package then is `joint.layout.ELK`.\n\n```html\n<script src=\"node_modules/@joint/core/dist/joint.min.js\"></script>\n<script src=\"node_modules/elkjs/lib/elk.bundled.js\"></script>\n<script src=\"node_modules/@joint/layout-elk/dist/umd/index.min.js\"></script>\n<script>\n    joint.layout.ELK.layout(graph).then(({ bbox }) => { /* ... */ });\n</script>\n```\n\n### Basic Usage\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { layout } from '@joint/layout-elk';\n\nconst graph = new dia.Graph({}, { cellNamespace: shapes });\nconst paper = new dia.Paper({\n    model: graph,\n    cellViewNamespace: shapes,\n    el: document.getElementById('paper'),\n});\n\nconst rect1 = new shapes.standard.Rectangle({ id: 'a', size: { width: 80, height: 40 }, attrs: { label: { text: 'A' }}});\nconst rect2 = new shapes.standard.Rectangle({ id: 'b', size: { width: 80, height: 40 }, attrs: { label: { text: 'B' }}});\nconst link = new shapes.standard.Link({ source: { id: 'a' }, target: { id: 'b' }});\n\ngraph.addCells([rect1, rect2, link]);\n\nconst { bbox } = await layout(graph, {\n    elkLayoutOptions: {\n        'elk.algorithm': 'layered',\n        'elk.direction': 'RIGHT',\n        'elk.edgeRouting': 'ORTHOGONAL'\n    }\n});\n```\n\n## 📖 API Reference\n\n### `layout(graphOrCells, options?): Promise<LayoutResult>`\n\n- `graphOrCells`: `dia.Graph | dia.Cell[]` - the graph to lay out, or the cells to lay out.\n- `options?`: `LayoutOptions` - layout configuration (see below).\n\nA list of cells is both the selection and the order:\n\n- **Selection** - only the given elements are laid out. An element whose parent isn't given is laid out as a top-level element.\n- **Links** - a link is laid out only if both of the elements it connects are given too. One that isn't - including a link connected to a point, to another link, or one that is in no graph, whose ends cannot be resolved at all - is left out of the layout entirely: no `exportLink` call, no route, and its vertices, anchors and label positions are left as they are.\n- **Order** - the top-level elements follow the order they are given in, and so do each container's children. The links follow their order too, inside each container.\n- Elements and links go in the same list, in any order - a link may come before the elements it connects. A cell listed more than once is laid out once.\n- ELK identifies everything by a string, while a JointJS id may be a string or a number - so two cells, or two ports of one element, whose ids differ only in type (`5` and `'5'`) would share one id in the ELK graph. `layout()` rejects rather than lay one of them out and leave the other where it was.\n\nThe layout is applied in a single `'layout'` batch on the graph the cells belong to, so it emits one combined change rather than one per element, port and link. An element that is in no graph is laid out and positioned like any other, but contributes no change events - which makes it useful as a container that only exists to group others for the layout.\n\n```ts\n// Lay out a selection, with its embedded elements and the links between its elements.\n// Everything else in the graph is left alone.\nawait layout(graph.getSubgraph(selectedElements, { deep: true }));\n```\n\n```ts\ninterface LayoutResult {\n    bbox: g.Rect;      // Tight bounding box of the laid out graph\n    elkGraph: ElkNode;  // The raw ELK layout result (e.g. for junction points)\n}\n```\n\n### `LayoutOptions`\n\n```ts\ninterface LayoutOptions {\n    // The ELK instance to lay out with, e.g. one running in a Web Worker (see `createWorkerElk()` below).\n    elk?: WorkerElk | ELK; // Default: a shared instance running on the main thread\n    // ELK layout options, in either spelling (see \"Layout option values\" below).\n    elkLayoutOptions?: ElkLayoutOptions; // Default: { 'elk.algorithm': 'layered', 'elk.hierarchyHandling': 'INCLUDE_CHILDREN', 'elk.layered.considerModelOrder.portModelOrder': 'true', 'elk.json.edgeCoords': 'ROOT' }\n    // Aborts the layout - `layout()` rejects with the signal's reason and applies nothing.\n    signal?: AbortSignal;\n\n    // Export callbacks (JointJS graph -> ELK graph) - see below.\n    exportElement?: ExportElementCallback;\n    exportPort?: ExportPortCallback;\n    exportPortLabel?: ExportPortLabelCallback;\n    exportLink?: ExportLinkCallback;\n    exportLinkLabel?: ExportLinkLabelCallback;\n\n    // Import callbacks (ELK layout result -> JointJS graph) - see below.\n    setElementAttributes?: SetElementAttributesCallback;\n    setPortAttributes?: SetPortAttributesCallback;\n    setLinkAttributes?: SetLinkAttributesCallback;\n}\n```\n\n### Layout option values\n\nELK takes every option value as a string. This package takes that spelling and the JointJS one, per option, so one object can mix them:\n\n```ts\nelkLayoutOptions: {\n    'elk.spacing.nodeNode': 6,                  // or '6'\n    'elk.rectpacking.orderBySize': true,        // or 'true'\n    'elk.padding': { left: 10, vertical: 40 },  // or '[top=40,left=10,bottom=40,right=0]'\n    'elk.position': { x: 10, y: 20 },           // or '10,20'\n    'elk.nodeLabels.placement': ['INSIDE', 'V_TOP', 'H_LEFT'],  // or 'INSIDE V_TOP H_LEFT'\n    'elk.direction': 'RIGHT'                    // enums are strings either way\n}\n```\n\nThe string spelling stays supported: it is what the ELK reference page linked from each option uses, and what options that arrive as data - a saved diagram, a backend's config - are already shaped like.\n\n- **Numbers and booleans** take `6` / `true` on any option ELK types as one.\n- **Paddings and margins** (`elk.padding`, `elk.margins`, `elk.spacing.portsSurrounding`, `elk.nodeLabels.padding`) take a [`dia.Sides`](https://docs.jointjs.com/api/util/normalizeSides/): a number for every side, or an object naming `top`/`right`/`bottom`/`left`/`horizontal`/`vertical`. **A side the value does not name is `0`**, not the option's own default - ELK's own string parser reads `'[top=40]'` the same way. That default applies only when the option is not set at all.\n- **Points** (`elk.position`, `elk.nodeSize.minimum`, `elk.port.anchor`) take `{ x, y }`, `g.Point` included.\n- **Sets of enum values** (`elk.nodeLabels.placement`, `elk.portLabels.placement`, `elk.contentAlignment`, `elk.nodeSize.constraints`, `elk.nodeSize.options`) take an array of the option's values, typed against its enum - `[]` is the empty set. ELK itself takes them only as one string of the values; elkjs throws on an array. **ELK ignores the whole value, without an error, if any one of its values is not one it knows** - `'INSIDE BOGUS'` does not even apply `INSIDE` - so the array, which is type-checked, is the safer spelling.\n- Anything else goes to ELK as it is, so ELK reports a value it cannot use rather than this package guessing at it.\n\nThe same applies to the `layoutOptions` set on a draft in an [export callback](#export-callbacks).\n\nTo be held to the JointJS spelling alone, annotate your own options with the `Strict` type - there is no flag on this package to switch:\n\n```ts\nimport type { StrictElkLayoutOptions } from '@joint/layout-elk';\n\nconst options: StrictElkLayoutOptions = {\n    'elk.spacing.nodeNode': 6,\n    // Type error: the strict spelling takes `6`, not `'6'`.\n    'elk.layered.spacing.nodeNodeBetweenLayers': '40'\n};\n```\n\n`Native*` is the other direction - ELK's own spelling alone - and is what `toNativeElkNode()` produces. Each has a per-element counterpart: `StrictElkNodeLayoutOptions`, `NativeElkEdgeLayoutOptions`, and so on.\n\n#### `toNativeElkNode(node: ElkNode): NativeElkNode`\n\nReturns a new graph with every layout option written out as the string ELK takes - throughout: the node's own options, its labels', its ports' (and theirs), the edges filed under it (and theirs), and each of its children. The graph passed in is left as it is.\n\n`layout()` does this for you. Reach for it when you build an ELK graph of your own and want to write its options in the JointJS spelling too.\n\n### Seeing what ELK is given\n\n`layout()` builds the ELK graph, runs ELK on it and applies the result back. All three are one step - there is no public way to do them separately, so that what the ids in between stand for stays this package's business.\n\nTo see the graph itself, pass an `elk` of your own: it is any object with `layout()` and `terminate()` (see [Running ELK in a Web Worker](#running-elk-in-a-web-worker)), and it is handed exactly what ELK is handed - every option already written out as a string, so it pastes straight into [elk-live](https://rtsys.informatik.uni-kiel.de/elklive/json.html).\n\n```ts\nimport ELK from 'elkjs/lib/elk.bundled.js';\n\nconst realElk = new ELK();\n\nawait layout(graph, {\n    elk: {\n        layout: (elkGraph) => {\n            console.log(JSON.stringify(elkGraph, null, 2));\n            return realElk.layout(elkGraph);\n        },\n        terminate: () => {}\n    }\n});\n```\n\nThe same hook is how you run ELK somewhere else - a Web Worker, a server, a cache - or change the graph on its way there. `layout()` still applies the result for you.\n\nOne thing to know if you also use [`signal`](#aborting-a-layout): a `terminate()` is how an `elk` says it can be stopped, so `layout()` hands it the signal instead of merely ignoring a late result. The wrapper above cannot stop `elkjs`, so an abort only takes effect once ELK settles. Leave the wrapper out of the abort path, or forward the signal to something that honours it.\n\n### Export callbacks\n\nEach receives an ELK draft already populated with what this package computed for that element/port/link/label, to mutate in place (e.g. to set `elk.*` `layoutOptions`). Returning `false` instead drops it from the ELK graph entirely - for `exportElement`, its whole subtree (embeds, ports, any connected edge) goes with it, and for `exportPort`, the links connected to that port, which ELK can no longer route to. A link left out this way keeps the route it already had.\n\n```ts\ntype ExportElementCallback = (params: { element: dia.Element; elkNode: ElkNodeDraft }) => void | false;\ntype ExportPortCallback = (params: { portId: string; element: dia.Element; elkPort: ElkPortDraft }) => void | false;\ntype ExportPortLabelCallback = (params: { portId: string; element: dia.Element; elkPortLabel: ElkLabelDraft }) => void | false;\ntype ExportLinkCallback = (params: { link: dia.Link; elkEdge: ElkEdgeDraft }) => void | false;\ntype ExportLinkLabelCallback = (params: { link: dia.Link; labelIndex: number; elkEdgeLabel: ElkLabelDraft }) => void | false;\n```\n\n### Import callbacks\n\nEach applies the ELK-computed attributes itself, in place of the package's own default (`element.set(...)`/`element.portProp(...)`/`link.set(...)`) - use one to redirect where the result goes, e.g. into a `transition()`.\n\n```ts\ntype SetElementAttributesCallback = (params: { element: dia.Element; attributes: { position: dia.Point; size?: dia.Size }; elkNode: ElkNode }) => void;\ntype SetPortAttributesCallback = (params: { element: dia.Element; portId: dia.Cell.ID; attributes: { position: { args: dia.Point }; label?: { position: { args: dia.Point } } }; elkPort: ElkPort; hasMoved: boolean }) => void;\ntype SetLinkAttributesCallback = (params: { link: dia.Link; attributes: { vertices: dia.Point[]; source?: dia.Link.EndJSON; target?: dia.Link.EndJSON; labels?: dia.Link.Label[] }; elkEdge: ElkExtendedEdge }) => void;\n```\n\n### Running ELK in a Web Worker\n\nBy default, `layout()` runs ELK on the main thread - nothing to set up with a bundler or in Node, and it works anywhere (browsers, Node/SSR, tests, and the UMD build once `elkjs` is loaded - see \"UMD build\" above). ELK blocks the page while it runs, though: a few milliseconds for a small graph, but up to seconds for one with thousands of elements. To keep the page responsive, run ELK in a Web Worker instead - start one with `createWorkerElk()` and pass it to `layout()` as `elk`:\n\n```ts\nimport { layout, createWorkerElk } from '@joint/layout-elk';\n\nconst elk = createWorkerElk(() => new Worker(new URL('@joint/layout-elk/worker', import.meta.url), { type: 'module' }));\n\nawait layout(graph, { elk });\n\n// Once no longer needed.\nelk.terminate();\n```\n\n#### `createWorkerElk(createWorker: () => Worker): WorkerElk`\n\n`createWorker` starts the worker - running `@joint/layout-elk/worker`, the package's worker script (ELK's own, `elkjs/lib/elk-worker.min.js`). Starting it is up to you, since only your bundler knows where the script ends up:\n\n- **webpack 5, Vite** (dev server and builds) - `new Worker(new URL('@joint/layout-elk/worker', import.meta.url), { type: 'module' })`, as above. Both bundle the worker script as a file of its own. With webpack, keep `output.publicPath` at `'auto'` (the default), so the file is found wherever the app is served from.\n- **Vite** - also `import ElkWorker from '@joint/layout-elk/worker?worker'`, then `createWorkerElk(() => new ElkWorker())`.\n- **No bundler** (e.g. the UMD build) - serve a copy of `elkjs/lib/elk-worker.min.js`, then `createWorkerElk(() => new Worker('/path/to/elk-worker.min.js'))`.\n\nThe returned `WorkerElk`:\n\n- **Starts the worker on its first layout**, then lays out every graph in it, one at a time - share one instance between layouts.\n- **Stops an aborted layout** (see `signal` below) by terminating the worker, if it is busy with it - a new worker takes over the layouts still waiting.\n- **Rejects the layout the worker crashes during** (e.g. out of memory) - a new worker takes over the layouts still waiting.\n- **Rejects every layout when the worker fails to load** (e.g. its script isn't served where `createWorker` starts it from) or can't be started (e.g. a CSP `worker-src` that blocks it) - with an error saying so, rather than running ELK on the main thread. The next layout tries again with a new worker.\n- **`terminate()`** terminates the worker - layouts not settled yet are rejected, and a later layout starts a new worker.\n\nAny other ELK instance works as `elk` too, e.g. `elkjs`'s own `new ELK({ workerUrl })` (`elkjs/lib/elk-api.js`) - without stopping an aborted layout, though (see below).\n\n### Aborting a layout\n\n`layout()` is asynchronous, so the graph may change while ELK is still computing - pass an `AbortSignal` to drop a layout that is no longer wanted (or takes too long). An aborted `layout()` rejects with the signal's reason (an `AbortError` `DOMException` by default) and applies nothing to the graph.\n\n```ts\nlet controller: AbortController | undefined;\n\nasync function runLayout() {\n    // Only the latest layout is applied.\n    controller?.abort();\n    controller = new AbortController();\n    try {\n        await layout(graph, { signal: controller.signal });\n    } catch (error) {\n        if ((error as Error).name !== 'AbortError') throw error;\n    }\n}\n\n// Give up after 5 seconds.\nawait layout(graph, { signal: AbortSignal.timeout(5000) });\n```\n\nELK can't stop a layout in progress, so a layout a `createWorkerElk()` worker is busy with is stopped by terminating the worker - a new one takes over the layouts still waiting. A layout on the main thread, or in any other `elk` instance, keeps running - only its result is ignored.\n\n## ⚠️ Caveats & Known Limitations\n\n- **Edge coordinates are graph-absolute** - `layout()` sets `elk.json.edgeCoords: 'ROOT'`, so ELK returns every edge's route points and labels relative to the root, whichever container the edge is in, and the default import applies them as they are. Overriding it (e.g. `'CONTAINER'`) is allowed, but the default import then misplaces vertices, end anchors and labels of edges inside containers - convert them yourself in `setLinkAttributes` (from `elkEdge`). The same applies to the raw `elkGraph` in `layout()`'s result.\n- **Node labels are not supported** - ELK's node-label placement assumes labels are layout participants, whereas JointJS labels are attrs inside the shape. Link labels are supported.\n- **Element rotation is not taken into account** - an ELK node is a box with a position and a size, with no rotation, so an element is laid out by the geometry it would have at `angle: 0` and ELK reserves the wrong area for a rotated one. `layout()` warns the first time it sees one.\n- **Ports keep their JointJS-computed position by default** - every element with ports is exported with `elk.portConstraints: 'FIXED_POS'`, so ELK keeps each port where the element's port groups place it and edges route to/from that exact spot. Opt into ELK repositioning/reordering them by overriding it (e.g. `'FIXED_SIDE'`/`'FREE'`) in `exportElement` - setting it in `elkLayoutOptions` has no effect, since the per-node value takes precedence. `layout()` also defaults `elk.layered.considerModelOrder.portModelOrder` to `'true'`, so that a port freed this way keeps the order of the element's `ports.items`; override it in `elkLayoutOptions` to let ELK reorder them to reduce edge crossings.\n- **Asynchronous** - unlike `@joint/layout-directed-graph`, `layout()` returns a `Promise`, since `elkjs` computes layouts asynchronously - even on the main thread.\n- **Main thread by default** - without an `elk` option, ELK runs on the main thread and blocks the page while it runs - see \"Running ELK in a Web Worker\" above. In the ESM build, the main-thread copy of ELK (`elkjs/lib/elk.bundled.js`) is imported dynamically, so bundlers split it into a chunk of its own, only loaded by the first layout without an `elk` option. The UMD build can't load a chunk - it uses the `ELK` global instead, which the page has to load first (see \"UMD build\" above).\n\n## 📄 License\n\n[Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/)\n\nThe code in this package is licensed under the Mozilla Public License 2.0, same as the rest of JointJS. It contains no ELK code: it only calls ELK through its API, and its TypeScript option types link to [ELK's option reference](https://eclipse.dev/elk/reference/options.html) instead of reproducing it.\n\nIt depends on [`elkjs`](https://github.com/kieler/elkjs), which is dual-licensed under the [Eclipse Public License 2.0](https://github.com/kieler/elkjs/blob/master/LICENSE.md) or GPL-3.0-or-later (`EPL-2.0 OR GPL-3.0-or-later`) - you can use it under the EPL-2.0. `elkjs` is installed as a regular dependency and kept external to this package's own builds (ESM and UMD) - it is never copied or inlined into them. An application that bundles this package does ship `elkjs` code though (the main-thread chunk, and the Web Worker file if it uses `@joint/layout-elk/worker`), under that license: keep `elkjs`'s license notice with it (e.g. with your bundler's license extraction), and note where its source is available (it is published on [GitHub](https://github.com/kieler/elkjs) and [npm](https://www.npmjs.com/package/elkjs)).\n\nCopyright © 2013-2026 client IO\n","packages/joint-layout-msagl/README.md":"# JointJS MSAGL Layout\n\nA module for automatic layout of *[JointJS](https://www.jointjs.com)* graphs using the [Microsoft Automatic Graph Layout (MSAGL)](https://github.com/microsoft/msagljs).\n\nThis library fully depends on [JointJS](https://github.com/clientio/joint) (*>=4.0*), so please read its `README.md` before using this library.\n\nThis library provides hierarchical (Sugiyama) layout with support for nested subgraphs and multiple edge routing modes.\n\n## 🚀 Quick Start\n\n### Installation\n\n```bash\nnpm install @joint/layout-msagl\n```\n\n### Basic Usage\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { layout, EdgeRoutingMode } from '@joint/layout-msagl';\n\n// Create your JointJS graph\nconst graph = new dia.Graph({}, { cellNamespace: shapes });\nconst paper = new dia.Paper({\n    model: graph,\n    cellViewNamespace: shapes,\n    el: document.getElementById('paper'),\n});\nconst rect1 = new shapes.standard.Rectangle({\n    id: 'a',\n    size: { width: 80, height: 40 },\n    attrs: { label: { text: 'A' } }\n});\nconst rect2 = new shapes.standard.Rectangle({\n    id: 'b',\n    size: { width: 80, height: 40 },\n    attrs: { label: { text: 'B' } }\n});\nconst link = new shapes.standard.Link({ source: { id: 'a' }, target: { id: 'b' } });\n\ngraph.addCells([rect1, rect2, link]);\n\n// Or with custom options\nlayout(graph, {\n    layerSeparation: 60,\n    nodeSeparation: 30,\n    edgeRoutingMode: EdgeRoutingMode.Rectilinear\n});\n```\n\n## 📖 API Reference\n\n### Main Functions\n\n#### `layout(graphOrCells, options?): LayoutResult`\n\nMain layout function with full customization options.\n\n**Parameters:**\n- `graphOrCells`: `dia.Graph | dia.Cell[]` - JointJS graph or array of cells to layout\n- `options?`: `Options` - Layout configuration (see below)\n\n**Returns:** `LayoutResult`\n\n```ts\ninterface LayoutResult {\n    bbox: g.Rect;           // Tight bounding box of the laid out graph\n    msGraph: Graph;         // Underlying MSAGL graph data structure\n    msGeomGraph: GeomGraph; // Geometry instance used by MSAGL to layout graph\n}\n```\n\n### Options Interface\n\n```ts\n// Callback type definitions\ntype GetSizeCallback = (element: dia.Element) => dia.Size;\ntype GetLabelSizeCallback = (cell: dia.Cell) => dia.Size | undefined;\ntype SetPositionCallback = (element: dia.Element, position: dia.Point) => void;\ntype SetVerticesCallback = (link: dia.Link, vertices: dia.Point[]) => void;\ntype SetLabelsCallback = (link: dia.Link, labelBBox: dia.BBox, points: dia.Point[]) => void;\ntype SetAnchorCallback = (link: dia.Link, linkEndPoint: dia.Point, bbox: dia.BBox, endType: 'source' | 'target') => void;\ntype SetClusterSizeCallback = (element: dia.Element, size: dia.Size) => void;\n\ninterface Options {\n    // Layout direction\n    layerDirection?: LayerDirectionEnum; // Default: LayerDirectionEnum.TB\n    // Spacing\n    layerSeparation?: number; // Default: 40\n    nodeSeparation?: number; // Default: 20\n    // Edge routing\n    edgeRoutingMode?: EdgeRoutingMode; // Default: EdgeRoutingMode.Rectilinear\n    polylinePadding?: number; // Default: 1\n    // Rectilinear self-loop offset\n    // Vertical pixel offset applied to the two inner vertices of a self-edge\n    // when edgeRoutingMode is Rectilinear. Default: 10\n    rectilinearSelfEdgeOffset?: number;\n    // Grid and origin\n    gridSize?: number; // Default: 0\n    x?: number; // Default: 0\n    y?: number; // Default: 0\n    clusterPadding?: dia.Sides; // Default: 10\n    // Element sizing callbacks\n    getSize?: GetSizeCallback; // Default: element.size()\n    getLabelSize?: GetLabelSizeCallback; // Default: element.get('labelSize')\n    // Callbacks for customizing how the layout is applied\n    setPosition?: SetPositionCallback; // Default: element.position(x, y)\n    setVertices?: boolean | SetVerticesCallback; // Default: true\n    setLabels?: boolean | SetLabelsCallback; // Default: true\n    setAnchor?: boolean | SetAnchorCallback; // Default: true\n    setClusterSize?: SetClusterSizeCallback; // Default: element.size(size)\n}\n```\n\n### Enums\n\n#### `LayerDirectionEnum`\n- `TB` - Top to Bottom (default)\n- `BT` - Bottom to Top\n- `LR` - Left to Right\n- `RL` - Right to Left\n\n#### `EdgeRoutingMode`\n- `Rectilinear` - Orthogonal edges with right angles (default)\n- `SplineBundling` - Smooth curved edges\n\n## 🎯 Examples\n\n### Basic Hierarchical Layout\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { layout, LayerDirectionEnum, EdgeRoutingMode } from '@joint/layout-msagl';\n\nconst graph = new dia.Graph({}, { cellNamespace: shapes });\nconst paper = new dia.Paper({\n    model: graph,\n    cellViewNamespace: shapes,\n    el: document.getElementById('paper'),\n});\n\n// Create elements\nconst elements = ['A', 'B', 'C', 'D'].map(id =>\n    new shapes.standard.Rectangle({\n        id,\n        size: { width: 80, height: 40 },\n        attrs: { label: { text: id } }\n    })\n);\n\n// Create links to form a hierarchy: A -> B, A -> C, B -> D, C -> D\nconst links = [\n    new shapes.standard.Link({ source: { id: 'A' }, target: { id: 'B' } }),\n    new shapes.standard.Link({ source: { id: 'A' }, target: { id: 'C' } }),\n    new shapes.standard.Link({ source: { id: 'B' }, target: { id: 'D' } }),\n    new shapes.standard.Link({ source: { id: 'C' }, target: { id: 'D' } })\n];\n\ngraph.addCells([...elements, ...links]);\n\n// Apply layout\nconst bbox = layout(graph, {\n    layerDirection: LayerDirectionEnum.TB,\n    layerSeparation: 50,\n    nodeSeparation: 30,\n    edgeRoutingMode: EdgeRoutingMode.Rectilinear\n});\n```\n\n### Nested Subgraphs\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { layout } from '@joint/layout-msagl';\n\nconst graph = new dia.Graph({}, { cellNamespace: shapes });\nconst paper = new dia.Paper({\n    model: graph,\n    cellViewNamespace: shapes,\n    el: document.getElementById('paper'),\n});\n\nconst parent = new shapes.standard.Rectangle({\n    id: 'parent',\n    size: { width: 200, height: 150 }\n});\n\nconst child1 = new shapes.standard.Rectangle({\n    id: 'child1',\n    size: { width: 60, height: 30 }\n});\n\nconst child2 = new shapes.standard.Rectangle({\n    id: 'child2',\n    size: { width: 60, height: 30 }\n});\n\n// Embed children in parent\nparent.embed(child1);\nparent.embed(child2);\n\n// Add internal link between children\nconst internalLink = new shapes.standard.Link({\n    source: { id: 'child1' },\n    target: { id: 'child2' }\n});\n\ngraph.addCells([parent, child1, child2, internalLink]);\n\n// Layout will automatically handle the subgraph\nlayout(graph);\n```\n\n### Edge Routing with Labels\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { layout } from '@joint/layout-msagl';\n\nconst graph = new dia.Graph({}, { cellNamespace: shapes });\nconst paper = new dia.Paper({\n    model: graph,\n    cellViewNamespace: shapes,\n    el: document.getElementById('paper'),\n});\n\nconst rect1 = new shapes.standard.Rectangle({\n    id: 'A',\n    size: { width: 80, height: 40 },\n    attrs: { label: { text: 'A' } }\n});\n\nconst rect2 = new shapes.standard.Rectangle({\n    id: 'B',\n    size: { width: 80, height: 40 },\n    attrs: { label: { text: 'B' } }\n});\n\n// Set label size on links for proper label positioning\nconst link = new shapes.standard.Link({\n    source: { id: 'A' },\n    target: { id: 'B' },\n    labels: [\n        {\n            attrs: {\n                text: {\n                    text: 'Label'\n                }\n            }\n        }\n    ],\n    labelSize: { width: 80, height: 20 } // Required for label layout\n});\n\ngraph.addCells([rect1, rect2, link]);\n\nlayout(graph);\n```\n\n### Custom Callbacks\n\n```ts\nimport { dia, util } from '@joint/core';\n\nlayout(graph, {\n    // Custom positioning with animation\n    setPosition: (element: dia.Element, position: dia.Point) => {\n        element.transition('position', position, {\n            duration: 500,\n            timingFunction: util.timing.cubic,\n            valueFunction: util.interpolate.object\n        });\n    }\n});\n```\n\n## 🔧 Advanced Features\n\n### Label Positioning\n\nThe layout automatically positions labels on links and reserves space for element labels in subgraphs. Label sizing is controlled by the `getLabelSize` callback:\n\n**Default behavior** - reads from the `labelSize` property:\n```ts\nlink.set('labelSize', { width: 100, height: 20 });\nparentElement.set('labelSize', { width: 120, height: 25 });\n```\n\n**Custom label sizing** - provide your own callback:\n```ts\nlayout(graph, {\n    getLabelSize: (cell) => {\n        if (cell.isLink()) {\n            // Calculate link label size based on text content\n            const text = cell.label(0)?.attrs?.text?.text || '';\n            return { width: text.length * 8, height: 20 };\n        } else {\n            // Calculate element label size\n            const text = cell.attr('label/text') || '';\n            return { width: text.length * 10, height: 25 };\n        }\n    }\n});\n```\n\nIf `getLabelSize` returns `undefined` for a cell, no space is reserved for its label.\n\n## ⚠️ Caveats & Known Limitations\n\n- **Rectilinear self‑loops** – When `edgeRoutingMode` is set to `Rectilinear`, self‑edges use a configurable vertical offset controlled by `rectilinearSelfEdgeOffset` (default `10`). This is a stop‑gap until upstream `msagljs` provides native rectilinear self‑loop geometry.\n- **Subgraph resizing** – Parent elements that embed other elements are resized by the layout to tightly pack all their children.\n- **Subgraph layout direction** - Layout inside subgraphs is always set to `TB` (Top-to-Bottom) direction, as other directions can cause layout issues.\n- **Link labels in subgraphs** – Link labels within subgraphs may be positioned incorrectly, despite the layout correctly reserving space for them.\n- **Obstacle padding (first‑bend distance)** – MSAGL exposes an `edgeRoutingSettings.Padding` option intended to keep edges a minimum distance away from obstacles (nodes) and thus control where the first bend occurs. In msagljs this setting is currently not working.\n- **ID handling** - MSAGL expects IDs to be string values so `id: 1` and `id: '1'` are considered the same.\n\n## 📄 License\n\n[Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/)\n\nCopyright © 2013-2026 client IO\n","packages/joint-mock-svg/README.md":"# JointJS Mock SVG\n\nMocks of those SVG globals that JSDOM does not implement but *[JointJS](https://www.jointjs.com/)* relies on, including `SVGAngle`, `createSVGMatrix()`, `getBBox()`, and `transform.baseVal`.\n\nThis package is **test-runner agnostic**: it depends on no test runner and uses no spies, so it can be consumed from any of them.\n\n## Usage\n\nImport it once, before your tests run, in a DOM environment:\n\n```js\nimport '@joint/mock-svg';\n```\n\nThe import is side-effectful: it installs the mocks on `globalThis` and exports no value. Note that a DOM is required - without it the import fails.\n\nWhere that import belongs depends on your runner - it needs to run before any test file touches the SVG globals, which usually means a setup-file option rather than an import in the test itself.\n\n## TypeScript\n\n`getBBox()`, `getScreenCTM()`, `getComputedTextLength()` and `transform` are mocked on `SVGElement`, one level wider than where DOM types (`lib.DOM`) declare them, because JSDOM makes `<rect>`, `<path>` and `<text>` plain `SVGElement`s. Reading one of them off `SVGElement.prototype` - like when spying on the mocks - therefore needs the exported `MockedSVGElement` type. For example:\n\n```ts\nimport type { MockedSVGElement } from '@joint/mock-svg';\n\njest.spyOn(SVGElement.prototype as MockedSVGElement, 'getBBox')\n    .mockReturnValue({ x: 0, y: 0, width: 100, height: 50 } as DOMRect);\n```\n\n## License\n\nThe *@joint/mock-svg* library is licensed under the [MIT License](https://github.com/clientIO/joint/blob/master/packages/joint-mock-svg/LICENSE).\n\nCopyright © 2013-2026 client IO\n","packages/joint-react/CLAUDE.md":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with\ncode in this repository.\n\n## Project Overview\n\n`@joint/react` is the React bindings library for JointJS. It provides React\ncomponents and hooks for building diagramming applications with JointJS,\nrendering diagram elements as React components via SVG foreignObject portals.\n\n**Stack:** TypeScript 5.9, React 19 (peer: >=18), Rollup + esbuild (build), Vite\n(storybook/dev), Jest 30 + @testing-library/react (testing), ESLint 9 flat\nconfig, Storybook 10\n\n## Common Commands\n\n```bash\n# Build (Rollup + esbuild → dist/cjs, dist/esm, dist/types)\nyarn build\n\n# Run all checks (typecheck + knip + jest, once against React 19 and once\n# against React 18)\nyarn test\n\n# Run individual checks\nyarn typecheck              # tsc --noEmit\nyarn lint                   # ESLint\nyarn lint-fix               # ESLint with auto-fix\nyarn jest                   # Jest tests only\n\n# Run a single test file\nyarn jest --testPathPatterns=\"use-elements\"\n\n# Run tests in watch mode\nyarn jest --watch\n\n# Storybook (dev server on :6006)\nyarn storybook\n\n# Build storybook\nyarn build-storybook\n```\n\n## Changesets\n\nReleasable changes need a changeset - the format is defined in the **Changeset\nformat** section of the repository's `CONTRIBUTING.md`. Two things are specific\nto this package:\n\n- The changelog scope is the exported component or hook rather than a JointJS\n  namespace - components as JSX tags, hooks by name:\n\n  ```markdown\n  <Paper /> - fix the visual grid to redraw reactively when `drawGrid` changes\n  useCells - fix ghost cells reported after `resetCells()`\n  ```\n\n- `@joint/react` is versioned independently. It is not part of the\n  `@joint/core` linked group, and because it depends on core via `workspace:^`,\n  a `@joint/core` minor release does not drag it along - only a major does.\n\n# Clean Code & Style Guidelines\n\n## General\n\n- Prefer clear, explicit code over clever one-liners. Maintainability > brevity.\n- When editing existing code, preserve the existing style unless I explicitly\n  ask for a refactor.\n- Leave code cleaner than you found it. Continuously refactor small things\n  instead of accumulating technical debt.\n\n## Constants Over Magic Numbers\n\n- Replace hard-coded values with named constants.\n- Use descriptive constant names that explain the value's purpose.\n- Keep constants at the top of the file\n\n## Meaningful Names\n\n- Variables, functions, and classes should reveal their purpose.\n- Names should explain **why** something exists and **how** it is used.\n- Avoid abbreviations unless they are truly universal (e.g. `id`, `URL`,\n  `HTML`).\n- Boolean names must be self-descriptive and read like conditions:\n  - Use prefixes: `is`, `has`, `should`, `can`, `must`, `needs` (e.g. `isOpen`,\n    `hasError`, `shouldRetry`).\n  - Avoid generic names like `flag`, `ok`, `state`, `enabled` without context.\n- Function names should describe behavior, not implementation details.\n\n## File Naming (TypeScript / JavaScript)\n\n- All `.ts`, `.tsx`, `.js`, `.jsx` files must use **kebab-case**:\n  - Examples: `user-profile.ts`, `something-table.tsx`\n- React component files:\n  - File name: kebab-case (e.g. `user-profile-card.tsx`).\n  - Component name inside: `PascalCase` (e.g. `UserProfileCard`).\n\n## Smart Comments\n\n- Do **not** comment on what the code does if the code can be made\n  self-explanatory instead.\n- Prefer to improve the code so it reads clearly without comments.\n- Use comments to explain:\n  - Why something is done in a non-obvious way.\n  - Important constraints, assumptions, or business rules.\n  - Non-obvious side effects.\n- Document APIs, complex algorithms, and tricky edge cases.\n\n## Single Responsibility\n\n- Each function should do exactly one thing.\n- Functions should be small and focused.\n- If a function needs a long comment to explain what it does, consider\n  splitting it into smaller functions.\n- JSDoc is mandatory for all exported functions.\n\n## DRY (Don't Repeat Yourself)\n\n- Extract repeated code into reusable functions or utilities.\n- Share common logic through proper abstractions.\n- Maintain single sources of truth for configuration, constants, and shared\n  logic.\n\n## Clean Structure\n\n- Keep related code together.\n- Organize code in a logical hierarchy (modules, domains, features).\n- Use consistent file and folder naming conventions.\n- Avoid \"god\" files that own too many responsibilities.\n\n## Encapsulation\n\n- Hide implementation details behind clear interfaces.\n- Expose only what is needed from a module.\n- Move nested conditionals or complex logic into well-named helper functions.\n\n## TypeScript\n\n- Never use `any` in new code:\n  - Prefer proper types, generics, unions, or `unknown` + narrowing.\n  - If `any` is unavoidable at a boundary (e.g. 3rd-party library), isolate it\n    and add a clear `TODO` explaining why.\n- Write `strict`-friendly code:\n  - No implicit `any`.\n  - Avoid sloppy or unnecessary `as` casts.\n  - Avoid `as any` and non-null assertions (`!`) unless absolutely necessary;\n    justify them with a short comment.\n- Use `interface`/`type` aliases for data structures instead of loose objects.\n- Keep function signatures explicit and well-typed, especially for exported\n  functions and public APIs.\n- Prefer readonly/immutable data where reasonable (e.g. `readonly` props, avoid\n  mutating function arguments).\n\n## Performance Mindset\n\n- Avoid unnecessary allocations and copies:\n  - No pointless spreading (`{ ...obj }`) or `JSON.parse(JSON.stringify(...))`\n    in hot paths.\n- Avoid creating new functions/objects inside tight loops or very frequently\n  rendered components unless memoized.\n- Use `useMemo` / `useCallback` **only** when they prevent real, recurring work\n  or re-renders, not everywhere by default.\n- Be careful not to introduce extra re-renders by changing prop shapes\n  unnecessarily.\n- Prefer algorithmic improvements and proper data structures over\n  micro-optimizations when performance is a concern.\n\n## Code Quality Maintenance\n\n- Refactor continuously in small, safe steps.\n- Fix technical debt early when you touch a piece of code.\n- Prefer explicit, predictable behavior over clever tricks.\n- Keep the codebase internally consistent.\n\n## Testing & Safety\n\n- For non-trivial logic changes, prefer adding or updating tests.\n- Write tests before fixing bugs when possible (regression tests).\n- Keep tests readable, deterministic, and maintainable.\n- Test edge cases, error conditions, and boundary values.\n- Avoid relying on implementation details; test behavior and contracts.\n- Performance is must have, so test it.\n","packages/joint-react/README.md":"<p align=\"center\">\n  <img src=\"https://cdn.prod.website-files.com/63061d4ee85b5a18644f221c/633045c1d726c7116dcbe582_JJS_logo.svg\" alt=\"JointJS\" height=\"56\" />\n</p>\n\n<h1 align=\"center\">JointJS for React</h1>\n\n<p align=\"center\">\n  <strong>Production-scale diagramming for React.</strong><br/>\n  A first-class React integration for <a href=\"https://jointjs.com\">JointJS</a> — real components and hooks for building complex diagramming apps, with full control over the data model, rendering, and interactions, using the React patterns you already know.\n</p>\n\n<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/@joint/react\"><img src=\"https://img.shields.io/npm/v/@joint/react?style=flat-square&color=0EA5E9\" alt=\"npm\" /></a>\n  <a href=\"https://www.npmjs.com/package/@joint/react\"><img src=\"https://img.shields.io/npm/types/@joint/react?style=flat-square&color=3178C6\" alt=\"types\" /></a>\n  <a href=\"https://www.npmjs.com/package/@joint/react\"><img src=\"https://img.shields.io/npm/dm/@joint/react?style=flat-square&color=F59E0B\" alt=\"downloads\" /></a>\n  <a href=\"./LICENSE\"><img src=\"https://img.shields.io/npm/l/@joint/react?style=flat-square&color=10B981\" alt=\"license\" /></a>\n</p>\n\n<p align=\"center\">\n  🚀 <a href=\"#quick-start\">Quick start</a> ·\n  🧩 <a href=\"https://www.jointjs.com/demos/ai-workflow-builder\">Live Example</a> ·\n  📖 <a href=\"#learn-more\">Learn more</a>\n</p>\n\n[![Use Cases](https://github.com/user-attachments/assets/fc6de738-3fad-48f7-99aa-ba8c3729d9b1)](https://github.com/user-attachments/assets/fc6de738-3fad-48f7-99aa-ba8c3729d9b1)\n\n---\n\nBuilt on the proven JointJS core, you get a mature diagramming UI library and an idiomatic React layer in one. Like JointJS, it's open source.\n\n## Why JointJS for React\n\n- **A real data model.** A serializable graph you can query, transform, and persist — the backbone for complex, stateful diagram applications, not just visual nodes.\n- **Full control.** Complete control over how diagrams render and behave — shapes, ports, links, routing, and custom interactions.\n- **Production-grade UX.** The interaction details end users expect come built in: connection snapping, link routing that avoids obstacles, element and link tools, highlighters for hover and selection states. Good UX is designed into every feature, not left for you to assemble.\n- **Performance at scale.** Smooth interaction on large graphs with thousands of nodes.\n- **Idiomatic React.** Real components and hooks that fit naturally into your existing React app and tooling.\n\n## Installation\n\n```bash\nnpm install @joint/react     # or pnpm add / yarn add / bun add\n```\n\nPeer dependency: **React 18 or 19**.\n\n## Quick start\n\n```tsx\nimport { GraphProvider, Paper, HTMLBox, type CellRecord } from '@joint/react';\n\ninterface NodeData {\n  label: string;\n}\n\nconst initialCells: ReadonlyArray<CellRecord<NodeData>> = [\n  { id: '1', type: 'element', position: { x: 40, y: 40 }, data: { label: 'Hello' } },\n  { id: '2', type: 'element', position: { x: 280, y: 180 }, data: { label: 'World' } },\n  { id: '1 → 2', type: 'link', source: { id: '1' }, target: { id: '2' } },\n];\n\nfunction renderElement(data: NodeData) {\n  return <HTMLBox>{data.label}</HTMLBox>;\n}\n\nexport default function Diagram() {\n  return (\n    <GraphProvider initialCells={initialCells}>\n      <Paper style={{ width: '100%', height: 600 }} renderElement={renderElement} />\n    </GraphProvider>\n  );\n}\n```\n\n`GraphProvider` holds the graph state, `Paper` renders it, and `renderElement` turns each node's data into a React component. See [Learn more](#learn-more) for the full getting-started guide.\n\n## When is JointJS for React the right choice?\n\nJointJS for React is built for production-scale diagramming — applications where the diagram is a core part of the product and has to handle real complexity: a rich data model, large graphs, domain-specific shapes, and deep control over interactions and rendering.\n\nProduction-scale is not only about node count. Most workflow tools top out at tens of nodes, yet their users still expect a polished editing experience: precise connections, clear feedback on hover and selection, links that route cleanly around obstacles. JointJS for React treats that end-user UX as part of the library, so you ship it instead of building it.\n\nIt's designed to be the layer you build on as your requirements grow, from the first prototype through to a mature, scaling application.\n\n## Use cases\n\nJointJS for React is used to build production diagramming applications across many domains:\n\n| Domain | Examples |\n|---|---|\n| Workflow & process | Flowchart editors, process automation, activity and sequence diagrams |\n| AI workflow builders | Node-based AI pipeline and agent builders, marketing automation studios |\n| BPMN | Business process modelers, swimlanes, Visio BPMN import/export |\n| Data modeling | ER diagrams, database schema design, data lineage and mapping |\n| UML & software modeling | Class, sequence, statechart and use-case diagrams |\n| Industrial / SCADA & HMI | SCADA interfaces, HMI control panels, P&ID and process diagrams |\n| Energy networks | Power grid and substation diagrams, telecom and fiber (DWDM) network design |\n| Electronic design | Circuit and logic design, wiring and cable harness diagrams, schematic capture |\n| Org charts | Org charts, reporting hierarchies, team and headcount planning |\n| Timelines | Project and milestone timelines, Gantt and PERT charts |\n\nExplore the [demos](https://www.jointjs.com/demos) to see them in action.\n\n## Learn more\n\n- 📖 [Learn JointJS for React](https://docs.jointjs.com/react)\n- 🧾 [API reference](https://docs.jointjs.com/api-react)\n- 🖼️ [Examples](https://www.jointjs.com/demos)\n- 📚 [Storybook](https://react.jointjs.com/learn/)\n- 🤖 [MCP server](#using-jointjs-for-react-with-ai-coding-agents)\n- 💬 [Ask a question or share feedback](https://github.com/clientIO/joint/discussions)\n\n## Using JointJS for React with AI coding agents\n\nJointJS is built to work well with AI-assisted development.\n\n**MCP server** — connect your AI coding agent (Claude Code, Cursor, and other MCP-compatible tools) to the JointJS Model Context Protocol server so it can search the official docs and demos while you build:\n\n```\nhttps://mcp.jointjs.com/mcp\n```\n\nIt exposes `search_docs` and `search_demos`, so your agent writes correct JointJS for React code against the real API instead of guessing.\n\n## Commercial version\n\nThe open-source library covers core diagramming. **JointJS+ for React** is the commercial extension that takes the end-user experience further with production-ready plugins — stencils, toolbars, snaplines, keyboard shortcuts, undo/redo, advanced layouts, import/export, and more — plus professional demo apps (including source code) and direct support from the team that builds the library. [Start a free 30-day trial.](https://www.jointjs.com/free-trial)\n\n## License\n\nJointJS for React is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n","packages/joint-router-avoid/README.md":"# JointJS Avoid Router\n\nA module that routes *[JointJS](https://www.jointjs.com)* links using [libavoid](https://github.com/mjwybrow/libavoid), a C++ library for automatic, obstacle-avoiding orthogonal connector routing, compiled to WebAssembly via [libavoid-js](https://github.com/Aksem/libavoid-js).\n\nThis library fully depends on [JointJS](https://github.com/clientio/joint) (*>=4.0*), so please read its `README.md` before using this library.\n\nlibavoid maintains a single incremental router shared by the whole graph: it tracks every element as an obstacle and every link as a connector, and reroutes affected connectors whenever obstacles move. This package wraps that behavior in a `RouterService` that listens to a `dia.Graph` and, whenever libavoid computes a new route for a link, applies it directly to that link's `vertices` and source/target anchors - there is no `router: { name: ... }` attribute to set on links; once a link is connected to two elements on a routed graph, it is routed automatically. See [`examples/avoid-router-ts`](https://github.com/clientIO/joint/tree/master/examples/avoid-router-ts) for a full demo.\n\n## 🚀 Quick Start\n\n### Installation\n\n```bash\nnpm install @joint/router-avoid\n```\n\n### Basic Usage\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { initAvoidRouter } from '@joint/router-avoid';\n\nconst graph = new dia.Graph({}, { cellNamespace: shapes });\nconst paper = new dia.Paper({\n    model: graph,\n    cellViewNamespace: shapes,\n    el: document.getElementById('paper'),\n});\n\n// Loads the libavoid-js WebAssembly module (if not already loaded), then\n// registers every element/link currently in the graph as an avoid\n// obstacle/connector and keeps them in sync with future graph changes.\nconst routerService = await initAvoidRouter(graph, {\n    shapeBufferDistance: 20,\n    idealNudgingDistance: 10,\n});\n\n// ... add elements and links to the graph - links connected to two\n// elements are picked up and routed automatically, no `router`\n// attribute needed:\n// new shapes.standard.Link({ source: { id: a.id }, target: { id: b.id } });\n\n// Stop routing this graph and release the underlying resources\n// (e.g. terminate a Worker thread) when they're no longer needed.\n// routerService.destroy();\n```\n\n`initAvoidRouter()` resolves once the module is loaded and the current graph content has been registered with libavoid; the routes themselves are still computed asynchronously and applied as they come in - listen for the `link:routed` event to know when a specific link's route has settled (see below).\n\nLoaded as a script tag rather than via ESM, the same functions are available as `joint.routers.avoid.initAvoidRouter` / `joint.routers.avoid.loadAvoidRouter` on the UMD build.\n\n## 📖 API Reference\n\n### `loadAvoidRouter(filePath?): Promise<void>`\n\nLoads the `libavoid-js` WebAssembly module. `initAvoidRouter()` calls this automatically if the module isn't loaded yet, so calling it directly is only needed to preload the module ahead of time. Accepts an optional path to the `libavoid.wasm` file if it is not served from its default location. Safe to call multiple times - later calls resolve once the first load completes.\n\n### `initAvoidRouter(graph, options?): Promise<RouterService>`\n\nLoads the module (via `loadAvoidRouter()`, if needed) and creates a `RouterService` for `graph`. The returned service is not started - call `start()` to keep the graph continuously routed, or `routeAll()`/`routeSubgraph()` for a one-shot routing pass.\n\n- `graph`: `dia.Graph` - the graph to route.\n- `options?`: `InitAvoidOptions`\n\n```ts\ninterface InitAvoidOptions {\n    shapeBufferDistance?: number;  // Default: 10 - spacing added around shapes when computing obstacles, and used as the fallback route's margin around elements\n    idealNudgingDistance?: number; // Default: 5 - spacing used to nudge apart overlapping connector segments\n    worker?: boolean | WorkerOptions; // Default: false - run libavoid inside a Web Worker instead of the main thread; pass an object to configure the Worker\n    libavoidFilePath?: string;     // Forwarded to loadAvoidRouter()\n    trackLink?: (params: { link: dia.Link }) => boolean;          // Which links to route; return false to exclude one. Default: all\n    trackElement?: (params: { element: dia.Element }) => boolean; // Which elements to track as avoid obstacles; return false to exclude one. Default: all\n    // First refusal on a link avoid can't route because one of its ends\n    // isn't connected to a cell ('unconnected'), is connected to another\n    // link ('unsupported'), or is connected to an element excluded via\n    // `trackElement` ('untracked'). Return true to take over routing that\n    // link yourself; otherwise the built-in `rightAngle`-based fallback\n    // route is applied.\n    interceptUnroutableLink?: (params: { link: dia.Link, reason: 'unconnected' | 'untracked' | 'unsupported' }) => boolean;\n    // Override how computed route attributes are applied to a link, e.g. to\n    // route the update through a command manager. Default: link.set()\n    setRouteAttributes?: (params: { link, attributes, origin, routing, unroutableReason }) => void;\n    changeFlag?: string;           // Default: 'avoidRouter' - name of the opt flag marking this router's own link.set() calls\n}\n\ninterface WorkerOptions {\n    debounceTime?: number; // Default: 100 - ms the Worker waits after the last received change before recomputing routes in a single batch; 0 applies every change immediately\n}\n```\n\n### `RouterService` (returned by `initAvoidRouter()`)\n\nThe object responsible for keeping libavoid's internal obstacle/connector graph in sync with the `dia.Graph`, applying computed routes to links, and emitting routing events. It mixes in JointJS's `mvc.Events` (`on`/`off`/`trigger`).\n\n- `start()` / `stop()` - start/stop listening to graph changes (added/removed cells, moved/resized elements, reconnected links). `start()` also syncs every cell the graph already holds.\n- `isStarted` - whether the service is currently listening to its graph.\n- `routeAll()` - one-shot: routes every cell currently in the graph, resolving once all routes are applied. No graph listener is attached. Throws while started. Resolves with a `RoutingResult` - `{ status: 'done' }`, or `{ status: 'cancelled' }` if the pass was interrupted by `destroy()`. Overlapping calls are queued and run one after another.\n- `routeSubgraph(cells)` - one-shot: routes only `cells`, independently of the rest of the graph (cells outside the array are neither routed nor obstacles). Throws while started. Resolves with the same `RoutingResult` as `routeAll()`.\n- `changeFlag` - the `opt` flag name marking this router's own `link.set()` calls, for filtering them out of your own `change` listeners.\n- `destroy()` - stops routing the graph, cancels open routing cycles, and releases the resources held by the service and its provider (e.g. terminates a Worker thread). Do not use the instance afterwards.\n\n**Events:**\n\n- `link:routing` `(link)` - emitted when a link's route is (re-)computing.\n- `link:routed` `(link, { origin, reason })` - emitted once a link's route has been applied. `origin` is `'avoid'` or `'fallback'`; `reason` is set when the fallback route was applied because the link was unroutable.\n- `link:routing:cancelled` `(link)` - emitted when a link with an open `link:routing` cycle becomes unroutable (e.g. disconnected) before libavoid produced a route for it.\n- `idle` `()` - emitted when there are no more open routing cycles.\n\n## ⚠️ Caveats & Known Limitations\n\n- **Asynchronous setup** - `initAvoidRouter()` loads the WebAssembly module and performs the initial graph sync asynchronously; it must be awaited before the graph is considered routed.\n- **Bundler configuration** - `libavoid-js` ships its logic and the `libavoid.wasm` binary as separate files, and `worker: true` additionally loads this package's own worker script as a module. Consuming applications are responsible for ensuring both are served alongside the rest of the bundle (e.g. via a copy plugin for your bundler of choice).\n- **Fallback routing** - libavoid does not expose a way to check whether a computed route is valid, so a heuristic is used. When the route is deemed invalid, or when a link isn't connected to elements on both ends, the built-in `rightAngle`-based fallback route is applied instead (unless `interceptUnroutableLink` takes over).\n- **One `RouterService` per graph** - constructing more than one `RouterService` for the same `dia.Graph` results in both instances applying routes to the same links; call `destroy()` on a previous instance before creating a new one for the same graph.\n- **Custom vertices (checkpoints)** are not currently supported.\n\n## 📄 License\n\nThe code in this package is licensed under the [Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/), same as the rest of JointJS.\n\nIt depends on [`libavoid-js`](https://www.npmjs.com/package/libavoid-js), a WebAssembly build of [libavoid](https://github.com/mjwybrow/adaptagrams), which is licensed under the **GNU LGPL 2.1 or later** - applications shipping this package also ship the `libavoid.wasm` binary under that license. Keep the binary a separately served file (as described under bundler configuration above); don't configure your bundler to inline it into your application bundle.\n\nCopyright © 2013-2026 client IO\n\nThis package depends on [`libavoid-js`](https://github.com/Aksem/libavoid-js), which is licensed under the [LGPL-2.1-or-later license](https://github.com/Aksem/libavoid-js?tab=LGPL-2.1-1-ov-file#readme).\n","packages/joint-shapes-general-tools/README.md":"# JointJS General Shapes Tools\n\nA collection of ECMAScript / TypeScript tools for use with general *[JointJS](https://www.jointjs.com/)* shapes.\n\nThis library fully depends on [JointJS](https://github.com/clientio/joint) (*>=3.5*), so please read its `README.md` before using this library.\n\n## License\n\n[Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/)\n\nCopyright © 2013-2026 client IO\n","packages/joint-shapes-general/README.md":"# JointJS General Shapes\n\nA collection of general ECMAScript / TypeScript shapes for *[JointJS](https://www.jointjs.com/)*.\n\nThis library fully depends on [JointJS](https://github.com/clientio/joint) (*>=3.5*), so please read its `README.md` before using this library.\n\n## License\n\n[Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/)\n\nCopyright © 2013-2026 client IO\n","packages/joint-vitest-plugin-mock-svg/README.md":"# JointJS Mock SVG plugin for Vitest\n\nA Vitest plugin to mock SVG methods not implemented in JSDOM used by *[JointJS](https://www.jointjs.com/)*.\n\n## Usage\n\nImport the plugin in your `vitest.config.js`:\n\n```js\nimport { defineConfig } from 'vitest/config';\nimport mockSVG from '@joint/vitest-plugin-mock-svg'\n\nexport default defineConfig({\n  plugins: [mockSVG()],\n})\n```\n\nThe mocks themselves come from [`@joint/mock-svg`](https://github.com/clientIO/joint/tree/master/packages/joint-mock-svg) as plain functions. If your test needs to assert on one of them, or change what the mock returns, use `vi.spyOn()`.\n\n### Changing what a mock returns\n\nThe default `getBBox()` returns an all-zero rect, which leaves `checkVisibility()` false and `util.breakText()` with nothing to measure. If your test needs one, give the mock a size object as a return value:\n\n```js\nvi.spyOn(SVGElement.prototype, 'getBBox')\n  .mockReturnValue({ x: 0, y: 0, width: 100, height: 20 });\n```\n\n`mockRestore()`, `vi.restoreAllMocks()` and `restoreMocks: true` all put the mock back afterwards, so an override stays local to its test and the suite needs no `beforeEach` to reinstall anything.\n\nThe same spy also records calls. This lets you assert whether JointJS measured anything at all:\n\n```js\nconst getBBox = vi.spyOn(SVGElement.prototype, 'getBBox');\nrender(<MyDiagram />);\nexpect(getBBox).toHaveBeenCalled();\n```\n\n`vi.spyOn` reaches nested members, as well - for example `vi.spyOn(SVGElement.prototype.transform.baseVal, 'appendItem')`.\n\nTo swap a mock implementation permanently, reassign it:\n\n```js\nSVGElement.prototype.getComputedTextLength = () => 42;\n```\n\nRestore the previous value yourself if the change should not outlive the file.\n\n### Driving `ResizeObserver`\n\nThe mocked observer accepts a callback and never calls it. To deliver entries, or to assert on `observe`, provide your own:\n\n```js\nglobalThis.ResizeObserver = class {\n  observe = vi.fn();\n  unobserve = vi.fn();\n  disconnect = vi.fn();\n  constructor(callback) { this.callback = callback; }\n  resize(entries) { this.callback(entries, this); }\n};\n```\n\n## TypeScript\n\n`getBBox()`, `getScreenCTM()`, `getComputedTextLength()` and `transform` are mocked on `SVGElement`, one level wider than where DOM types (`lib.DOM`) declare them, because JSDOM makes `<rect>`, `<path>` and `<text>` plain `SVGElement`s. Reading one of them off `SVGElement.prototype` - as every example above does - therefore needs the re-exported `MockedSVGElement` type. For example:\n\n```ts\nimport type { MockedSVGElement } from '@joint/vitest-plugin-mock-svg';\n\nvi.spyOn(SVGElement.prototype as MockedSVGElement, 'getBBox')\n  .mockReturnValue({ x: 0, y: 0, width: 100, height: 20 } as DOMRect);\n```\n\n## License\n\nThe *@joint/vitest-plugin-mock-svg* library is licensed under the [MIT License](https://github.com/clientIO/joint/blob/master/packages/joint-vitest-plugin-mock-svg/LICENSE).\n\nCopyright © 2013-2026 client IO\n"},"items":[{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/clientIO/joint/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with\ncode in this repository.\n\n## Project Overview\n\nJointJS is a JavaScript/TypeScript diagramming library for building visual and\nno-code/low-code applications. It's a Yarn workspace monorepo with multiple\npackages.\n\n**Stack:** Node 22.14.0, Yarn 4.18.0, TypeScript 5.8 (5.9 in `joint-react`),\nGrunt (build), QUnit/Karma/Mocha/Jest (testing)\n\n## Common Commands\n\n```bash\n# Install dependencies\nyarn install\n\n# Build distribution files (runs Grunt)\nyarn dist\n\n# Run all tests (QUnit + Jest + TypeScript checks)\nyarn test\n\n# Run specific test types\nyarn test-server       # Server-side tests (Node.js)\nyarn test-client       # Client-side tests (Browser with Karma)\nyarn test-ts           # TypeScript type definition tests\n\n# Linting\nyarn lint              # Check all packages\nyarn lint-fix          # Auto-fix errors\n\n# Build bundles\nyarn build\nyarn build-bundles\n```\n\n### Running Single Tests\n\nFor joint-react Jest tests:\n```bash\ncd packages/joint-react\nyarn jest --testPathPatterns=\"ComponentName\"\n```\n\n## Architecture\n\n### Monorepo Packages (`/packages`)\n\n- **@joint/core** - Main diagramming library (MVC architecture, SVG rendering)\n- **@joint/react** - React bindings and hooks (Rollup + esbuild, Storybook)\n- **@joint/layout-directed-graph** - Graph layout algorithms\n- **@joint/layout-msagl** - Microsoft MSAGL layout integration\n- **@joint/shapes-general** - General-purpose diagram shapes\n- **@joint/eslint-config** - Shared ESLint configuration\n\n### Core Library Structure (`/packages/joint-core/src`)\n\nThe library follows an MVC pattern:\n\n- **`mvc/`** - Base MVC components: Model, View, Collection, Events, Dom\n- **`dia/`** - Diagramming API built on MVC:\n  - `Graph.mjs` - Graph data structure and cell management\n  - `Paper.mjs` - SVG canvas and rendering engine\n  - `Element.mjs` / `ElementView.mjs` - Diagram nodes\n  - `Link.mjs` / `LinkView.mjs` - Connections between elements\n- **`g/`** - Geometry library (points, lines, curves, polygons, matrices)\n- **`V/`** - Vectorizer for SVG manipulation\n- **`connectors/`** - Link path strategies (line, curve, bezier, jumpover)\n- **`routers/`** - Link routing algorithms (manhattan, orthogonal, metro)\n- **`anchors/`** - Connection point anchors on elements\n- **`highlighters/`** - Visual highlighting mechanisms\n- **`linkTools/`**, **`elementTools/`** - Interactive manipulation tools\n- **`shapes/`** - Built-in shapes (standard)\n- **`layout/`** - Built-in layouts (port, port label)\n- **`alg/`** - Graph algorithms (DFS, BFS, shortest path)\n\n### Main Export (`/packages/joint-core/src/core.mjs`)\n\n```javascript\nexport { anchors, linkAnchors, config, connectionPoints, connectionStrategies,\n         connectors, dia, env, highlighters, layout, mvc, routers, setTheme,\n         util, version, linkTools, elementTools, V, Vectorizer, g };\n```\n\n## Test Organization\n\n- **`/packages/joint-core/test/jointjs/`** - QUnit tests for core functionality\n- **`/packages/joint-core/test/jointjs-nodejs/`** - Mocha tests run by\n  `test-server`\n- **`/packages/joint-core/test/geometry/`** - Geometry library tests\n- **`/packages/joint-core/test/vectorizer/`** - SVG vectorizer tests\n- **`/packages/joint-core/test/ts/`**, **`test/ts-exports/`** - TypeScript\n  definition validation\n- **`/packages/joint-react/src/`** - Jest tests with @testing-library/react,\n  colocated in `__tests__` folders\n\n## Changesets\n\nReleasable changes need a changeset in `/.changeset`. Before writing one, read\nthe **Changeset format** section of `CONTRIBUTING.md` - it defines the required\nfrontmatter, the `scope - description` changelog style (`dia.Paper`, `anchors`,\n`<Paper />`, ...), and the backtick/`()` conventions - and follow it exactly.\n\n## Key Patterns\n\n- **Change tracking:** Models emit `change:*` events; `changeId` event triggers\n  only when ID actually changes\n- **Workspace commands:** Use `yarn workspaces foreach --all -tvv run <cmd>`\n  for cross-package operations\n- **TypeScript:** Strict mode enabled; the settings are per package, not\n  repo-wide. `tsconfig.common.json` adds `noUnusedLocals`,\n  `noUnusedParameters` and `noImplicitReturns`, and is used by `joint-cli`,\n  `joint-decorators` and the `shapes-general` packages - `joint-core` and\n  `joint-react` each have their own\n- **ESLint:** Uses flat config format (v9) via `@joint/eslint-config`\n","isInternal":false,"tokens":1171,"sizeBytes":4389},{"name":"CLAUDE.md","path":"packages/joint-react/CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/clientIO/joint/HEAD/packages/joint-react/CLAUDE.md","title":"Joint-react Skill","category":"anthropic-skill","format":"markdown","content":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with\ncode in this repository.\n\n## Project Overview\n\n`@joint/react` is the React bindings library for JointJS. It provides React\ncomponents and hooks for building diagramming applications with JointJS,\nrendering diagram elements as React components via SVG foreignObject portals.\n\n**Stack:** TypeScript 5.9, React 19 (peer: >=18), Rollup + esbuild (build), Vite\n(storybook/dev), Jest 30 + @testing-library/react (testing), ESLint 9 flat\nconfig, Storybook 10\n\n## Common Commands\n\n```bash\n# Build (Rollup + esbuild → dist/cjs, dist/esm, dist/types)\nyarn build\n\n# Run all checks (typecheck + knip + jest, once against React 19 and once\n# against React 18)\nyarn test\n\n# Run individual checks\nyarn typecheck              # tsc --noEmit\nyarn lint                   # ESLint\nyarn lint-fix               # ESLint with auto-fix\nyarn jest                   # Jest tests only\n\n# Run a single test file\nyarn jest --testPathPatterns=\"use-elements\"\n\n# Run tests in watch mode\nyarn jest --watch\n\n# Storybook (dev server on :6006)\nyarn storybook\n\n# Build storybook\nyarn build-storybook\n```\n\n## Changesets\n\nReleasable changes need a changeset - the format is defined in the **Changeset\nformat** section of the repository's `CONTRIBUTING.md`. Two things are specific\nto this package:\n\n- The changelog scope is the exported component or hook rather than a JointJS\n  namespace - components as JSX tags, hooks by name:\n\n  ```markdown\n  <Paper /> - fix the visual grid to redraw reactively when `drawGrid` changes\n  useCells - fix ghost cells reported after `resetCells()`\n  ```\n\n- `@joint/react` is versioned independently. It is not part of the\n  `@joint/core` linked group, and because it depends on core via `workspace:^`,\n  a `@joint/core` minor release does not drag it along - only a major does.\n\n# Clean Code & Style Guidelines\n\n## General\n\n- Prefer clear, explicit code over clever one-liners. Maintainability > brevity.\n- When editing existing code, preserve the existing style unless I explicitly\n  ask for a refactor.\n- Leave code cleaner than you found it. Continuously refactor small things\n  instead of accumulating technical debt.\n\n## Constants Over Magic Numbers\n\n- Replace hard-coded values with named constants.\n- Use descriptive constant names that explain the value's purpose.\n- Keep constants at the top of the file\n\n## Meaningful Names\n\n- Variables, functions, and classes should reveal their purpose.\n- Names should explain **why** something exists and **how** it is used.\n- Avoid abbreviations unless they are truly universal (e.g. `id`, `URL`,\n  `HTML`).\n- Boolean names must be self-descriptive and read like conditions:\n  - Use prefixes: `is`, `has`, `should`, `can`, `must`, `needs` (e.g. `isOpen`,\n    `hasError`, `shouldRetry`).\n  - Avoid generic names like `flag`, `ok`, `state`, `enabled` without context.\n- Function names should describe behavior, not implementation details.\n\n## File Naming (TypeScript / JavaScript)\n\n- All `.ts`, `.tsx`, `.js`, `.jsx` files must use **kebab-case**:\n  - Examples: `user-profile.ts`, `something-table.tsx`\n- React component files:\n  - File name: kebab-case (e.g. `user-profile-card.tsx`).\n  - Component name inside: `PascalCase` (e.g. `UserProfileCard`).\n\n## Smart Comments\n\n- Do **not** comment on what the code does if the code can be made\n  self-explanatory instead.\n- Prefer to improve the code so it reads clearly without comments.\n- Use comments to explain:\n  - Why something is done in a non-obvious way.\n  - Important constraints, assumptions, or business rules.\n  - Non-obvious side effects.\n- Document APIs, complex algorithms, and tricky edge cases.\n\n## Single Responsibility\n\n- Each function should do exactly one thing.\n- Functions should be small and focused.\n- If a function needs a long comment to explain what it does, consider\n  splitting it into smaller functions.\n- JSDoc is mandatory for all exported functions.\n\n## DRY (Don't Repeat Yourself)\n\n- Extract repeated code into reusable functions or utilities.\n- Share common logic through proper abstractions.\n- Maintain single sources of truth for configuration, constants, and shared\n  logic.\n\n## Clean Structure\n\n- Keep related code together.\n- Organize code in a logical hierarchy (modules, domains, features).\n- Use consistent file and folder naming conventions.\n- Avoid \"god\" files that own too many responsibilities.\n\n## Encapsulation\n\n- Hide implementation details behind clear interfaces.\n- Expose only what is needed from a module.\n- Move nested conditionals or complex logic into well-named helper functions.\n\n## TypeScript\n\n- Never use `any` in new code:\n  - Prefer proper types, generics, unions, or `unknown` + narrowing.\n  - If `any` is unavoidable at a boundary (e.g. 3rd-party library), isolate it\n    and add a clear `TODO` explaining why.\n- Write `strict`-friendly code:\n  - No implicit `any`.\n  - Avoid sloppy or unnecessary `as` casts.\n  - Avoid `as any` and non-null assertions (`!`) unless absolutely necessary;\n    justify them with a short comment.\n- Use `interface`/`type` aliases for data structures instead of loose objects.\n- Keep function signatures explicit and well-typed, especially for exported\n  functions and public APIs.\n- Prefer readonly/immutable data where reasonable (e.g. `readonly` props, avoid\n  mutating function arguments).\n\n## Performance Mindset\n\n- Avoid unnecessary allocations and copies:\n  - No pointless spreading (`{ ...obj }`) or `JSON.parse(JSON.stringify(...))`\n    in hot paths.\n- Avoid creating new functions/objects inside tight loops or very frequently\n  rendered components unless memoized.\n- Use `useMemo` / `useCallback` **only** when they prevent real, recurring work\n  or re-renders, not everywhere by default.\n- Be careful not to introduce extra re-renders by changing prop shapes\n  unnecessarily.\n- Prefer algorithmic improvements and proper data structures over\n  micro-optimizations when performance is a concern.\n\n## Code Quality Maintenance\n\n- Refactor continuously in small, safe steps.\n- Fix technical debt early when you touch a piece of code.\n- Prefer explicit, predictable behavior over clever tricks.\n- Keep the codebase internally consistent.\n\n## Testing & Safety\n\n- For non-trivial logic changes, prefer adding or updating tests.\n- Write tests before fixing bugs when possible (regression tests).\n- Keep tests readable, deterministic, and maintainable.\n- Test edge cases, error conditions, and boundary values.\n- Avoid relying on implementation details; test behavior and contracts.\n- Performance is must have, so test it.\n","isInternal":false,"tokens":1532,"sizeBytes":6603},{"name":"README.md","path":"packages/joint-cli/README.md","rawUrl":"https://raw.githubusercontent.com/clientIO/joint/HEAD/packages/joint-cli/README.md","title":"joint-cli Documentation","category":"plugin-manifest","format":"markdown","content":"# @joint/cli\n\nCommand-line tool for [JointJS](https://jointjs.com).\n\n## Quick Start\n\n```bash\nnpx @joint/cli list\nnpx @joint/cli download kitchen-sink/js\n```\n\n## Installation\n\n```bash\nnpm install -g @joint/cli\n```\n\nOnce installed globally, the `joint` command is available:\n\n```bash\njoint list\njoint download kitchen-sink/js\n```\n\n## Commands\n\n### `joint list`\n\nList available examples from the [joint-demos](https://github.com/clientIO/joint-demos) repository.\n\n```bash\njoint list\n```\n\n### `joint download <name> [dest]`\n\nDownload an example into the current working directory.\n\n```bash\n# Downloads into ./kitchen-sink-js/\njoint download kitchen-sink/js\n\n# Downloads into ./my-app/\njoint download kitchen-sink/js my-app\n\n# Downloads into the current directory (must be empty or use --force)\njoint download kitchen-sink/js .\n```\n\nIf the destination directory already exists, use `--force` to overwrite:\n\n```bash\njoint download kitchen-sink/js --force\njoint download kitchen-sink/js . --force\n```\n\n## Options\n\n| Option | Description |\n|---|---|\n| `--help`, `-h` | Show help message |\n| `--version`, `-v` | Show version number |\n| `--owner <name>` | GitHub repo owner (default: `clientIO`) |\n| `--branch <name>` | GitHub repo branch (default: `main`) |\n| `--force` | Overwrite existing files when downloading |\n\n### Working with forks\n\nUse `--owner` and `--branch` to list and download examples from a fork:\n\n```bash\njoint list --owner myGitHubUser\njoint download kitchen-sink/js --owner myGitHubUser --branch dev\n```\n\n## Environment Variables\n\n| Variable | Description |\n|---|---|\n| `GITHUB_TOKEN` | Optional GitHub token to avoid API rate limiting |\n\n## Requirements\n\n- Node.js >= 22\n- Git\n","isInternal":false,"tokens":431,"sizeBytes":1688},{"name":"README.md","path":"packages/joint-core/demo/custom-embedding/README.md","rawUrl":"https://raw.githubusercontent.com/clientIO/joint/HEAD/packages/joint-core/demo/custom-embedding/README.md","title":"custom-embedding Documentation","category":"plugin-manifest","format":"markdown","content":"# JointJS Custom Embedding Demo\n\n## Setup\n\nUse Yarn to run this demo.\n\nYou need to build *JointJS* first. Navigate to the root folder and run:\n```bash\nyarn install\nyarn run build\n```\n\nThen navigate to this directory and open `index.html` in your browser:\n```bash\nopen index.html\n```\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n","isInternal":false,"tokens":120,"sizeBytes":459},{"name":"README.md","path":"packages/joint-core/demo/custom-shapes/README.md","rawUrl":"https://raw.githubusercontent.com/clientIO/joint/HEAD/packages/joint-core/demo/custom-shapes/README.md","title":"custom-shapes Documentation","category":"plugin-manifest","format":"markdown","content":"# JointJS Custom Shapes Demo\n\n## Setup\n\nUse Yarn to run this demo.\n\nYou need to build *JointJS* first. Navigate to the root folder and run:\n```bash\nyarn install\nyarn run build\n```\n\nNavigate to this directory, then run:\n```bash\nyarn install\nyarn start\n```\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n","isInternal":false,"tokens":116,"sizeBytes":431},{"name":"README.md","path":"packages/joint-core/demo/devs/README.md","rawUrl":"https://raw.githubusercontent.com/clientIO/joint/HEAD/packages/joint-core/demo/devs/README.md","title":"devs Documentation","category":"plugin-manifest","format":"markdown","content":"The **Devs** demo introduces a ready-to-use shape with predefined input & output port groups and simplified API.\n\n### joint.shapes.devs.Model\n\nThe Model shape implements simple API on top of the JointJS built-in ports. It splits ports into two semantic groups (**in** and **out**) and adds convenient methods for adding and removing ports.\n\n| Attribute | Description |\n| --- | --- |\n| inPorts | an array of all input ports |\n| outPorts | an array of all output ports |\n\n##### shapes.devs.Model.addInPort\n\n    element.addInPort(port, [opt])\n\nAdd a single port into the \\`inPorts\\` array, where \\`port\\` is a name of the port.\n\n##### shapes.devs.Model.addOutPort\n\n    element.addOutPort(port, [opt])\n\nAdd a single port into the \\`outPorts\\` array, where \\`port\\` is a name of the port.\n\n##### shapes.devs.Model.changeInGroup\n\n    element.changeInGroup(properties, [opt])\n\nChange the settings for the input ports, where \\`properties\\` is an object with a [group configuration](https://resources.jointjs.com/docs/jointjs/v3.7/joint.html#dia.Element.ports.interface). It extends the previous settings with the new configuration by default. Pass `{ rewrite: true }` via `opt` to invalidate the previous settings.\n\n##### shapes.devs.Model.changeOutGroup\n\n    element.changeOutGroup(properties, [opt])\n\nChange the settings for the output ports, where \\`properties\\` is an object with a [group configuration](https://resources.jointjs.com/docs/jointjs/v3.7/joint.html#dia.Element.ports.interface). It extends the previous settings with the new configuration by default. Pass `{ rewrite: true }` via `opt` to invalidate the previous settings.\n\n##### shapes.devs.Model.removeInPort\n\n    element.removeInPort(port, [opt])\n\nRemove a port from an element from the \\`inPorts\\` array, where \\`port\\` is a name of the port.\n\n##### shapes.devs.Model.removeOutPort\n\n    element.removeOutPort(port, [opt])\n\nRemove a port from an element from the \\`outPorts\\` array, where \\`port\\` is a name of the port.\n\n### joint.shapes.devs.Link\n\nThe `devs.Link` extends the `joint.shapes.standard.Link` and changes the link appearance.\n\n#### Example usage\n\n    const shape = new joint.shapes.devs.Model({\n        position: {\n            x: 100,\n            y: 100\n        },\n        inPorts: ['in1', 'in2'],\n        outPorts: ['out1', 'out2']\n    });\n\n    shape.addTo(graph);\n\n    // adding/removing ports dynamically\n    shape.addInPort('in3');\n    shape.removeOutPort('out1').addOutPort('out3');\n\n    const link = new joint.shapes.devs.Link({\n        source: {\n            id: shape.id,\n            port: 'out3'\n        },\n        target: {\n            x: 200,\n            y: 300\n        }\n    });\n    link.addTo(graph);\n\n    // moving the input ports from `left` to `top`\n    shape.changeInGroup({ position: 'top' });\n    // moving the output ports 'right' to 'bottom'\n    shape.changeOutGroup({ position: 'bottom' });\n\n\n#### Hierarchical diagrams\n\nThere are two more shapes designed for hierarchical diagrams as part of the demo - `devs.Atomic` and `devs.Coupled`. They inherit from the `devs.Model` and differ only in the color and size. The purpose of these shapes is to enable you to implement a custom logic in your application based on their type. For instance a `devs.Coupled` can embed `devs.Atomic`'s but not the other way round as shown in the demo below.\n\nWhen working with hierarchical diagrams there is a few methods, that might come in handy.\n\n[`coupled.embed(atomic)`](https://resources.jointjs.com/docs/jointjs/v3.7/joint.html#dia.Element.prototype.embed) that can put the \\`atomic\\` shape into the \\`coupled\\`.\n\n[`coupled.fitToChildren()`](https://resources.jointjs.com/docs/jointjs/v3.7/joint.html#dia.Element.prototype.fitToChildren) that resizes the \\`coupled\\` shape such that all embedded elements are visually contained within it.\n\n[`link.reparent()`](https://resources.jointjs.com/docs/jointjs/v3.7/joint.html#dia.Link.prototype.reparent) that finds the best parent for the \\`link\\` based on the source and target element.\n","isInternal":false,"tokens":977,"sizeBytes":4017},{"name":"README.md","path":"packages/joint-core/demo/elk/README.md","rawUrl":"https://raw.githubusercontent.com/clientIO/joint/HEAD/packages/joint-core/demo/elk/README.md","title":"elk Documentation","category":"plugin-manifest","format":"markdown","content":"# JointJS Eclipse Layout Kernel Graph Demo\n\n## Setup\n\nUse Yarn to run this demo.\n\nYou need to build *JointJS* first. Navigate to the root folder and run:\n```bash\nyarn install\nyarn run build\n```\n\nNavigate to this directory, then run:\n```bash\nyarn install\nyarn start\n```\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n","isInternal":false,"tokens":118,"sizeBytes":445},{"name":"README.md","path":"packages/joint-core/demo/rough/README.md","rawUrl":"https://raw.githubusercontent.com/clientIO/joint/HEAD/packages/joint-core/demo/rough/README.md","title":"rough Documentation","category":"plugin-manifest","format":"markdown","content":"# JointJS RoughJS Demo\n\n## Setup\n\nUse Yarn to run this demo.\n\nYou need to build *JointJS* first. Navigate to the root folder and run:\n```bash\nyarn install\nyarn run build\n```\n\nNavigate to this directory, then run:\n```bash\nyarn install\n```\n\nThen open `index.html` in your browser:\n```bash\nopen index.html\n```\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n","isInternal":false,"tokens":131,"sizeBytes":483},{"name":"README.md","path":"packages/joint-core/demo/ts-demo/README.md","rawUrl":"https://raw.githubusercontent.com/clientIO/joint/HEAD/packages/joint-core/demo/ts-demo/README.md","title":"ts-demo Documentation","category":"plugin-manifest","format":"markdown","content":"# JointJS TypeScript Demo\n\n## Setup\n\nUse Yarn to run this demo.\n\nYou need to build *JointJS* first. Navigate to the root folder and run:\n```bash\nyarn install\nyarn run build\n```\n\nNavigate to this directory, then run:\n```bash\nyarn install\nyarn start\n```\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n","isInternal":false,"tokens":115,"sizeBytes":428},{"name":"README.md","path":"packages/joint-core/demo/vuejs/README.md","rawUrl":"https://raw.githubusercontent.com/clientIO/joint/HEAD/packages/joint-core/demo/vuejs/README.md","title":"vuejs Documentation","category":"plugin-manifest","format":"markdown","content":"# JointJS VueJS Demo\n\n## Setup\n\nUse Yarn to run this demo.\n\nYou need to build *JointJS* first. Navigate to the root folder and run:\n```bash\nyarn install\nyarn run build\n```\n\nNavigate to this directory, then run:\n```bash\nyarn install\n```\n\nThen open `index.html` in your browser:\n```bash\nopen index.html\n```\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n","isInternal":false,"tokens":131,"sizeBytes":481},{"name":"README.md","path":"packages/joint-core/README.md","rawUrl":"https://raw.githubusercontent.com/clientIO/joint/HEAD/packages/joint-core/README.md","title":"joint-core Documentation","category":"plugin-manifest","format":"markdown","content":"# JointJS — The JavaScript diagramming library you don't replace at scale\n\n[JointJS](https://jointjs.com) is a proven JavaScript/TypeScript diagramming library for building complex, production-scale diagramming applications.\n\nTeams use it to build AI workflow builders, flowchart and process editors, BPMN modelers, SCADA/HMI and monitoring dashboards, data modeling tools, energy network diagrams, electronic design tools, and org chart builders.\n\nJointJS is framework-agnostic, with first-class support for React.\n\n[![Use Cases](https://user-images.githubusercontent.com/3967880/200360293-808f148c-32af-4f46-bec1-b4ae4e1592a0.jpg)](https://user-images.githubusercontent.com/3967880/200360293-808f148c-32af-4f46-bec1-b4ae4e1592a0.jpg)\n\nFurther information, examples and documentation can be found at [jointjs.com](https://jointjs.com).\n\n- 🚀 [Getting started guide](https://docs.jointjs.com)\n- 💡 Ask a question or share feedback in our [GitHub discussions](https://github.com/clientIO/joint/discussions)\n- 🖊️ [180+ demo apps](https://www.jointjs.com/demos) to kickstart your development\n- 🤖 [MCP server integration](https://mcp.jointjs.com/mcp) for AI coding agents\n- ⚛️ **New:** [JointJS for React](https://www.jointjs.com/react-diagrams) — build diagramming UIs in your React app\n\n## Use cases\n\nJointJS is used to build production diagramming applications across many domains:\n\n| Domain | Examples |\n|---|---|\n| Workflow & process | Flowchart editors, process automation, activity and sequence diagrams |\n| AI workflow builders | Node-based AI pipeline and agent builders, marketing automation studios |\n| BPMN | Business process modelers, swimlanes, Visio BPMN import/export |\n| Data modeling | ER diagrams, database schema designers, data mapping tools |\n| UML & software modeling | Class, sequence, statechart and use-case diagrams |\n| Industrial / SCADA & HMI | P&ID diagrams, monitoring and control dashboards |\n| Energy networks | Power and telecom network diagrams, DWDM circuits |\n| Electronic design | Logic circuits, wiring and cable diagrams |\n| Org charts | Organizational charts, hierarchy and tree builders |\n| Timelines | Project and milestone timelines, Gantt and PERT charts |\n\nExplore all [180+ demo apps](https://www.jointjs.com/demos) that serve as boilerplates for your project.\n\n## Framework integrations\n\nJointJS is framework-agnostic and works with any frontend stack. React has first-class, native support; the others have integration guides and examples:\n\n- [**React**](https://docs.jointjs.com/react) — native components and hooks (JointJS for React)\n- [Angular](https://docs.jointjs.com/learn/integration/angular/)\n- [Vue](https://docs.jointjs.com/learn/integration/vue)\n- [Svelte](https://docs.jointjs.com/learn/integration/svelte)\n- [Salesforce Lightning](https://docs.jointjs.com/learn/integration/lightning)\n\n## Features\n\n- essential diagram elements (rect, circle, ellipse, text, image, path)\n- ready-to-use diagram elements of well-known diagrams (ERD, Org chart, FSA, UML, PN, DEVS, ...)\n- custom diagram elements based on SVG or programmatically rendered\n- connecting diagram elements with links or links with links\n- customizable links, their arrowheads and labels\n- configurable link shapes (anchors, connection points, vertices, routers, connectors)\n- custom element properties and data\n- import/export from/to JSON format\n- customizable element ports (look and position, distributed around shapes or manually positioned)\n- rich graph API (traversal, dfs, bfs, find neighbors, predecessors, elements at point, ...)\n- granular interactivity\n- hierarchical diagrams (containers, embedded elements, child-parent relationships)\n- element & link tools (buttons, status icons, tools to change the shape of links, ...)\n- highlighters (provide visual emphasis to your elements)\n- automatic layouts (arrange the elements and links automatically)\n- highly event driven (react on any event that happens inside the diagram)\n- zoom in/out\n- touch support\n- MVC architecture\n- SVG based\n- ... a lot more\n\n## Using JointJS with AI coding agents\n\nJointJS is built to work well with AI-assisted development.\n\n**MCP server** — connect your AI coding agent (Claude Code, Cursor, and other MCP-compatible tools) to the JointJS Model Context Protocol server so it can search the official docs and demos while you build.\n\n```\nhttps://mcp.jointjs.com/mcp\n```\n\nExposes `search_docs` and `search_demos` so your agent writes correct JointJS code against the real API instead of guessing.\n\n## Supported browsers\n\n- Latest Google Chrome (including mobile)\n- Latest Firefox\n- Latest Safari (including mobile)\n- Latest Microsoft's Edge\n- Latest Opera\n\n## Commercial version\n\nThe open-source library covers core diagramming. **[JointJS+](https://www.jointjs.com/jointjs-plus)** adds production-ready plugins (stencils, inspectors, toolbars, advanced layouts, import/export, and more), professional demo apps (including source code), JointJS for React, and direct support from the team that builds the library. [Start a free 30-day trial.](https://www.jointjs.com/free-trial)\n\n## Development Environment\n\nIf you want to work on *JointJS* locally, use the following guidelines to get started.\n\n### Dependencies\n\nMake sure you have the following dependencies installed on your system:\n\n- [Node.js](https://nodejs.org/)\n- [grunt-cli](http://gruntjs.com/using-the-cli)\n- [git](https://git-scm.com/)\n- [yarn](https://yarnpkg.com/getting-started/install)\n\nThe installation requires Node version 22.14.0, to avoid syntax errors during installation.\n\nMake sure that you are using Yarn version >= 2.0.0, so that you have access to [Yarn workspace ranges](https://yarnpkg.com/features/workspaces#workspace-ranges-workspace) functionality. If you are using [Volta](https://volta.sh/), it will automatically read this restriction from `package.json`.\n\n### Setup\n\nClone this git repository:\n\n```\ngit clone https://github.com/clientIO/joint.git\n```\n\nNavigate to the `joint` directory:\n\n```\ncd joint\n```\n\nInstall all dependencies:\n\n```\nyarn install\n```\n\nGenerate distribution files from the source code:\n\n```\nyarn run dist\n```\n\nYou are now ready to browse our example applications, which combine functionality from multiple JointJS packages:\n\n```\ncd examples\n```\n\nRefer to each application's `README.md` file for additional instructions.\n\nYou can also browse the demo applications of our JointJS Core package:\n\n```\ncd packages/joint-core/demo\n```\n\nMost demos can be run by simply opening the `index.html` file in your browser. Some demos have additional instructions, which you can find in their respective `README.md` files.\n\n### Tests\n\nTo run all tests:\n\n```\nyarn run test\n```\n\nTo run only the server-side tests:\n\n```\nyarn run test-server\n```\n\nTo run only the client-side tests:\n\n```\nyarn run test-client\n```\n\nTo run only TypeScript tests:\n\n```\nyarn run test-ts\n```\n\n### Lint\n\nTo check for linting errors in `src` and `types` directories:\n\n```\nyarn run lint\n```\n\nTo auto fix errors, run eslint for `src` and `types` directories:\n\n```\nyarn run lint-fix\n```\n\n### Code Coverage Reports\n\nTo output a code coverage report in HTML:\n\n```\nyarn run test-coverage\n```\n\nTo output a code coverage report in [lcov format](http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php):\n\n```\nyarn run test-coverage-lcov\n```\n\nThe output for all unit tests will be saved in the `packages/joint-core/coverage` directory.\n\n## Contributors\n\n[![](https://contrib.rocks/image?repo=clientIO/joint)](https://github.com/clientIO/joint/graphs/contributors)\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n","isInternal":false,"tokens":1835,"sizeBytes":7723},{"name":"README.md","path":"packages/joint-decorators/README.md","rawUrl":"https://raw.githubusercontent.com/clientIO/joint/HEAD/packages/joint-decorators/README.md","title":"joint-decorators Documentation","category":"plugin-manifest","format":"markdown","content":"# JointJS Decorators\n\nECMAScript / TypeScript decorator for defining *[JointJS](https://www.jointjs.com/)* shapes.\n\nThis library fully depends on [JointJS](https://github.com/clientio/joint) (*>=3.5*), so please read its README before using this library.\n\n## Setup\n\nEnable the [experimentalDecorators](https://www.typescriptlang.org/docs/handbook/decorators.html) compiler option in your `tsconfig.json`.\n\nThen install JointJS Decorators from NPM:\n```bash\nnpm i -S @joint/decorators\n```\n\n## License\n\n[Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/)\n\n## Usage\n\nThere are a few class decorators:\n\n- [`@Model`](#Model)\n- [`@View`](#View)\n\nAnd several class member decorators:\n\n- [`@Function`](#Function)\n- [`@On`](#On)\n- [`@SVGAttribute`](#SVGAttribute)\n\n---\n\n## <a id=\"Model\"></a> @Model(options: ModelOptions)\n\nThe decorator allows you to:\n- paste an existing SVG and use it as the model's markup\n- keep your SVG attributes in sync with your model's attributes\n- transform data using functions\n- introduce a new SVG attribute or change the behavior of an existing one\n\n```ts\nimport { dia } from '@joint/core';\nimport { Model } from '@joint/decorators';\n\n@Model({\n```\n```handlebars\n    template: `\n        <g>\n            <rect\n                width=\"calc(w)\"\n                height=\"calc(h)\"\n                :fill=\"{{color}}\"\n                stroke=\"black\"\n            />\n            <text\n                x=\"calc(0.5*w)\"\n                y=\"calc(0.5*h)\"\n                text-anchor=\"middle\"\n                text-vertical-anchor=\"middle\"\n                font-size=\"14\"\n                font-family=\"sans-serif\"\n                fill=\"black\"\n            >{{firstName}} {{lastName}}</text>\n        </g>\n    `,\n```\n```ts\n    attributes: {\n        color: 'red',\n        firstName: 'John',\n        lastName: 'Doe'\n    }\n})\nclass MyElement extends dia.Element {\n\n}\n```\n\n### ModelOptions\n\n| Option | Description | Optional\n|---|---|---|\n| [template](#Model.template) | the SVG string markup of the model | No |\n| [attributes](#Model.attributes) | the default attributes of the model | Yes |\n| [namespace](#Model.namespace) | the namespace for the model class to be added to | Yes |\n\n### <a id=\"Model.template\"></a> **template**\n\nThe decorator uses an SVG-based template syntax that allows you to declaratively bind the rendered DOM to the underlying model's data.\nAll templates are syntactically valid SVG that can be parsed by spec-compliant browsers and SVG parsers.\n\n> While using the SVG XML string in the markup attribute is not recommended (every cell view needs to parse the string and it might affect the performance), the parsing of the decorator's template runs only once per class (translating it into [JSON markup](https://resources.jointjs.com/docs/jointjs/v3.5/joint.html#dia.Cell.markup) and defining event listeners needed for reactivity).\n\n#### <a id=\"Model.template.text-interpolation\"></a> Text Interpolation\n\nThe most basic form of data binding is text interpolation using the \"Mustache\" syntax (double curly braces):\n\n```handlebars\n<text font-size=\"14\">{{ label }}</text>\n```\n\nThe mustache tag will be replaced with the value of the `label` property from the corresponding model's instance. It will also be updated whenever the `label` property changes.\n\n#### <a id=\"Model.template.attributes\"></a> Attributes Binding\n\nTo bind to an SVG attribute to a model's attribute, add a colon symbol (`:`) before the attribute's name.\n\n```handlebars\n<rect :fill=\"color\" />\n```\n\nThe colon symbol, `:`, instructs the decorator to keep the SVG attribute in sync with the model's attribute.\nThe model's `color` value can be set this way.\n\n```ts\nmodel.set('color', 'red');\n```\n\nIf the bound value is `null`, then the SVG attribute will be removed from the rendered element.\n\n```ts\nmodel.set('color', null);\n```\n\nIt's possible to use mustaches inside binding expressions\n    to combine multiple model's attributes into a single result.\n\n```handlebars\n<rect :fill=\"rgb({{red}},{{green}},{{blue}})\" />\n```\n\n#### <a id=\"Model.template.functions\"></a> Calling Functions\n\nThe value of an attribute can be modified with [functions](#Function) before set/display.\n\n```handlebars\n<rect :stroke=\"color\" :fill=\"lighten(color)\"/>\n<text>{{ capitalize(label) }}</text>\n```\n> Functions called inside binding expressions will be called every time the cell view updates, so they should not have any side effects, such as changing data or triggering asynchronous operations.\n\nIt is possible to call a component-exposed method inside a binding expression only if the method is decorated with the [Function](#Function) decorator.\n\nThe function can accept any number of additional arguments. Every such argument shall be parsable with the [JSON.parse](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) function.\n\n```ts\n@Model({\n    template: `\n        <text y=\"10\">{{ maxLength(label1, 20) }}</text>\n        <text y=\"30\">{{ maxLength(label2, 10) }}</text>\n    `\n})\nclass MyElement extends dia.Element {\n\n    @Function()\n    maxLength(value: string, max: number) {\n        return value.substr(0, max);\n    }\n}\n```\n\nMultiple dependencies can be defined using an array.\n\n```handlebars\n<path :d=\"data([param1, param2])\"/>\n```\n\n> The function is run every time one or more attributes in the dependency array are changed.\n\n```ts\n@Model({\n    attributes: {\n        param1: 20,\n        param2: 30\n    },\n    template: `\n        <path :d=\"data([param1, param2])\" stroke=\"white\" fill=\"transparent\" />\n    `\n})\nclass Arrow extends dia.Element {\n    @Function()\n    data(param1: number, param2: number): string {\n        return `\n                M ${param1} 0\n                L calc(w) calc(0.5*h)\n                L ${param1}  calc(h)\n                V calc(h/2 + ${param2 / 2})\n                H 0\n                v -${param2}\n                H ${param1}\n                Z\n        `;\n    }\n}\n```\n\n#### <a id=\"Model.template.selectors\"></a> Selectors\n\nIf you want to modify any of the template attributes programmatically, you must add the `@selector` attribute to the SVG element.\n\n```handlebars\n<rect @selector=\"body\" stroke=\"black\" fill=\"red\">\n```\n\n```ts\nmodel.attr(['body', 'fill'], 'blue');\n```\n\nThe `<g>` wrapper in the template is added automatically and always has a `@selector` equal to `root`. In case you want to add SVG attributes to the root group, wrap the template with one of them.\n\n```handlebars\n<g @selector=\"root\" data-tooltip=\"My Tooltip\">\n    <rect @selector=\"body\" stroke=\"black\" fill=\"red\">\n</g>\n```\n\nTo create selectors pointing to multiple SVG elements at once, use `@group-selector`.\n\n```handlebars\n<rect @group-selector=\"rectangles\" fill=\"red\">\n<rect @group-selector=\"rectangles\" fill=\"blue\">\n<rect @group-selector=\"rectangles\" fill=\"green\">\n```\n\n```ts\n// Change the stroke of all rectangles\nmodel.attr(['rectangles', 'stroke'], 'black');\n```\n\n#### <a id=\"Model.template.caveats\"></a> Caveats\n\nSome JointJS attributes expect their value to be an object ([fill](https://resources.jointjs.com/docs/jointjs/v3.5/joint.html#dia.attributes.textWrap) & [stroke](https://resources.jointjs.com/docs/jointjs/v3.5/joint.html#dia.attributes.stroke) gradient, [filters](https://resources.jointjs.com/docs/jointjs/v3.5/joint.html#dia.attributes.filter), [markers](https://resources.jointjs.com/docs/jointjs/v3.5/joint.html#dia.attributes.sourceMarker) and [textWrap](https://resources.jointjs.com/docs/jointjs/v3.5/joint.html#dia.attributes.textWrap)).\n\nThe solution is to define the property inside the [attributes](#Model.attributes) (mixing the template attributes with explicit model attributes).\n\n```ts\nconst selector = 'label';\n\n@Model({\n    attributes: {\n        title: 'My Title',\n        attrs: {\n            [selector]: {\n                textWrap: {\n                    maxLineCount: 1,\n                    ellipsis: true\n                }\n            }\n        }\n    },\n    template: `\n        <text @selector=\"${selector}\">{{title}}</text>\n    `\n})\nclass MyElement extends dia.Element {\n\n}\n```\n\n### <a id=\"Model.attributes\"></a> **attributes**\n\nThe default attributes of the model. When creating an instance of the model, any unspecified attributes will be set to their default value.\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { Model } from '@joint/decorators';\n\n@Model({\n    attributes: {\n        color: 'red'\n    }\n})\nclass MyElement extends dia.Element {\n\n}\n```\n\nis equivalent to\n\n```ts\nimport { dia, shapes } from '@joint/core';\n\nclass MyElement extends dia.Element {\n\n    defaults() {\n        const attributes = {\n            color: 'red'\n        };\n        return {\n            ...super.defaults,\n            ...attributes,\n            type: 'MyElement'\n        }\n    }\n}\n```\n\n###  <a id=\"Model.namespace\"></a>  **namespace**\n\nSyntactic sugar for adding a model to the namespace.\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { Model } from '@joint/decorators';\n\n@Model({\n    namespace: shapes\n})\nclass MyElement extends dia.Element {\n\n}\n```\n\nis equivalent to\n\n```ts\nimport { dia, shapes } from '@joint/core';\n\nclass MyElement extends dia.Element {\n\n}\n\nObject.assign(shapes, {\n    'MyElement': MyElement\n});\n```\n\n---\n\n## <a id=\"View\"></a> @View(options: ViewOptions)\n\n### ViewOptions\n\n| Option | Description | Optional |\n|---|---|---|\n| namespace | the namespace for the view class to be added to | Yes\n| models | an array of model classes this view is to be used with | Yes\n\nDefine a new cell view, which is automatically used by 2 different models.\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { View } from '@joint/decorators';\n\n@View({\n    namespace: shapes\n    models: [MyElement, MyOtherElement]\n})\nclass MyElementView extends dia.ElementView {\n\n}\n```\n\nis equivalent to\n\n```ts\nimport { dia, shapes } from '@joint/core';\n\nclass MyElementView extends dia.ElementView {\n\n}\n\nObject.assign(shapes, {\n    'MyElementView': MyElementView,\n    'MyOtherElementView': MyElementView\n});\n```\n---\n## <a id=\"Function\"></a> @Function(name?: string)\n\nDefine functions to transform data (e.g strings, amounts, dates) to be used within the [template](#Model.template).\n\n```ts\n@Model({\n    template: `\n        <text>{{ capitalize(name) }}</text>\n    `,\n    attributes: {\n        name: 'john',\n    }\n})\nclass MyElement extends dia.Element {\n\n    @Function()\n    capitalize(value: string) {\n        return value.charAt(0).toUpperCase() + value.slice(1);\n    }\n}\n```\n\n---\n\n## <a id=\"SVGAttribute\"></a> @SVGAttribute(attributeName: string)\n\nIntroduce new SVG attributes or redefine existing ones.\n\n```ts\nimport { dia, g, attributes } from '@joint/core';\nimport { Model, SVGAttribute } from '@joint/decorators';\n\n@Model({\n    attributes: {\n        width: 140,\n        height: 100\n    },\n    template: `\n        <rect\n            line-style=\"dashed\"\n            stroke-width=\"2\"\n        />\n    `,\n})\nclass MyElement extends dia.Element {\n\n    /* `stroke-dasharray` that adjusts based on the current node's `stroke-width` */\n    @SVGAttribute('line-style')\n    setStrokeDasharray(\n        this: dia.CellView,\n        value: string,\n        rect: g.Rect,\n        node: SVGElement,\n        nodeAttrs: attributes.SVGAttributes\n    ) {\n        const { strokeWidth = 1 } = nodeAttrs;\n        let pattern;\n        switch (value) {\n            case 'dashed': {\n                pattern = `${4 * strokeWidth},${2 * strokeWidth}`;\n                break;\n            }\n            case 'dotted': {\n                pattern = `${strokeWidth},${strokeWidth}`;\n                break;\n            }\n            case 'solid': {\n                pattern = 'none';\n                break;\n            }\n            default: {\n                throw new Error('Invalid line-style value.');\n            }\n        }\n        node.setAttribute('stroke-dasharray', pattern);\n    }\n}\n```\n### SVGAttribute function signature\n\n| Argument | Description | Example |\n|---|---|---|\n| value | the right-hand side of the template's attribute | `\"dashed\"` |\n| rect | a rectangle describing the coordinate system the node is rendered in (if no `ref` attribute is in use, the value is the model's bounding box relative to the model's position, otherwise it is the relative bounding box of the node referenced by the `ref` attribute) | `new g.Rect(0, 0, 140, 100)` |\n| node | a rendered DOM [SVGElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement) | `<rect/> as SVGElement` |\n| nodeAttrs | an object with all defined attributes of the node | `{ lineStyle: \"dashed\", strokeWidth: \"2\" }` |\n\n---\n## <a id=\"On\"></a> @On(eventName: string)\n\n Decorate an event handler in the context of the method it refers to.\n\n```ts\nclass MyElementView extends dia.ElementView {\n\n    @On('click')\n    onClick() {\n        console.log('click!', this.model.id);\n    }\n}\n```\n\n---\n\nCopyright © 2013-2026 client IO\n","isInternal":false,"tokens":3092,"sizeBytes":12841},{"name":"README.md","path":"packages/joint-eslint-config/README.md","rawUrl":"https://raw.githubusercontent.com/clientIO/joint/HEAD/packages/joint-eslint-config/README.md","title":"joint-eslint-config Documentation","category":"plugin-manifest","format":"markdown","content":"# Shared package for using eslint config.\n\nConfiguring ESLint in multiple projects is a repetitive task, so we created this package to share a common ESLint configuration across all JointJS packages.\n\n### Issue with running `yarn lint`\n\nIf there is an issue with running `yarn lint`, try removing `node_modules/` and re-installing from monorepo root! This should fix the issue, as sometimes, when `yarn install` is used in a monorepo package directly, it **overwrites** default `eslint-configs`.\n\nTo avoid these issues, **use `yarn install` directly from monorepo root folder**, instead of per package.\n","isInternal":false,"tokens":137,"sizeBytes":603},{"name":"README.md","path":"packages/joint-layout-directed-graph/README.md","rawUrl":"https://raw.githubusercontent.com/clientIO/joint/HEAD/packages/joint-layout-directed-graph/README.md","title":"joint-layout-directed-graph Documentation","category":"plugin-manifest","format":"markdown","content":"# JointJS Directed Graph Layout\n\nA module for automatic layout of directed graphs for *[JointJS](https://www.jointjs.com/)*.\n\nThis library fully depends on [JointJS](https://github.com/clientio/joint) (*>=4.0*), so please read its `README.md` before using this library.\n\n### Tests\n\nGenerate distribution files from the source code:\n```bash\nyarn run dist\n```\n\nTo run all tests:\n```bash\nyarn run test\n```\n\n## License\n\n[Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/)\n\nCopyright © 2013-2026 client IO\n","isInternal":false,"tokens":141,"sizeBytes":519},{"name":"README.md","path":"packages/joint-layout-elk/README.md","rawUrl":"https://raw.githubusercontent.com/clientIO/joint/HEAD/packages/joint-layout-elk/README.md","title":"joint-layout-elk Documentation","category":"plugin-manifest","format":"markdown","content":"# JointJS ELK Layout\n\nA module for automatic layout of *[JointJS](https://www.jointjs.com)* graphs using the [Eclipse Layout Kernel (ELK)](https://www.eclipse.org/elk/), via its JavaScript port [elkjs](https://github.com/kieler/elkjs).\n\nThis library depends on [JointJS](https://github.com/clientio/joint) (*>=4.0*), so please read its `README.md` before using this library.\n\n`layout()` is a one-off, asynchronous transform: it builds an ELK graph from a JointJS graph (embedded elements become nested containers at any depth, ports and link labels are included), runs ELK's layout algorithm, and writes the result (positions, sizes, link vertices/anchors, label positions) back onto the graph. It does not keep the graph laid out afterwards - call it again after further changes.\n\n## 🚀 Quick Start\n\n### Installation\n\n```bash\nnpm install @joint/layout-elk\n```\n\n#### UMD build (script tags)\n\n`dist/umd/index.js` keeps `@joint/core` and `elkjs` external - load both first: `@joint/core` as the `joint` global, and `elkjs/lib/elk.bundled.js` as the `ELK` global, which `layout()` runs on the main thread. The package then is `joint.layout.ELK`.\n\n```html\n<script src=\"node_modules/@joint/core/dist/joint.min.js\"></script>\n<script src=\"node_modules/elkjs/lib/elk.bundled.js\"></script>\n<script src=\"node_modules/@joint/layout-elk/dist/umd/index.min.js\"></script>\n<script>\n    joint.layout.ELK.layout(graph).then(({ bbox }) => { /* ... */ });\n</script>\n```\n\n### Basic Usage\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { layout } from '@joint/layout-elk';\n\nconst graph = new dia.Graph({}, { cellNamespace: shapes });\nconst paper = new dia.Paper({\n    model: graph,\n    cellViewNamespace: shapes,\n    el: document.getElementById('paper'),\n});\n\nconst rect1 = new shapes.standard.Rectangle({ id: 'a', size: { width: 80, height: 40 }, attrs: { label: { text: 'A' }}});\nconst rect2 = new shapes.standard.Rectangle({ id: 'b', size: { width: 80, height: 40 }, attrs: { label: { text: 'B' }}});\nconst link = new shapes.standard.Link({ source: { id: 'a' }, target: { id: 'b' }});\n\ngraph.addCells([rect1, rect2, link]);\n\nconst { bbox } = await layout(graph, {\n    elkLayoutOptions: {\n        'elk.algorithm': 'layered',\n        'elk.direction': 'RIGHT',\n        'elk.edgeRouting': 'ORTHOGONAL'\n    }\n});\n```\n\n## 📖 API Reference\n\n### `layout(graphOrCells, options?): Promise<LayoutResult>`\n\n- `graphOrCells`: `dia.Graph | dia.Cell[]` - the graph to lay out, or the cells to lay out.\n- `options?`: `LayoutOptions` - layout configuration (see below).\n\nA list of cells is both the selection and the order:\n\n- **Selection** - only the given elements are laid out. An element whose parent isn't given is laid out as a top-level element.\n- **Links** - a link is laid out only if both of the elements it connects are given too. One that isn't - including a link connected to a point, to another link, or one that is in no graph, whose ends cannot be resolved at all - is left out of the layout entirely: no `exportLink` call, no route, and its vertices, anchors and label positions are left as they are.\n- **Order** - the top-level elements follow the order they are given in, and so do each container's children. The links follow their order too, inside each container.\n- Elements and links go in the same list, in any order - a link may come before the elements it connects. A cell listed more than once is laid out once.\n- ELK identifies everything by a string, while a JointJS id may be a string or a number - so two cells, or two ports of one element, whose ids differ only in type (`5` and `'5'`) would share one id in the ELK graph. `layout()` rejects rather than lay one of them out and leave the other where it was.\n\nThe layout is applied in a single `'layout'` batch on the graph the cells belong to, so it emits one combined change rather than one per element, port and link. An element that is in no graph is laid out and positioned like any other, but contributes no change events - which makes it useful as a container that only exists to group others for the layout.\n\n```ts\n// Lay out a selection, with its embedded elements and the links between its elements.\n// Everything else in the graph is left alone.\nawait layout(graph.getSubgraph(selectedElements, { deep: true }));\n```\n\n```ts\ninterface LayoutResult {\n    bbox: g.Rect;      // Tight bounding box of the laid out graph\n    elkGraph: ElkNode;  // The raw ELK layout result (e.g. for junction points)\n}\n```\n\n### `LayoutOptions`\n\n```ts\ninterface LayoutOptions {\n    // The ELK instance to lay out with, e.g. one running in a Web Worker (see `createWorkerElk()` below).\n    elk?: WorkerElk | ELK; // Default: a shared instance running on the main thread\n    // ELK layout options, in either spelling (see \"Layout option values\" below).\n    elkLayoutOptions?: ElkLayoutOptions; // Default: { 'elk.algorithm': 'layered', 'elk.hierarchyHandling': 'INCLUDE_CHILDREN', 'elk.layered.considerModelOrder.portModelOrder': 'true', 'elk.json.edgeCoords': 'ROOT' }\n    // Aborts the layout - `layout()` rejects with the signal's reason and applies nothing.\n    signal?: AbortSignal;\n\n    // Export callbacks (JointJS graph -> ELK graph) - see below.\n    exportElement?: ExportElementCallback;\n    exportPort?: ExportPortCallback;\n    exportPortLabel?: ExportPortLabelCallback;\n    exportLink?: ExportLinkCallback;\n    exportLinkLabel?: ExportLinkLabelCallback;\n\n    // Import callbacks (ELK layout result -> JointJS graph) - see below.\n    setElementAttributes?: SetElementAttributesCallback;\n    setPortAttributes?: SetPortAttributesCallback;\n    setLinkAttributes?: SetLinkAttributesCallback;\n}\n```\n\n### Layout option values\n\nELK takes every option value as a string. This package takes that spelling and the JointJS one, per option, so one object can mix them:\n\n```ts\nelkLayoutOptions: {\n    'elk.spacing.nodeNode': 6,                  // or '6'\n    'elk.rectpacking.orderBySize': true,        // or 'true'\n    'elk.padding': { left: 10, vertical: 40 },  // or '[top=40,left=10,bottom=40,right=0]'\n    'elk.position': { x: 10, y: 20 },           // or '10,20'\n    'elk.nodeLabels.placement': ['INSIDE', 'V_TOP', 'H_LEFT'],  // or 'INSIDE V_TOP H_LEFT'\n    'elk.direction': 'RIGHT'                    // enums are strings either way\n}\n```\n\nThe string spelling stays supported: it is what the ELK reference page linked from each option uses, and what options that arrive as data - a saved diagram, a backend's config - are already shaped like.\n\n- **Numbers and booleans** take `6` / `true` on any option ELK types as one.\n- **Paddings and margins** (`elk.padding`, `elk.margins`, `elk.spacing.portsSurrounding`, `elk.nodeLabels.padding`) take a [`dia.Sides`](https://docs.jointjs.com/api/util/normalizeSides/): a number for every side, or an object naming `top`/`right`/`bottom`/`left`/`horizontal`/`vertical`. **A side the value does not name is `0`**, not the option's own default - ELK's own string parser reads `'[top=40]'` the same way. That default applies only when the option is not set at all.\n- **Points** (`elk.position`, `elk.nodeSize.minimum`, `elk.port.anchor`) take `{ x, y }`, `g.Point` included.\n- **Sets of enum values** (`elk.nodeLabels.placement`, `elk.portLabels.placement`, `elk.contentAlignment`, `elk.nodeSize.constraints`, `elk.nodeSize.options`) take an array of the option's values, typed against its enum - `[]` is the empty set. ELK itself takes them only as one string of the values; elkjs throws on an array. **ELK ignores the whole value, without an error, if any one of its values is not one it knows** - `'INSIDE BOGUS'` does not even apply `INSIDE` - so the array, which is type-checked, is the safer spelling.\n- Anything else goes to ELK as it is, so ELK reports a value it cannot use rather than this package guessing at it.\n\nThe same applies to the `layoutOptions` set on a draft in an [export callback](#export-callbacks).\n\nTo be held to the JointJS spelling alone, annotate your own options with the `Strict` type - there is no flag on this package to switch:\n\n```ts\nimport type { StrictElkLayoutOptions } from '@joint/layout-elk';\n\nconst options: StrictElkLayoutOptions = {\n    'elk.spacing.nodeNode': 6,\n    // Type error: the strict spelling takes `6`, not `'6'`.\n    'elk.layered.spacing.nodeNodeBetweenLayers': '40'\n};\n```\n\n`Native*` is the other direction - ELK's own spelling alone - and is what `toNativeElkNode()` produces. Each has a per-element counterpart: `StrictElkNodeLayoutOptions`, `NativeElkEdgeLayoutOptions`, and so on.\n\n#### `toNativeElkNode(node: ElkNode): NativeElkNode`\n\nReturns a new graph with every layout option written out as the string ELK takes - throughout: the node's own options, its labels', its ports' (and theirs), the edges filed under it (and theirs), and each of its children. The graph passed in is left as it is.\n\n`layout()` does this for you. Reach for it when you build an ELK graph of your own and want to write its options in the JointJS spelling too.\n\n### Seeing what ELK is given\n\n`layout()` builds the ELK graph, runs ELK on it and applies the result back. All three are one step - there is no public way to do them separately, so that what the ids in between stand for stays this package's business.\n\nTo see the graph itself, pass an `elk` of your own: it is any object with `layout()` and `terminate()` (see [Running ELK in a Web Worker](#running-elk-in-a-web-worker)), and it is handed exactly what ELK is handed - every option already written out as a string, so it pastes straight into [elk-live](https://rtsys.informatik.uni-kiel.de/elklive/json.html).\n\n```ts\nimport ELK from 'elkjs/lib/elk.bundled.js';\n\nconst realElk = new ELK();\n\nawait layout(graph, {\n    elk: {\n        layout: (elkGraph) => {\n            console.log(JSON.stringify(elkGraph, null, 2));\n            return realElk.layout(elkGraph);\n        },\n        terminate: () => {}\n    }\n});\n```\n\nThe same hook is how you run ELK somewhere else - a Web Worker, a server, a cache - or change the graph on its way there. `layout()` still applies the result for you.\n\nOne thing to know if you also use [`signal`](#aborting-a-layout): a `terminate()` is how an `elk` says it can be stopped, so `layout()` hands it the signal instead of merely ignoring a late result. The wrapper above cannot stop `elkjs`, so an abort only takes effect once ELK settles. Leave the wrapper out of the abort path, or forward the signal to something that honours it.\n\n### Export callbacks\n\nEach receives an ELK draft already populated with what this package computed for that element/port/link/label, to mutate in place (e.g. to set `elk.*` `layoutOptions`). Returning `false` instead drops it from the ELK graph entirely - for `exportElement`, its whole subtree (embeds, ports, any connected edge) goes with it, and for `exportPort`, the links connected to that port, which ELK can no longer route to. A link left out this way keeps the route it already had.\n\n```ts\ntype ExportElementCallback = (params: { element: dia.Element; elkNode: ElkNodeDraft }) => void | false;\ntype ExportPortCallback = (params: { portId: string; element: dia.Element; elkPort: ElkPortDraft }) => void | false;\ntype ExportPortLabelCallback = (params: { portId: string; element: dia.Element; elkPortLabel: ElkLabelDraft }) => void | false;\ntype ExportLinkCallback = (params: { link: dia.Link; elkEdge: ElkEdgeDraft }) => void | false;\ntype ExportLinkLabelCallback = (params: { link: dia.Link; labelIndex: number; elkEdgeLabel: ElkLabelDraft }) => void | false;\n```\n\n### Import callbacks\n\nEach applies the ELK-computed attributes itself, in place of the package's own default (`element.set(...)`/`element.portProp(...)`/`link.set(...)`) - use one to redirect where the result goes, e.g. into a `transition()`.\n\n```ts\ntype SetElementAttributesCallback = (params: { element: dia.Element; attributes: { position: dia.Point; size?: dia.Size }; elkNode: ElkNode }) => void;\ntype SetPortAttributesCallback = (params: { element: dia.Element; portId: dia.Cell.ID; attributes: { position: { args: dia.Point }; label?: { position: { args: dia.Point } } }; elkPort: ElkPort; hasMoved: boolean }) => void;\ntype SetLinkAttributesCallback = (params: { link: dia.Link; attributes: { vertices: dia.Point[]; source?: dia.Link.EndJSON; target?: dia.Link.EndJSON; labels?: dia.Link.Label[] }; elkEdge: ElkExtendedEdge }) => void;\n```\n\n### Running ELK in a Web Worker\n\nBy default, `layout()` runs ELK on the main thread - nothing to set up with a bundler or in Node, and it works anywhere (browsers, Node/SSR, tests, and the UMD build once `elkjs` is loaded - see \"UMD build\" above). ELK blocks the page while it runs, though: a few milliseconds for a small graph, but up to seconds for one with thousands of elements. To keep the page responsive, run ELK in a Web Worker instead - start one with `createWorkerElk()` and pass it to `layout()` as `elk`:\n\n```ts\nimport { layout, createWorkerElk } from '@joint/layout-elk';\n\nconst elk = createWorkerElk(() => new Worker(new URL('@joint/layout-elk/worker', import.meta.url), { type: 'module' }));\n\nawait layout(graph, { elk });\n\n// Once no longer needed.\nelk.terminate();\n```\n\n#### `createWorkerElk(createWorker: () => Worker): WorkerElk`\n\n`createWorker` starts the worker - running `@joint/layout-elk/worker`, the package's worker script (ELK's own, `elkjs/lib/elk-worker.min.js`). Starting it is up to you, since only your bundler knows where the script ends up:\n\n- **webpack 5, Vite** (dev server and builds) - `new Worker(new URL('@joint/layout-elk/worker', import.meta.url), { type: 'module' })`, as above. Both bundle the worker script as a file of its own. With webpack, keep `output.publicPath` at `'auto'` (the default), so the file is found wherever the app is served from.\n- **Vite** - also `import ElkWorker from '@joint/layout-elk/worker?worker'`, then `createWorkerElk(() => new ElkWorker())`.\n- **No bundler** (e.g. the UMD build) - serve a copy of `elkjs/lib/elk-worker.min.js`, then `createWorkerElk(() => new Worker('/path/to/elk-worker.min.js'))`.\n\nThe returned `WorkerElk`:\n\n- **Starts the worker on its first layout**, then lays out every graph in it, one at a time - share one instance between layouts.\n- **Stops an aborted layout** (see `signal` below) by terminating the worker, if it is busy with it - a new worker takes over the layouts still waiting.\n- **Rejects the layout the worker crashes during** (e.g. out of memory) - a new worker takes over the layouts still waiting.\n- **Rejects every layout when the worker fails to load** (e.g. its script isn't served where `createWorker` starts it from) or can't be started (e.g. a CSP `worker-src` that blocks it) - with an error saying so, rather than running ELK on the main thread. The next layout tries again with a new worker.\n- **`terminate()`** terminates the worker - layouts not settled yet are rejected, and a later layout starts a new worker.\n\nAny other ELK instance works as `elk` too, e.g. `elkjs`'s own `new ELK({ workerUrl })` (`elkjs/lib/elk-api.js`) - without stopping an aborted layout, though (see below).\n\n### Aborting a layout\n\n`layout()` is asynchronous, so the graph may change while ELK is still computing - pass an `AbortSignal` to drop a layout that is no longer wanted (or takes too long). An aborted `layout()` rejects with the signal's reason (an `AbortError` `DOMException` by default) and applies nothing to the graph.\n\n```ts\nlet controller: AbortController | undefined;\n\nasync function runLayout() {\n    // Only the latest layout is applied.\n    controller?.abort();\n    controller = new AbortController();\n    try {\n        await layout(graph, { signal: controller.signal });\n    } catch (error) {\n        if ((error as Error).name !== 'AbortError') throw error;\n    }\n}\n\n// Give up after 5 seconds.\nawait layout(graph, { signal: AbortSignal.timeout(5000) });\n```\n\nELK can't stop a layout in progress, so a layout a `createWorkerElk()` worker is busy with is stopped by terminating the worker - a new one takes over the layouts still waiting. A layout on the main thread, or in any other `elk` instance, keeps running - only its result is ignored.\n\n## ⚠️ Caveats & Known Limitations\n\n- **Edge coordinates are graph-absolute** - `layout()` sets `elk.json.edgeCoords: 'ROOT'`, so ELK returns every edge's route points and labels relative to the root, whichever container the edge is in, and the default import applies them as they are. Overriding it (e.g. `'CONTAINER'`) is allowed, but the default import then misplaces vertices, end anchors and labels of edges inside containers - convert them yourself in `setLinkAttributes` (from `elkEdge`). The same applies to the raw `elkGraph` in `layout()`'s result.\n- **Node labels are not supported** - ELK's node-label placement assumes labels are layout participants, whereas JointJS labels are attrs inside the shape. Link labels are supported.\n- **Element rotation is not taken into account** - an ELK node is a box with a position and a size, with no rotation, so an element is laid out by the geometry it would have at `angle: 0` and ELK reserves the wrong area for a rotated one. `layout()` warns the first time it sees one.\n- **Ports keep their JointJS-computed position by default** - every element with ports is exported with `elk.portConstraints: 'FIXED_POS'`, so ELK keeps each port where the element's port groups place it and edges route to/from that exact spot. Opt into ELK repositioning/reordering them by overriding it (e.g. `'FIXED_SIDE'`/`'FREE'`) in `exportElement` - setting it in `elkLayoutOptions` has no effect, since the per-node value takes precedence. `layout()` also defaults `elk.layered.considerModelOrder.portModelOrder` to `'true'`, so that a port freed this way keeps the order of the element's `ports.items`; override it in `elkLayoutOptions` to let ELK reorder them to reduce edge crossings.\n- **Asynchronous** - unlike `@joint/layout-directed-graph`, `layout()` returns a `Promise`, since `elkjs` computes layouts asynchronously - even on the main thread.\n- **Main thread by default** - without an `elk` option, ELK runs on the main thread and blocks the page while it runs - see \"Running ELK in a Web Worker\" above. In the ESM build, the main-thread copy of ELK (`elkjs/lib/elk.bundled.js`) is imported dynamically, so bundlers split it into a chunk of its own, only loaded by the first layout without an `elk` option. The UMD build can't load a chunk - it uses the `ELK` global instead, which the page has to load first (see \"UMD build\" above).\n\n## 📄 License\n\n[Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/)\n\nThe code in this package is licensed under the Mozilla Public License 2.0, same as the rest of JointJS. It contains no ELK code: it only calls ELK through its API, and its TypeScript option types link to [ELK's option reference](https://eclipse.dev/elk/reference/options.html) instead of reproducing it.\n\nIt depends on [`elkjs`](https://github.com/kieler/elkjs), which is dual-licensed under the [Eclipse Public License 2.0](https://github.com/kieler/elkjs/blob/master/LICENSE.md) or GPL-3.0-or-later (`EPL-2.0 OR GPL-3.0-or-later`) - you can use it under the EPL-2.0. `elkjs` is installed as a regular dependency and kept external to this package's own builds (ESM and UMD) - it is never copied or inlined into them. An application that bundles this package does ship `elkjs` code though (the main-thread chunk, and the Web Worker file if it uses `@joint/layout-elk/worker`), under that license: keep `elkjs`'s license notice with it (e.g. with your bundler's license extraction), and note where its source is available (it is published on [GitHub](https://github.com/kieler/elkjs) and [npm](https://www.npmjs.com/package/elkjs)).\n\nCopyright © 2013-2026 client IO\n","isInternal":false,"tokens":5082,"sizeBytes":19775},{"name":"README.md","path":"packages/joint-layout-msagl/README.md","rawUrl":"https://raw.githubusercontent.com/clientIO/joint/HEAD/packages/joint-layout-msagl/README.md","title":"joint-layout-msagl Documentation","category":"plugin-manifest","format":"markdown","content":"# JointJS MSAGL Layout\n\nA module for automatic layout of *[JointJS](https://www.jointjs.com)* graphs using the [Microsoft Automatic Graph Layout (MSAGL)](https://github.com/microsoft/msagljs).\n\nThis library fully depends on [JointJS](https://github.com/clientio/joint) (*>=4.0*), so please read its `README.md` before using this library.\n\nThis library provides hierarchical (Sugiyama) layout with support for nested subgraphs and multiple edge routing modes.\n\n## 🚀 Quick Start\n\n### Installation\n\n```bash\nnpm install @joint/layout-msagl\n```\n\n### Basic Usage\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { layout, EdgeRoutingMode } from '@joint/layout-msagl';\n\n// Create your JointJS graph\nconst graph = new dia.Graph({}, { cellNamespace: shapes });\nconst paper = new dia.Paper({\n    model: graph,\n    cellViewNamespace: shapes,\n    el: document.getElementById('paper'),\n});\nconst rect1 = new shapes.standard.Rectangle({\n    id: 'a',\n    size: { width: 80, height: 40 },\n    attrs: { label: { text: 'A' } }\n});\nconst rect2 = new shapes.standard.Rectangle({\n    id: 'b',\n    size: { width: 80, height: 40 },\n    attrs: { label: { text: 'B' } }\n});\nconst link = new shapes.standard.Link({ source: { id: 'a' }, target: { id: 'b' } });\n\ngraph.addCells([rect1, rect2, link]);\n\n// Or with custom options\nlayout(graph, {\n    layerSeparation: 60,\n    nodeSeparation: 30,\n    edgeRoutingMode: EdgeRoutingMode.Rectilinear\n});\n```\n\n## 📖 API Reference\n\n### Main Functions\n\n#### `layout(graphOrCells, options?): LayoutResult`\n\nMain layout function with full customization options.\n\n**Parameters:**\n- `graphOrCells`: `dia.Graph | dia.Cell[]` - JointJS graph or array of cells to layout\n- `options?`: `Options` - Layout configuration (see below)\n\n**Returns:** `LayoutResult`\n\n```ts\ninterface LayoutResult {\n    bbox: g.Rect;           // Tight bounding box of the laid out graph\n    msGraph: Graph;         // Underlying MSAGL graph data structure\n    msGeomGraph: GeomGraph; // Geometry instance used by MSAGL to layout graph\n}\n```\n\n### Options Interface\n\n```ts\n// Callback type definitions\ntype GetSizeCallback = (element: dia.Element) => dia.Size;\ntype GetLabelSizeCallback = (cell: dia.Cell) => dia.Size | undefined;\ntype SetPositionCallback = (element: dia.Element, position: dia.Point) => void;\ntype SetVerticesCallback = (link: dia.Link, vertices: dia.Point[]) => void;\ntype SetLabelsCallback = (link: dia.Link, labelBBox: dia.BBox, points: dia.Point[]) => void;\ntype SetAnchorCallback = (link: dia.Link, linkEndPoint: dia.Point, bbox: dia.BBox, endType: 'source' | 'target') => void;\ntype SetClusterSizeCallback = (element: dia.Element, size: dia.Size) => void;\n\ninterface Options {\n    // Layout direction\n    layerDirection?: LayerDirectionEnum; // Default: LayerDirectionEnum.TB\n    // Spacing\n    layerSeparation?: number; // Default: 40\n    nodeSeparation?: number; // Default: 20\n    // Edge routing\n    edgeRoutingMode?: EdgeRoutingMode; // Default: EdgeRoutingMode.Rectilinear\n    polylinePadding?: number; // Default: 1\n    // Rectilinear self-loop offset\n    // Vertical pixel offset applied to the two inner vertices of a self-edge\n    // when edgeRoutingMode is Rectilinear. Default: 10\n    rectilinearSelfEdgeOffset?: number;\n    // Grid and origin\n    gridSize?: number; // Default: 0\n    x?: number; // Default: 0\n    y?: number; // Default: 0\n    clusterPadding?: dia.Sides; // Default: 10\n    // Element sizing callbacks\n    getSize?: GetSizeCallback; // Default: element.size()\n    getLabelSize?: GetLabelSizeCallback; // Default: element.get('labelSize')\n    // Callbacks for customizing how the layout is applied\n    setPosition?: SetPositionCallback; // Default: element.position(x, y)\n    setVertices?: boolean | SetVerticesCallback; // Default: true\n    setLabels?: boolean | SetLabelsCallback; // Default: true\n    setAnchor?: boolean | SetAnchorCallback; // Default: true\n    setClusterSize?: SetClusterSizeCallback; // Default: element.size(size)\n}\n```\n\n### Enums\n\n#### `LayerDirectionEnum`\n- `TB` - Top to Bottom (default)\n- `BT` - Bottom to Top\n- `LR` - Left to Right\n- `RL` - Right to Left\n\n#### `EdgeRoutingMode`\n- `Rectilinear` - Orthogonal edges with right angles (default)\n- `SplineBundling` - Smooth curved edges\n\n## 🎯 Examples\n\n### Basic Hierarchical Layout\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { layout, LayerDirectionEnum, EdgeRoutingMode } from '@joint/layout-msagl';\n\nconst graph = new dia.Graph({}, { cellNamespace: shapes });\nconst paper = new dia.Paper({\n    model: graph,\n    cellViewNamespace: shapes,\n    el: document.getElementById('paper'),\n});\n\n// Create elements\nconst elements = ['A', 'B', 'C', 'D'].map(id =>\n    new shapes.standard.Rectangle({\n        id,\n        size: { width: 80, height: 40 },\n        attrs: { label: { text: id } }\n    })\n);\n\n// Create links to form a hierarchy: A -> B, A -> C, B -> D, C -> D\nconst links = [\n    new shapes.standard.Link({ source: { id: 'A' }, target: { id: 'B' } }),\n    new shapes.standard.Link({ source: { id: 'A' }, target: { id: 'C' } }),\n    new shapes.standard.Link({ source: { id: 'B' }, target: { id: 'D' } }),\n    new shapes.standard.Link({ source: { id: 'C' }, target: { id: 'D' } })\n];\n\ngraph.addCells([...elements, ...links]);\n\n// Apply layout\nconst bbox = layout(graph, {\n    layerDirection: LayerDirectionEnum.TB,\n    layerSeparation: 50,\n    nodeSeparation: 30,\n    edgeRoutingMode: EdgeRoutingMode.Rectilinear\n});\n```\n\n### Nested Subgraphs\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { layout } from '@joint/layout-msagl';\n\nconst graph = new dia.Graph({}, { cellNamespace: shapes });\nconst paper = new dia.Paper({\n    model: graph,\n    cellViewNamespace: shapes,\n    el: document.getElementById('paper'),\n});\n\nconst parent = new shapes.standard.Rectangle({\n    id: 'parent',\n    size: { width: 200, height: 150 }\n});\n\nconst child1 = new shapes.standard.Rectangle({\n    id: 'child1',\n    size: { width: 60, height: 30 }\n});\n\nconst child2 = new shapes.standard.Rectangle({\n    id: 'child2',\n    size: { width: 60, height: 30 }\n});\n\n// Embed children in parent\nparent.embed(child1);\nparent.embed(child2);\n\n// Add internal link between children\nconst internalLink = new shapes.standard.Link({\n    source: { id: 'child1' },\n    target: { id: 'child2' }\n});\n\ngraph.addCells([parent, child1, child2, internalLink]);\n\n// Layout will automatically handle the subgraph\nlayout(graph);\n```\n\n### Edge Routing with Labels\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { layout } from '@joint/layout-msagl';\n\nconst graph = new dia.Graph({}, { cellNamespace: shapes });\nconst paper = new dia.Paper({\n    model: graph,\n    cellViewNamespace: shapes,\n    el: document.getElementById('paper'),\n});\n\nconst rect1 = new shapes.standard.Rectangle({\n    id: 'A',\n    size: { width: 80, height: 40 },\n    attrs: { label: { text: 'A' } }\n});\n\nconst rect2 = new shapes.standard.Rectangle({\n    id: 'B',\n    size: { width: 80, height: 40 },\n    attrs: { label: { text: 'B' } }\n});\n\n// Set label size on links for proper label positioning\nconst link = new shapes.standard.Link({\n    source: { id: 'A' },\n    target: { id: 'B' },\n    labels: [\n        {\n            attrs: {\n                text: {\n                    text: 'Label'\n                }\n            }\n        }\n    ],\n    labelSize: { width: 80, height: 20 } // Required for label layout\n});\n\ngraph.addCells([rect1, rect2, link]);\n\nlayout(graph);\n```\n\n### Custom Callbacks\n\n```ts\nimport { dia, util } from '@joint/core';\n\nlayout(graph, {\n    // Custom positioning with animation\n    setPosition: (element: dia.Element, position: dia.Point) => {\n        element.transition('position', position, {\n            duration: 500,\n            timingFunction: util.timing.cubic,\n            valueFunction: util.interpolate.object\n        });\n    }\n});\n```\n\n## 🔧 Advanced Features\n\n### Label Positioning\n\nThe layout automatically positions labels on links and reserves space for element labels in subgraphs. Label sizing is controlled by the `getLabelSize` callback:\n\n**Default behavior** - reads from the `labelSize` property:\n```ts\nlink.set('labelSize', { width: 100, height: 20 });\nparentElement.set('labelSize', { width: 120, height: 25 });\n```\n\n**Custom label sizing** - provide your own callback:\n```ts\nlayout(graph, {\n    getLabelSize: (cell) => {\n        if (cell.isLink()) {\n            // Calculate link label size based on text content\n            const text = cell.label(0)?.attrs?.text?.text || '';\n            return { width: text.length * 8, height: 20 };\n        } else {\n            // Calculate element label size\n            const text = cell.attr('label/text') || '';\n            return { width: text.length * 10, height: 25 };\n        }\n    }\n});\n```\n\nIf `getLabelSize` returns `undefined` for a cell, no space is reserved for its label.\n\n## ⚠️ Caveats & Known Limitations\n\n- **Rectilinear self‑loops** – When `edgeRoutingMode` is set to `Rectilinear`, self‑edges use a configurable vertical offset controlled by `rectilinearSelfEdgeOffset` (default `10`). This is a stop‑gap until upstream `msagljs` provides native rectilinear self‑loop geometry.\n- **Subgraph resizing** – Parent elements that embed other elements are resized by the layout to tightly pack all their children.\n- **Subgraph layout direction** - Layout inside subgraphs is always set to `TB` (Top-to-Bottom) direction, as other directions can cause layout issues.\n- **Link labels in subgraphs** – Link labels within subgraphs may be positioned incorrectly, despite the layout correctly reserving space for them.\n- **Obstacle padding (first‑bend distance)** – MSAGL exposes an `edgeRoutingSettings.Padding` option intended to keep edges a minimum distance away from obstacles (nodes) and thus control where the first bend occurs. In msagljs this setting is currently not working.\n- **ID handling** - MSAGL expects IDs to be string values so `id: 1` and `id: '1'` are considered the same.\n\n## 📄 License\n\n[Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/)\n\nCopyright © 2013-2026 client IO\n","isInternal":false,"tokens":2586,"sizeBytes":10109},{"name":"README.md","path":"packages/joint-mock-svg/README.md","rawUrl":"https://raw.githubusercontent.com/clientIO/joint/HEAD/packages/joint-mock-svg/README.md","title":"joint-mock-svg Documentation","category":"plugin-manifest","format":"markdown","content":"# JointJS Mock SVG\n\nMocks of those SVG globals that JSDOM does not implement but *[JointJS](https://www.jointjs.com/)* relies on, including `SVGAngle`, `createSVGMatrix()`, `getBBox()`, and `transform.baseVal`.\n\nThis package is **test-runner agnostic**: it depends on no test runner and uses no spies, so it can be consumed from any of them.\n\n## Usage\n\nImport it once, before your tests run, in a DOM environment:\n\n```js\nimport '@joint/mock-svg';\n```\n\nThe import is side-effectful: it installs the mocks on `globalThis` and exports no value. Note that a DOM is required - without it the import fails.\n\nWhere that import belongs depends on your runner - it needs to run before any test file touches the SVG globals, which usually means a setup-file option rather than an import in the test itself.\n\n## TypeScript\n\n`getBBox()`, `getScreenCTM()`, `getComputedTextLength()` and `transform` are mocked on `SVGElement`, one level wider than where DOM types (`lib.DOM`) declare them, because JSDOM makes `<rect>`, `<path>` and `<text>` plain `SVGElement`s. Reading one of them off `SVGElement.prototype` - like when spying on the mocks - therefore needs the exported `MockedSVGElement` type. For example:\n\n```ts\nimport type { MockedSVGElement } from '@joint/mock-svg';\n\njest.spyOn(SVGElement.prototype as MockedSVGElement, 'getBBox')\n    .mockReturnValue({ x: 0, y: 0, width: 100, height: 50 } as DOMRect);\n```\n\n## License\n\nThe *@joint/mock-svg* library is licensed under the [MIT License](https://github.com/clientIO/joint/blob/master/packages/joint-mock-svg/LICENSE).\n\nCopyright © 2013-2026 client IO\n","isInternal":false,"tokens":403,"sizeBytes":1597},{"name":"README.md","path":"packages/joint-react/README.md","rawUrl":"https://raw.githubusercontent.com/clientIO/joint/HEAD/packages/joint-react/README.md","title":"joint-react Documentation","category":"plugin-manifest","format":"markdown","content":"<p align=\"center\">\n  <img src=\"https://cdn.prod.website-files.com/63061d4ee85b5a18644f221c/633045c1d726c7116dcbe582_JJS_logo.svg\" alt=\"JointJS\" height=\"56\" />\n</p>\n\n<h1 align=\"center\">JointJS for React</h1>\n\n<p align=\"center\">\n  <strong>Production-scale diagramming for React.</strong><br/>\n  A first-class React integration for <a href=\"https://jointjs.com\">JointJS</a> — real components and hooks for building complex diagramming apps, with full control over the data model, rendering, and interactions, using the React patterns you already know.\n</p>\n\n<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/@joint/react\"><img src=\"https://img.shields.io/npm/v/@joint/react?style=flat-square&color=0EA5E9\" alt=\"npm\" /></a>\n  <a href=\"https://www.npmjs.com/package/@joint/react\"><img src=\"https://img.shields.io/npm/types/@joint/react?style=flat-square&color=3178C6\" alt=\"types\" /></a>\n  <a href=\"https://www.npmjs.com/package/@joint/react\"><img src=\"https://img.shields.io/npm/dm/@joint/react?style=flat-square&color=F59E0B\" alt=\"downloads\" /></a>\n  <a href=\"./LICENSE\"><img src=\"https://img.shields.io/npm/l/@joint/react?style=flat-square&color=10B981\" alt=\"license\" /></a>\n</p>\n\n<p align=\"center\">\n  🚀 <a href=\"#quick-start\">Quick start</a> ·\n  🧩 <a href=\"https://www.jointjs.com/demos/ai-workflow-builder\">Live Example</a> ·\n  📖 <a href=\"#learn-more\">Learn more</a>\n</p>\n\n[![Use Cases](https://github.com/user-attachments/assets/fc6de738-3fad-48f7-99aa-ba8c3729d9b1)](https://github.com/user-attachments/assets/fc6de738-3fad-48f7-99aa-ba8c3729d9b1)\n\n---\n\nBuilt on the proven JointJS core, you get a mature diagramming UI library and an idiomatic React layer in one. Like JointJS, it's open source.\n\n## Why JointJS for React\n\n- **A real data model.** A serializable graph you can query, transform, and persist — the backbone for complex, stateful diagram applications, not just visual nodes.\n- **Full control.** Complete control over how diagrams render and behave — shapes, ports, links, routing, and custom interactions.\n- **Production-grade UX.** The interaction details end users expect come built in: connection snapping, link routing that avoids obstacles, element and link tools, highlighters for hover and selection states. Good UX is designed into every feature, not left for you to assemble.\n- **Performance at scale.** Smooth interaction on large graphs with thousands of nodes.\n- **Idiomatic React.** Real components and hooks that fit naturally into your existing React app and tooling.\n\n## Installation\n\n```bash\nnpm install @joint/react     # or pnpm add / yarn add / bun add\n```\n\nPeer dependency: **React 18 or 19**.\n\n## Quick start\n\n```tsx\nimport { GraphProvider, Paper, HTMLBox, type CellRecord } from '@joint/react';\n\ninterface NodeData {\n  label: string;\n}\n\nconst initialCells: ReadonlyArray<CellRecord<NodeData>> = [\n  { id: '1', type: 'element', position: { x: 40, y: 40 }, data: { label: 'Hello' } },\n  { id: '2', type: 'element', position: { x: 280, y: 180 }, data: { label: 'World' } },\n  { id: '1 → 2', type: 'link', source: { id: '1' }, target: { id: '2' } },\n];\n\nfunction renderElement(data: NodeData) {\n  return <HTMLBox>{data.label}</HTMLBox>;\n}\n\nexport default function Diagram() {\n  return (\n    <GraphProvider initialCells={initialCells}>\n      <Paper style={{ width: '100%', height: 600 }} renderElement={renderElement} />\n    </GraphProvider>\n  );\n}\n```\n\n`GraphProvider` holds the graph state, `Paper` renders it, and `renderElement` turns each node's data into a React component. See [Learn more](#learn-more) for the full getting-started guide.\n\n## When is JointJS for React the right choice?\n\nJointJS for React is built for production-scale diagramming — applications where the diagram is a core part of the product and has to handle real complexity: a rich data model, large graphs, domain-specific shapes, and deep control over interactions and rendering.\n\nProduction-scale is not only about node count. Most workflow tools top out at tens of nodes, yet their users still expect a polished editing experience: precise connections, clear feedback on hover and selection, links that route cleanly around obstacles. JointJS for React treats that end-user UX as part of the library, so you ship it instead of building it.\n\nIt's designed to be the layer you build on as your requirements grow, from the first prototype through to a mature, scaling application.\n\n## Use cases\n\nJointJS for React is used to build production diagramming applications across many domains:\n\n| Domain | Examples |\n|---|---|\n| Workflow & process | Flowchart editors, process automation, activity and sequence diagrams |\n| AI workflow builders | Node-based AI pipeline and agent builders, marketing automation studios |\n| BPMN | Business process modelers, swimlanes, Visio BPMN import/export |\n| Data modeling | ER diagrams, database schema design, data lineage and mapping |\n| UML & software modeling | Class, sequence, statechart and use-case diagrams |\n| Industrial / SCADA & HMI | SCADA interfaces, HMI control panels, P&ID and process diagrams |\n| Energy networks | Power grid and substation diagrams, telecom and fiber (DWDM) network design |\n| Electronic design | Circuit and logic design, wiring and cable harness diagrams, schematic capture |\n| Org charts | Org charts, reporting hierarchies, team and headcount planning |\n| Timelines | Project and milestone timelines, Gantt and PERT charts |\n\nExplore the [demos](https://www.jointjs.com/demos) to see them in action.\n\n## Learn more\n\n- 📖 [Learn JointJS for React](https://docs.jointjs.com/react)\n- 🧾 [API reference](https://docs.jointjs.com/api-react)\n- 🖼️ [Examples](https://www.jointjs.com/demos)\n- 📚 [Storybook](https://react.jointjs.com/learn/)\n- 🤖 [MCP server](#using-jointjs-for-react-with-ai-coding-agents)\n- 💬 [Ask a question or share feedback](https://github.com/clientIO/joint/discussions)\n\n## Using JointJS for React with AI coding agents\n\nJointJS is built to work well with AI-assisted development.\n\n**MCP server** — connect your AI coding agent (Claude Code, Cursor, and other MCP-compatible tools) to the JointJS Model Context Protocol server so it can search the official docs and demos while you build:\n\n```\nhttps://mcp.jointjs.com/mcp\n```\n\nIt exposes `search_docs` and `search_demos`, so your agent writes correct JointJS for React code against the real API instead of guessing.\n\n## Commercial version\n\nThe open-source library covers core diagramming. **JointJS+ for React** is the commercial extension that takes the end-user experience further with production-ready plugins — stencils, toolbars, snaplines, keyboard shortcuts, undo/redo, advanced layouts, import/export, and more — plus professional demo apps (including source code) and direct support from the team that builds the library. [Start a free 30-day trial.](https://www.jointjs.com/free-trial)\n\n## License\n\nJointJS for React is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n","isInternal":false,"tokens":1796,"sizeBytes":7073},{"name":"README.md","path":"packages/joint-router-avoid/README.md","rawUrl":"https://raw.githubusercontent.com/clientIO/joint/HEAD/packages/joint-router-avoid/README.md","title":"joint-router-avoid Documentation","category":"plugin-manifest","format":"markdown","content":"# JointJS Avoid Router\n\nA module that routes *[JointJS](https://www.jointjs.com)* links using [libavoid](https://github.com/mjwybrow/libavoid), a C++ library for automatic, obstacle-avoiding orthogonal connector routing, compiled to WebAssembly via [libavoid-js](https://github.com/Aksem/libavoid-js).\n\nThis library fully depends on [JointJS](https://github.com/clientio/joint) (*>=4.0*), so please read its `README.md` before using this library.\n\nlibavoid maintains a single incremental router shared by the whole graph: it tracks every element as an obstacle and every link as a connector, and reroutes affected connectors whenever obstacles move. This package wraps that behavior in a `RouterService` that listens to a `dia.Graph` and, whenever libavoid computes a new route for a link, applies it directly to that link's `vertices` and source/target anchors - there is no `router: { name: ... }` attribute to set on links; once a link is connected to two elements on a routed graph, it is routed automatically. See [`examples/avoid-router-ts`](https://github.com/clientIO/joint/tree/master/examples/avoid-router-ts) for a full demo.\n\n## 🚀 Quick Start\n\n### Installation\n\n```bash\nnpm install @joint/router-avoid\n```\n\n### Basic Usage\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { initAvoidRouter } from '@joint/router-avoid';\n\nconst graph = new dia.Graph({}, { cellNamespace: shapes });\nconst paper = new dia.Paper({\n    model: graph,\n    cellViewNamespace: shapes,\n    el: document.getElementById('paper'),\n});\n\n// Loads the libavoid-js WebAssembly module (if not already loaded), then\n// registers every element/link currently in the graph as an avoid\n// obstacle/connector and keeps them in sync with future graph changes.\nconst routerService = await initAvoidRouter(graph, {\n    shapeBufferDistance: 20,\n    idealNudgingDistance: 10,\n});\n\n// ... add elements and links to the graph - links connected to two\n// elements are picked up and routed automatically, no `router`\n// attribute needed:\n// new shapes.standard.Link({ source: { id: a.id }, target: { id: b.id } });\n\n// Stop routing this graph and release the underlying resources\n// (e.g. terminate a Worker thread) when they're no longer needed.\n// routerService.destroy();\n```\n\n`initAvoidRouter()` resolves once the module is loaded and the current graph content has been registered with libavoid; the routes themselves are still computed asynchronously and applied as they come in - listen for the `link:routed` event to know when a specific link's route has settled (see below).\n\nLoaded as a script tag rather than via ESM, the same functions are available as `joint.routers.avoid.initAvoidRouter` / `joint.routers.avoid.loadAvoidRouter` on the UMD build.\n\n## 📖 API Reference\n\n### `loadAvoidRouter(filePath?): Promise<void>`\n\nLoads the `libavoid-js` WebAssembly module. `initAvoidRouter()` calls this automatically if the module isn't loaded yet, so calling it directly is only needed to preload the module ahead of time. Accepts an optional path to the `libavoid.wasm` file if it is not served from its default location. Safe to call multiple times - later calls resolve once the first load completes.\n\n### `initAvoidRouter(graph, options?): Promise<RouterService>`\n\nLoads the module (via `loadAvoidRouter()`, if needed) and creates a `RouterService` for `graph`. The returned service is not started - call `start()` to keep the graph continuously routed, or `routeAll()`/`routeSubgraph()` for a one-shot routing pass.\n\n- `graph`: `dia.Graph` - the graph to route.\n- `options?`: `InitAvoidOptions`\n\n```ts\ninterface InitAvoidOptions {\n    shapeBufferDistance?: number;  // Default: 10 - spacing added around shapes when computing obstacles, and used as the fallback route's margin around elements\n    idealNudgingDistance?: number; // Default: 5 - spacing used to nudge apart overlapping connector segments\n    worker?: boolean | WorkerOptions; // Default: false - run libavoid inside a Web Worker instead of the main thread; pass an object to configure the Worker\n    libavoidFilePath?: string;     // Forwarded to loadAvoidRouter()\n    trackLink?: (params: { link: dia.Link }) => boolean;          // Which links to route; return false to exclude one. Default: all\n    trackElement?: (params: { element: dia.Element }) => boolean; // Which elements to track as avoid obstacles; return false to exclude one. Default: all\n    // First refusal on a link avoid can't route because one of its ends\n    // isn't connected to a cell ('unconnected'), is connected to another\n    // link ('unsupported'), or is connected to an element excluded via\n    // `trackElement` ('untracked'). Return true to take over routing that\n    // link yourself; otherwise the built-in `rightAngle`-based fallback\n    // route is applied.\n    interceptUnroutableLink?: (params: { link: dia.Link, reason: 'unconnected' | 'untracked' | 'unsupported' }) => boolean;\n    // Override how computed route attributes are applied to a link, e.g. to\n    // route the update through a command manager. Default: link.set()\n    setRouteAttributes?: (params: { link, attributes, origin, routing, unroutableReason }) => void;\n    changeFlag?: string;           // Default: 'avoidRouter' - name of the opt flag marking this router's own link.set() calls\n}\n\ninterface WorkerOptions {\n    debounceTime?: number; // Default: 100 - ms the Worker waits after the last received change before recomputing routes in a single batch; 0 applies every change immediately\n}\n```\n\n### `RouterService` (returned by `initAvoidRouter()`)\n\nThe object responsible for keeping libavoid's internal obstacle/connector graph in sync with the `dia.Graph`, applying computed routes to links, and emitting routing events. It mixes in JointJS's `mvc.Events` (`on`/`off`/`trigger`).\n\n- `start()` / `stop()` - start/stop listening to graph changes (added/removed cells, moved/resized elements, reconnected links). `start()` also syncs every cell the graph already holds.\n- `isStarted` - whether the service is currently listening to its graph.\n- `routeAll()` - one-shot: routes every cell currently in the graph, resolving once all routes are applied. No graph listener is attached. Throws while started. Resolves with a `RoutingResult` - `{ status: 'done' }`, or `{ status: 'cancelled' }` if the pass was interrupted by `destroy()`. Overlapping calls are queued and run one after another.\n- `routeSubgraph(cells)` - one-shot: routes only `cells`, independently of the rest of the graph (cells outside the array are neither routed nor obstacles). Throws while started. Resolves with the same `RoutingResult` as `routeAll()`.\n- `changeFlag` - the `opt` flag name marking this router's own `link.set()` calls, for filtering them out of your own `change` listeners.\n- `destroy()` - stops routing the graph, cancels open routing cycles, and releases the resources held by the service and its provider (e.g. terminates a Worker thread). Do not use the instance afterwards.\n\n**Events:**\n\n- `link:routing` `(link)` - emitted when a link's route is (re-)computing.\n- `link:routed` `(link, { origin, reason })` - emitted once a link's route has been applied. `origin` is `'avoid'` or `'fallback'`; `reason` is set when the fallback route was applied because the link was unroutable.\n- `link:routing:cancelled` `(link)` - emitted when a link with an open `link:routing` cycle becomes unroutable (e.g. disconnected) before libavoid produced a route for it.\n- `idle` `()` - emitted when there are no more open routing cycles.\n\n## ⚠️ Caveats & Known Limitations\n\n- **Asynchronous setup** - `initAvoidRouter()` loads the WebAssembly module and performs the initial graph sync asynchronously; it must be awaited before the graph is considered routed.\n- **Bundler configuration** - `libavoid-js` ships its logic and the `libavoid.wasm` binary as separate files, and `worker: true` additionally loads this package's own worker script as a module. Consuming applications are responsible for ensuring both are served alongside the rest of the bundle (e.g. via a copy plugin for your bundler of choice).\n- **Fallback routing** - libavoid does not expose a way to check whether a computed route is valid, so a heuristic is used. When the route is deemed invalid, or when a link isn't connected to elements on both ends, the built-in `rightAngle`-based fallback route is applied instead (unless `interceptUnroutableLink` takes over).\n- **One `RouterService` per graph** - constructing more than one `RouterService` for the same `dia.Graph` results in both instances applying routes to the same links; call `destroy()` on a previous instance before creating a new one for the same graph.\n- **Custom vertices (checkpoints)** are not currently supported.\n\n## 📄 License\n\nThe code in this package is licensed under the [Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/), same as the rest of JointJS.\n\nIt depends on [`libavoid-js`](https://www.npmjs.com/package/libavoid-js), a WebAssembly build of [libavoid](https://github.com/mjwybrow/adaptagrams), which is licensed under the **GNU LGPL 2.1 or later** - applications shipping this package also ship the `libavoid.wasm` binary under that license. Keep the binary a separately served file (as described under bundler configuration above); don't configure your bundler to inline it into your application bundle.\n\nCopyright © 2013-2026 client IO\n\nThis package depends on [`libavoid-js`](https://github.com/Aksem/libavoid-js), which is licensed under the [LGPL-2.1-or-later license](https://github.com/Aksem/libavoid-js?tab=LGPL-2.1-1-ov-file#readme).\n","isInternal":false,"tokens":2249,"sizeBytes":9610},{"name":"README.md","path":"packages/joint-shapes-general-tools/README.md","rawUrl":"https://raw.githubusercontent.com/clientIO/joint/HEAD/packages/joint-shapes-general-tools/README.md","title":"joint-shapes-general-tools Documentation","category":"plugin-manifest","format":"markdown","content":"# JointJS General Shapes Tools\n\nA collection of ECMAScript / TypeScript tools for use with general *[JointJS](https://www.jointjs.com/)* shapes.\n\nThis library fully depends on [JointJS](https://github.com/clientio/joint) (*>=3.5*), so please read its `README.md` before using this library.\n\n## License\n\n[Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/)\n\nCopyright © 2013-2026 client IO\n","isInternal":false,"tokens":110,"sizeBytes":406},{"name":"README.md","path":"packages/joint-shapes-general/README.md","rawUrl":"https://raw.githubusercontent.com/clientIO/joint/HEAD/packages/joint-shapes-general/README.md","title":"joint-shapes-general Documentation","category":"plugin-manifest","format":"markdown","content":"# JointJS General Shapes\n\nA collection of general ECMAScript / TypeScript shapes for *[JointJS](https://www.jointjs.com/)*.\n\nThis library fully depends on [JointJS](https://github.com/clientio/joint) (*>=3.5*), so please read its `README.md` before using this library.\n\n## License\n\n[Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/)\n\nCopyright © 2013-2026 client IO\n","isInternal":false,"tokens":106,"sizeBytes":385},{"name":"README.md","path":"packages/joint-vitest-plugin-mock-svg/README.md","rawUrl":"https://raw.githubusercontent.com/clientIO/joint/HEAD/packages/joint-vitest-plugin-mock-svg/README.md","title":"joint-vitest-plugin-mock-svg Documentation","category":"plugin-manifest","format":"markdown","content":"# JointJS Mock SVG plugin for Vitest\n\nA Vitest plugin to mock SVG methods not implemented in JSDOM used by *[JointJS](https://www.jointjs.com/)*.\n\n## Usage\n\nImport the plugin in your `vitest.config.js`:\n\n```js\nimport { defineConfig } from 'vitest/config';\nimport mockSVG from '@joint/vitest-plugin-mock-svg'\n\nexport default defineConfig({\n  plugins: [mockSVG()],\n})\n```\n\nThe mocks themselves come from [`@joint/mock-svg`](https://github.com/clientIO/joint/tree/master/packages/joint-mock-svg) as plain functions. If your test needs to assert on one of them, or change what the mock returns, use `vi.spyOn()`.\n\n### Changing what a mock returns\n\nThe default `getBBox()` returns an all-zero rect, which leaves `checkVisibility()` false and `util.breakText()` with nothing to measure. If your test needs one, give the mock a size object as a return value:\n\n```js\nvi.spyOn(SVGElement.prototype, 'getBBox')\n  .mockReturnValue({ x: 0, y: 0, width: 100, height: 20 });\n```\n\n`mockRestore()`, `vi.restoreAllMocks()` and `restoreMocks: true` all put the mock back afterwards, so an override stays local to its test and the suite needs no `beforeEach` to reinstall anything.\n\nThe same spy also records calls. This lets you assert whether JointJS measured anything at all:\n\n```js\nconst getBBox = vi.spyOn(SVGElement.prototype, 'getBBox');\nrender(<MyDiagram />);\nexpect(getBBox).toHaveBeenCalled();\n```\n\n`vi.spyOn` reaches nested members, as well - for example `vi.spyOn(SVGElement.prototype.transform.baseVal, 'appendItem')`.\n\nTo swap a mock implementation permanently, reassign it:\n\n```js\nSVGElement.prototype.getComputedTextLength = () => 42;\n```\n\nRestore the previous value yourself if the change should not outlive the file.\n\n### Driving `ResizeObserver`\n\nThe mocked observer accepts a callback and never calls it. To deliver entries, or to assert on `observe`, provide your own:\n\n```js\nglobalThis.ResizeObserver = class {\n  observe = vi.fn();\n  unobserve = vi.fn();\n  disconnect = vi.fn();\n  constructor(callback) { this.callback = callback; }\n  resize(entries) { this.callback(entries, this); }\n};\n```\n\n## TypeScript\n\n`getBBox()`, `getScreenCTM()`, `getComputedTextLength()` and `transform` are mocked on `SVGElement`, one level wider than where DOM types (`lib.DOM`) declare them, because JSDOM makes `<rect>`, `<path>` and `<text>` plain `SVGElement`s. Reading one of them off `SVGElement.prototype` - as every example above does - therefore needs the re-exported `MockedSVGElement` type. For example:\n\n```ts\nimport type { MockedSVGElement } from '@joint/vitest-plugin-mock-svg';\n\nvi.spyOn(SVGElement.prototype as MockedSVGElement, 'getBBox')\n  .mockReturnValue({ x: 0, y: 0, width: 100, height: 20 } as DOMRect);\n```\n\n## License\n\nThe *@joint/vitest-plugin-mock-svg* library is licensed under the [MIT License](https://github.com/clientIO/joint/blob/master/packages/joint-vitest-plugin-mock-svg/LICENSE).\n\nCopyright © 2013-2026 client IO\n","isInternal":false,"tokens":741,"sizeBytes":2934}],"systemPromptSnippet":"<yakaai_skills repo=\"clientIO/joint\">\n<!-- File: CLAUDE.md (Tokens: ~1171 | Category: claude-rule) -->\n# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with\ncode in this repository.\n\n## Project Overview\n\nJointJS is a JavaScript/TypeScript diagramming library for building visual and\nno-code/low-code applications. It's a Yarn workspace monorepo with multiple\npackages.\n\n**Stack:** Node 22.14.0, Yarn 4.18.0, TypeScript 5.8 (5.9 in `joint-react`),\nGrunt (build), QUnit/Karma/Mocha/Jest (testing)\n\n## Common Commands\n\n```bash\n# Install dependencies\nyarn install\n\n# Build distribution files (runs Grunt)\nyarn dist\n\n# Run all tests (QUnit + Jest + TypeScript checks)\nyarn test\n\n# Run specific test types\nyarn test-server       # Server-side tests (Node.js)\nyarn test-client       # Client-side tests (Browser with Karma)\nyarn test-ts           # TypeScript type definition tests\n\n# Linting\nyarn lint              # Check all packages\nyarn lint-fix          # Auto-fix errors\n\n# Build bundles\nyarn build\nyarn build-bundles\n```\n\n### Running Single Tests\n\nFor joint-react Jest tests:\n```bash\ncd packages/joint-react\nyarn jest --testPathPatterns=\"ComponentName\"\n```\n\n## Architecture\n\n### Monorepo Packages (`/packages`)\n\n- **@joint/core** - Main diagramming library (MVC architecture, SVG rendering)\n- **@joint/react** - React bindings and hooks (Rollup + esbuild, Storybook)\n- **@joint/layout-directed-graph** - Graph layout algorithms\n- **@joint/layout-msagl** - Microsoft MSAGL layout integration\n- **@joint/shapes-general** - General-purpose diagram shapes\n- **@joint/eslint-config** - Shared ESLint configuration\n\n### Core Library Structure (`/packages/joint-core/src`)\n\nThe library follows an MVC pattern:\n\n- **`mvc/`** - Base MVC components: Model, View, Collection, Events, Dom\n- **`dia/`** - Diagramming API built on MVC:\n  - `Graph.mjs` - Graph data structure and cell management\n  - `Paper.mjs` - SVG canvas and rendering engine\n  - `Element.mjs` / `ElementView.mjs` - Diagram nodes\n  - `Link.mjs` / `LinkView.mjs` - Connections between elements\n- **`g/`** - Geometry library (points, lines, curves, polygons, matrices)\n- **`V/`** - Vectorizer for SVG manipulation\n- **`connectors/`** - Link path strategies (line, curve, bezier, jumpover)\n- **`routers/`** - Link routing algorithms (manhattan, orthogonal, metro)\n- **`anchors/`** - Connection point anchors on elements\n- **`highlighters/`** - Visual highlighting mechanisms\n- **`linkTools/`**, **`elementTools/`** - Interactive manipulation tools\n- **`shapes/`** - Built-in shapes (standard)\n- **`layout/`** - Built-in layouts (port, port label)\n- **`alg/`** - Graph algorithms (DFS, BFS, shortest path)\n\n### Main Export (`/packages/joint-core/src/core.mjs`)\n\n```javascript\nexport { anchors, linkAnchors, config, connectionPoints, connectionStrategies,\n         connectors, dia, env, highlighters, layout, mvc, routers, setTheme,\n         util, version, linkTools, elementTools, V, Vectorizer, g };\n```\n\n## Test Organization\n\n- **`/packages/joint-core/test/jointjs/`** - QUnit tests for core functionality\n- **`/packages/joint-core/test/jointjs-nodejs/`** - Mocha tests run by\n  `test-server`\n- **`/packages/joint-core/test/geometry/`** - Geometry library tests\n- **`/packages/joint-core/test/vectorizer/`** - SVG vectorizer tests\n- **`/packages/joint-core/test/ts/`**, **`test/ts-exports/`** - TypeScript\n  definition validation\n- **`/packages/joint-react/src/`** - Jest tests with @testing-library/react,\n  colocated in `__tests__` folders\n\n## Changesets\n\nReleasable changes need a changeset in `/.changeset`. Before writing one, read\nthe **Changeset format** section of `CONTRIBUTING.md` - it defines the required\nfrontmatter, the `scope - description` changelog style (`dia.Paper`, `anchors`,\n`<Paper />`, ...), and the backtick/`()` conventions - and follow it exactly.\n\n## Key Patterns\n\n- **Change tracking:** Models emit `change:*` events; `changeId` event triggers\n  only when ID actually changes\n- **Workspace commands:** Use `yarn workspaces foreach --all -tvv run <cmd>`\n  for cross-package operations\n- **TypeScript:** Strict mode enabled; the settings are per package, not\n  repo-wide. `tsconfig.common.json` adds `noUnusedLocals`,\n  `noUnusedParameters` and `noImplicitReturns`, and is used by `joint-cli`,\n  `joint-decorators` and the `shapes-general` packages - `joint-core` and\n  `joint-react` each have their own\n- **ESLint:** Uses flat config format (v9) via `@joint/eslint-config`\n\n<!-- File: packages/joint-react/CLAUDE.md (Tokens: ~1532 | Category: anthropic-skill) -->\n# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with\ncode in this repository.\n\n## Project Overview\n\n`@joint/react` is the React bindings library for JointJS. It provides React\ncomponents and hooks for building diagramming applications with JointJS,\nrendering diagram elements as React components via SVG foreignObject portals.\n\n**Stack:** TypeScript 5.9, React 19 (peer: >=18), Rollup + esbuild (build), Vite\n(storybook/dev), Jest 30 + @testing-library/react (testing), ESLint 9 flat\nconfig, Storybook 10\n\n## Common Commands\n\n```bash\n# Build (Rollup + esbuild → dist/cjs, dist/esm, dist/types)\nyarn build\n\n# Run all checks (typecheck + knip + jest, once against React 19 and once\n# against React 18)\nyarn test\n\n# Run individual checks\nyarn typecheck              # tsc --noEmit\nyarn lint                   # ESLint\nyarn lint-fix               # ESLint with auto-fix\nyarn jest                   # Jest tests only\n\n# Run a single test file\nyarn jest --testPathPatterns=\"use-elements\"\n\n# Run tests in watch mode\nyarn jest --watch\n\n# Storybook (dev server on :6006)\nyarn storybook\n\n# Build storybook\nyarn build-storybook\n```\n\n## Changesets\n\nReleasable changes need a changeset - the format is defined in the **Changeset\nformat** section of the repository's `CONTRIBUTING.md`. Two things are specific\nto this package:\n\n- The changelog scope is the exported component or hook rather than a JointJS\n  namespace - components as JSX tags, hooks by name:\n\n  ```markdown\n  <Paper /> - fix the visual grid to redraw reactively when `drawGrid` changes\n  useCells - fix ghost cells reported after `resetCells()`\n  ```\n\n- `@joint/react` is versioned independently. It is not part of the\n  `@joint/core` linked group, and because it depends on core via `workspace:^`,\n  a `@joint/core` minor release does not drag it along - only a major does.\n\n# Clean Code & Style Guidelines\n\n## General\n\n- Prefer clear, explicit code over clever one-liners. Maintainability > brevity.\n- When editing existing code, preserve the existing style unless I explicitly\n  ask for a refactor.\n- Leave code cleaner than you found it. Continuously refactor small things\n  instead of accumulating technical debt.\n\n## Constants Over Magic Numbers\n\n- Replace hard-coded values with named constants.\n- Use descriptive constant names that explain the value's purpose.\n- Keep constants at the top of the file\n\n## Meaningful Names\n\n- Variables, functions, and classes should reveal their purpose.\n- Names should explain **why** something exists and **how** it is used.\n- Avoid abbreviations unless they are truly universal (e.g. `id`, `URL`,\n  `HTML`).\n- Boolean names must be self-descriptive and read like conditions:\n  - Use prefixes: `is`, `has`, `should`, `can`, `must`, `needs` (e.g. `isOpen`,\n    `hasError`, `shouldRetry`).\n  - Avoid generic names like `flag`, `ok`, `state`, `enabled` without context.\n- Function names should describe behavior, not implementation details.\n\n## File Naming (TypeScript / JavaScript)\n\n- All `.ts`, `.tsx`, `.js`, `.jsx` files must use **kebab-case**:\n  - Examples: `user-profile.ts`, `something-table.tsx`\n- React component files:\n  - File name: kebab-case (e.g. `user-profile-card.tsx`).\n  - Component name inside: `PascalCase` (e.g. `UserProfileCard`).\n\n## Smart Comments\n\n- Do **not** comment on what the code does if the code can be made\n  self-explanatory instead.\n- Prefer to improve the code so it reads clearly without comments.\n- Use comments to explain:\n  - Why something is done in a non-obvious way.\n  - Important constraints, assumptions, or business rules.\n  - Non-obvious side effects.\n- Document APIs, complex algorithms, and tricky edge cases.\n\n## Single Responsibility\n\n- Each function should do exactly one thing.\n- Functions should be small and focused.\n- If a function needs a long comment to explain what it does, consider\n  splitting it into smaller functions.\n- JSDoc is mandatory for all exported functions.\n\n## DRY (Don't Repeat Yourself)\n\n- Extract repeated code into reusable functions or utilities.\n- Share common logic through proper abstractions.\n- Maintain single sources of truth for configuration, constants, and shared\n  logic.\n\n## Clean Structure\n\n- Keep related code together.\n- Organize code in a logical hierarchy (modules, domains, features).\n- Use consistent file and folder naming conventions.\n- Avoid \"god\" files that own too many responsibilities.\n\n## Encapsulation\n\n- Hide implementation details behind clear interfaces.\n- Expose only what is needed from a module.\n- Move nested conditionals or complex logic into well-named helper functions.\n\n## TypeScript\n\n- Never use `any` in new code:\n  - Prefer proper types, generics, unions, or `unknown` + narrowing.\n  - If `any` is unavoidable at a boundary (e.g. 3rd-party library), isolate it\n    and add a clear `TODO` explaining why.\n- Write `strict`-friendly code:\n  - No implicit `any`.\n  - Avoid sloppy or unnecessary `as` casts.\n  - Avoid `as any` and non-null assertions (`!`) unless absolutely necessary;\n    justify them with a short comment.\n- Use `interface`/`type` aliases for data structures instead of loose objects.\n- Keep function signatures explicit and well-typed, especially for exported\n  functions and public APIs.\n- Prefer readonly/immutable data where reasonable (e.g. `readonly` props, avoid\n  mutating function arguments).\n\n## Performance Mindset\n\n- Avoid unnecessary allocations and copies:\n  - No pointless spreading (`{ ...obj }`) or `JSON.parse(JSON.stringify(...))`\n    in hot paths.\n- Avoid creating new functions/objects inside tight loops or very frequently\n  rendered components unless memoized.\n- Use `useMemo` / `useCallback` **only** when they prevent real, recurring work\n  or re-renders, not everywhere by default.\n- Be careful not to introduce extra re-renders by changing prop shapes\n  unnecessarily.\n- Prefer algorithmic improvements and proper data structures over\n  micro-optimizations when performance is a concern.\n\n## Code Quality Maintenance\n\n- Refactor continuously in small, safe steps.\n- Fix technical debt early when you touch a piece of code.\n- Prefer explicit, predictable behavior over clever tricks.\n- Keep the codebase internally consistent.\n\n## Testing & Safety\n\n- For non-trivial logic changes, prefer adding or updating tests.\n- Write tests before fixing bugs when possible (regression tests).\n- Keep tests readable, deterministic, and maintainable.\n- Test edge cases, error conditions, and boundary values.\n- Avoid relying on implementation details; test behavior and contracts.\n- Performance is must have, so test it.\n\n<!-- File: packages/joint-cli/README.md (Tokens: ~431 | Category: plugin-manifest) -->\n# @joint/cli\n\nCommand-line tool for [JointJS](https://jointjs.com).\n\n## Quick Start\n\n```bash\nnpx @joint/cli list\nnpx @joint/cli download kitchen-sink/js\n```\n\n## Installation\n\n```bash\nnpm install -g @joint/cli\n```\n\nOnce installed globally, the `joint` command is available:\n\n```bash\njoint list\njoint download kitchen-sink/js\n```\n\n## Commands\n\n### `joint list`\n\nList available examples from the [joint-demos](https://github.com/clientIO/joint-demos) repository.\n\n```bash\njoint list\n```\n\n### `joint download <name> [dest]`\n\nDownload an example into the current working directory.\n\n```bash\n# Downloads into ./kitchen-sink-js/\njoint download kitchen-sink/js\n\n# Downloads into ./my-app/\njoint download kitchen-sink/js my-app\n\n# Downloads into the current directory (must be empty or use --force)\njoint download kitchen-sink/js .\n```\n\nIf the destination directory already exists, use `--force` to overwrite:\n\n```bash\njoint download kitchen-sink/js --force\njoint download kitchen-sink/js . --force\n```\n\n## Options\n\n| Option | Description |\n|---|---|\n| `--help`, `-h` | Show help message |\n| `--version`, `-v` | Show version number |\n| `--owner <name>` | GitHub repo owner (default: `clientIO`) |\n| `--branch <name>` | GitHub repo branch (default: `main`) |\n| `--force` | Overwrite existing files when downloading |\n\n### Working with forks\n\nUse `--owner` and `--branch` to list and download examples from a fork:\n\n```bash\njoint list --owner myGitHubUser\njoint download kitchen-sink/js --owner myGitHubUser --branch dev\n```\n\n## Environment Variables\n\n| Variable | Description |\n|---|---|\n| `GITHUB_TOKEN` | Optional GitHub token to avoid API rate limiting |\n\n## Requirements\n\n- Node.js >= 22\n- Git\n\n<!-- File: packages/joint-core/demo/custom-embedding/README.md (Tokens: ~120 | Category: plugin-manifest) -->\n# JointJS Custom Embedding Demo\n\n## Setup\n\nUse Yarn to run this demo.\n\nYou need to build *JointJS* first. Navigate to the root folder and run:\n```bash\nyarn install\nyarn run build\n```\n\nThen navigate to this directory and open `index.html` in your browser:\n```bash\nopen index.html\n```\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n\n<!-- File: packages/joint-core/demo/custom-shapes/README.md (Tokens: ~116 | Category: plugin-manifest) -->\n# JointJS Custom Shapes Demo\n\n## Setup\n\nUse Yarn to run this demo.\n\nYou need to build *JointJS* first. Navigate to the root folder and run:\n```bash\nyarn install\nyarn run build\n```\n\nNavigate to this directory, then run:\n```bash\nyarn install\nyarn start\n```\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n\n<!-- File: packages/joint-core/demo/devs/README.md (Tokens: ~977 | Category: plugin-manifest) -->\nThe **Devs** demo introduces a ready-to-use shape with predefined input & output port groups and simplified API.\n\n### joint.shapes.devs.Model\n\nThe Model shape implements simple API on top of the JointJS built-in ports. It splits ports into two semantic groups (**in** and **out**) and adds convenient methods for adding and removing ports.\n\n| Attribute | Description |\n| --- | --- |\n| inPorts | an array of all input ports |\n| outPorts | an array of all output ports |\n\n##### shapes.devs.Model.addInPort\n\n    element.addInPort(port, [opt])\n\nAdd a single port into the \\`inPorts\\` array, where \\`port\\` is a name of the port.\n\n##### shapes.devs.Model.addOutPort\n\n    element.addOutPort(port, [opt])\n\nAdd a single port into the \\`outPorts\\` array, where \\`port\\` is a name of the port.\n\n##### shapes.devs.Model.changeInGroup\n\n    element.changeInGroup(properties, [opt])\n\nChange the settings for the input ports, where \\`properties\\` is an object with a [group configuration](https://resources.jointjs.com/docs/jointjs/v3.7/joint.html#dia.Element.ports.interface). It extends the previous settings with the new configuration by default. Pass `{ rewrite: true }` via `opt` to invalidate the previous settings.\n\n##### shapes.devs.Model.changeOutGroup\n\n    element.changeOutGroup(properties, [opt])\n\nChange the settings for the output ports, where \\`properties\\` is an object with a [group configuration](https://resources.jointjs.com/docs/jointjs/v3.7/joint.html#dia.Element.ports.interface). It extends the previous settings with the new configuration by default. Pass `{ rewrite: true }` via `opt` to invalidate the previous settings.\n\n##### shapes.devs.Model.removeInPort\n\n    element.removeInPort(port, [opt])\n\nRemove a port from an element from the \\`inPorts\\` array, where \\`port\\` is a name of the port.\n\n##### shapes.devs.Model.removeOutPort\n\n    element.removeOutPort(port, [opt])\n\nRemove a port from an element from the \\`outPorts\\` array, where \\`port\\` is a name of the port.\n\n### joint.shapes.devs.Link\n\nThe `devs.Link` extends the `joint.shapes.standard.Link` and changes the link appearance.\n\n#### Example usage\n\n    const shape = new joint.shapes.devs.Model({\n        position: {\n            x: 100,\n            y: 100\n        },\n        inPorts: ['in1', 'in2'],\n        outPorts: ['out1', 'out2']\n    });\n\n    shape.addTo(graph);\n\n    // adding/removing ports dynamically\n    shape.addInPort('in3');\n    shape.removeOutPort('out1').addOutPort('out3');\n\n    const link = new joint.shapes.devs.Link({\n        source: {\n            id: shape.id,\n            port: 'out3'\n        },\n        target: {\n            x: 200,\n            y: 300\n        }\n    });\n    link.addTo(graph);\n\n    // moving the input ports from `left` to `top`\n    shape.changeInGroup({ position: 'top' });\n    // moving the output ports 'right' to 'bottom'\n    shape.changeOutGroup({ position: 'bottom' });\n\n\n#### Hierarchical diagrams\n\nThere are two more shapes designed for hierarchical diagrams as part of the demo - `devs.Atomic` and `devs.Coupled`. They inherit from the `devs.Model` and differ only in the color and size. The purpose of these shapes is to enable you to implement a custom logic in your application based on their type. For instance a `devs.Coupled` can embed `devs.Atomic`'s but not the other way round as shown in the demo below.\n\nWhen working with hierarchical diagrams there is a few methods, that might come in handy.\n\n[`coupled.embed(atomic)`](https://resources.jointjs.com/docs/jointjs/v3.7/joint.html#dia.Element.prototype.embed) that can put the \\`atomic\\` shape into the \\`coupled\\`.\n\n[`coupled.fitToChildren()`](https://resources.jointjs.com/docs/jointjs/v3.7/joint.html#dia.Element.prototype.fitToChildren) that resizes the \\`coupled\\` shape such that all embedded elements are visually contained within it.\n\n[`link.reparent()`](https://resources.jointjs.com/docs/jointjs/v3.7/joint.html#dia.Link.prototype.reparent) that finds the best parent for the \\`link\\` based on the source and target element.\n\n<!-- File: packages/joint-core/demo/elk/README.md (Tokens: ~118 | Category: plugin-manifest) -->\n# JointJS Eclipse Layout Kernel Graph Demo\n\n## Setup\n\nUse Yarn to run this demo.\n\nYou need to build *JointJS* first. Navigate to the root folder and run:\n```bash\nyarn install\nyarn run build\n```\n\nNavigate to this directory, then run:\n```bash\nyarn install\nyarn start\n```\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n\n<!-- File: packages/joint-core/demo/rough/README.md (Tokens: ~131 | Category: plugin-manifest) -->\n# JointJS RoughJS Demo\n\n## Setup\n\nUse Yarn to run this demo.\n\nYou need to build *JointJS* first. Navigate to the root folder and run:\n```bash\nyarn install\nyarn run build\n```\n\nNavigate to this directory, then run:\n```bash\nyarn install\n```\n\nThen open `index.html` in your browser:\n```bash\nopen index.html\n```\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n\n<!-- File: packages/joint-core/demo/ts-demo/README.md (Tokens: ~115 | Category: plugin-manifest) -->\n# JointJS TypeScript Demo\n\n## Setup\n\nUse Yarn to run this demo.\n\nYou need to build *JointJS* first. Navigate to the root folder and run:\n```bash\nyarn install\nyarn run build\n```\n\nNavigate to this directory, then run:\n```bash\nyarn install\nyarn start\n```\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n\n<!-- File: packages/joint-core/demo/vuejs/README.md (Tokens: ~131 | Category: plugin-manifest) -->\n# JointJS VueJS Demo\n\n## Setup\n\nUse Yarn to run this demo.\n\nYou need to build *JointJS* first. Navigate to the root folder and run:\n```bash\nyarn install\nyarn run build\n```\n\nNavigate to this directory, then run:\n```bash\nyarn install\n```\n\nThen open `index.html` in your browser:\n```bash\nopen index.html\n```\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n\n<!-- File: packages/joint-core/README.md (Tokens: ~1835 | Category: plugin-manifest) -->\n# JointJS — The JavaScript diagramming library you don't replace at scale\n\n[JointJS](https://jointjs.com) is a proven JavaScript/TypeScript diagramming library for building complex, production-scale diagramming applications.\n\nTeams use it to build AI workflow builders, flowchart and process editors, BPMN modelers, SCADA/HMI and monitoring dashboards, data modeling tools, energy network diagrams, electronic design tools, and org chart builders.\n\nJointJS is framework-agnostic, with first-class support for React.\n\n[![Use Cases](https://user-images.githubusercontent.com/3967880/200360293-808f148c-32af-4f46-bec1-b4ae4e1592a0.jpg)](https://user-images.githubusercontent.com/3967880/200360293-808f148c-32af-4f46-bec1-b4ae4e1592a0.jpg)\n\nFurther information, examples and documentation can be found at [jointjs.com](https://jointjs.com).\n\n- 🚀 [Getting started guide](https://docs.jointjs.com)\n- 💡 Ask a question or share feedback in our [GitHub discussions](https://github.com/clientIO/joint/discussions)\n- 🖊️ [180+ demo apps](https://www.jointjs.com/demos) to kickstart your development\n- 🤖 [MCP server integration](https://mcp.jointjs.com/mcp) for AI coding agents\n- ⚛️ **New:** [JointJS for React](https://www.jointjs.com/react-diagrams) — build diagramming UIs in your React app\n\n## Use cases\n\nJointJS is used to build production diagramming applications across many domains:\n\n| Domain | Examples |\n|---|---|\n| Workflow & process | Flowchart editors, process automation, activity and sequence diagrams |\n| AI workflow builders | Node-based AI pipeline and agent builders, marketing automation studios |\n| BPMN | Business process modelers, swimlanes, Visio BPMN import/export |\n| Data modeling | ER diagrams, database schema designers, data mapping tools |\n| UML & software modeling | Class, sequence, statechart and use-case diagrams |\n| Industrial / SCADA & HMI | P&ID diagrams, monitoring and control dashboards |\n| Energy networks | Power and telecom network diagrams, DWDM circuits |\n| Electronic design | Logic circuits, wiring and cable diagrams |\n| Org charts | Organizational charts, hierarchy and tree builders |\n| Timelines | Project and milestone timelines, Gantt and PERT charts |\n\nExplore all [180+ demo apps](https://www.jointjs.com/demos) that serve as boilerplates for your project.\n\n## Framework integrations\n\nJointJS is framework-agnostic and works with any frontend stack. React has first-class, native support; the others have integration guides and examples:\n\n- [**React**](https://docs.jointjs.com/react) — native components and hooks (JointJS for React)\n- [Angular](https://docs.jointjs.com/learn/integration/angular/)\n- [Vue](https://docs.jointjs.com/learn/integration/vue)\n- [Svelte](https://docs.jointjs.com/learn/integration/svelte)\n- [Salesforce Lightning](https://docs.jointjs.com/learn/integration/lightning)\n\n## Features\n\n- essential diagram elements (rect, circle, ellipse, text, image, path)\n- ready-to-use diagram elements of well-known diagrams (ERD, Org chart, FSA, UML, PN, DEVS, ...)\n- custom diagram elements based on SVG or programmatically rendered\n- connecting diagram elements with links or links with links\n- customizable links, their arrowheads and labels\n- configurable link shapes (anchors, connection points, vertices, routers, connectors)\n- custom element properties and data\n- import/export from/to JSON format\n- customizable element ports (look and position, distributed around shapes or manually positioned)\n- rich graph API (traversal, dfs, bfs, find neighbors, predecessors, elements at point, ...)\n- granular interactivity\n- hierarchical diagrams (containers, embedded elements, child-parent relationships)\n- element & link tools (buttons, status icons, tools to change the shape of links, ...)\n- highlighters (provide visual emphasis to your elements)\n- automatic layouts (arrange the elements and links automatically)\n- highly event driven (react on any event that happens inside the diagram)\n- zoom in/out\n- touch support\n- MVC architecture\n- SVG based\n- ... a lot more\n\n## Using JointJS with AI coding agents\n\nJointJS is built to work well with AI-assisted development.\n\n**MCP server** — connect your AI coding agent (Claude Code, Cursor, and other MCP-compatible tools) to the JointJS Model Context Protocol server so it can search the official docs and demos while you build.\n\n```\nhttps://mcp.jointjs.com/mcp\n```\n\nExposes `search_docs` and `search_demos` so your agent writes correct JointJS code against the real API instead of guessing.\n\n## Supported browsers\n\n- Latest Google Chrome (including mobile)\n- Latest Firefox\n- Latest Safari (including mobile)\n- Latest Microsoft's Edge\n- Latest Opera\n\n## Commercial version\n\nThe open-source library covers core diagramming. **[JointJS+](https://www.jointjs.com/jointjs-plus)** adds production-ready plugins (stencils, inspectors, toolbars, advanced layouts, import/export, and more), professional demo apps (including source code), JointJS for React, and direct support from the team that builds the library. [Start a free 30-day trial.](https://www.jointjs.com/free-trial)\n\n## Development Environment\n\nIf you want to work on *JointJS* locally, use the following guidelines to get started.\n\n### Dependencies\n\nMake sure you have the following dependencies installed on your system:\n\n- [Node.js](https://nodejs.org/)\n- [grunt-cli](http://gruntjs.com/using-the-cli)\n- [git](https://git-scm.com/)\n- [yarn](https://yarnpkg.com/getting-started/install)\n\nThe installation requires Node version 22.14.0, to avoid syntax errors during installation.\n\nMake sure that you are using Yarn version >= 2.0.0, so that you have access to [Yarn workspace ranges](https://yarnpkg.com/features/workspaces#workspace-ranges-workspace) functionality. If you are using [Volta](https://volta.sh/), it will automatically read this restriction from `package.json`.\n\n### Setup\n\nClone this git repository:\n\n```\ngit clone https://github.com/clientIO/joint.git\n```\n\nNavigate to the `joint` directory:\n\n```\ncd joint\n```\n\nInstall all dependencies:\n\n```\nyarn install\n```\n\nGenerate distribution files from the source code:\n\n```\nyarn run dist\n```\n\nYou are now ready to browse our example applications, which combine functionality from multiple JointJS packages:\n\n```\ncd examples\n```\n\nRefer to each application's `README.md` file for additional instructions.\n\nYou can also browse the demo applications of our JointJS Core package:\n\n```\ncd packages/joint-core/demo\n```\n\nMost demos can be run by simply opening the `index.html` file in your browser. Some demos have additional instructions, which you can find in their respective `README.md` files.\n\n### Tests\n\nTo run all tests:\n\n```\nyarn run test\n```\n\nTo run only the server-side tests:\n\n```\nyarn run test-server\n```\n\nTo run only the client-side tests:\n\n```\nyarn run test-client\n```\n\nTo run only TypeScript tests:\n\n```\nyarn run test-ts\n```\n\n### Lint\n\nTo check for linting errors in `src` and `types` directories:\n\n```\nyarn run lint\n```\n\nTo auto fix errors, run eslint for `src` and `types` directories:\n\n```\nyarn run lint-fix\n```\n\n### Code Coverage Reports\n\nTo output a code coverage report in HTML:\n\n```\nyarn run test-coverage\n```\n\nTo output a code coverage report in [lcov format](http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php):\n\n```\nyarn run test-coverage-lcov\n```\n\nThe output for all unit tests will be saved in the `packages/joint-core/coverage` directory.\n\n## Contributors\n\n[![](https://contrib.rocks/image?repo=clientIO/joint)](https://github.com/clientIO/joint/graphs/contributors)\n\n## License\n\nThe *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n\n<!-- File: packages/joint-decorators/README.md (Tokens: ~3092 | Category: plugin-manifest) -->\n# JointJS Decorators\n\nECMAScript / TypeScript decorator for defining *[JointJS](https://www.jointjs.com/)* shapes.\n\nThis library fully depends on [JointJS](https://github.com/clientio/joint) (*>=3.5*), so please read its README before using this library.\n\n## Setup\n\nEnable the [experimentalDecorators](https://www.typescriptlang.org/docs/handbook/decorators.html) compiler option in your `tsconfig.json`.\n\nThen install JointJS Decorators from NPM:\n```bash\nnpm i -S @joint/decorators\n```\n\n## License\n\n[Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/)\n\n## Usage\n\nThere are a few class decorators:\n\n- [`@Model`](#Model)\n- [`@View`](#View)\n\nAnd several class member decorators:\n\n- [`@Function`](#Function)\n- [`@On`](#On)\n- [`@SVGAttribute`](#SVGAttribute)\n\n---\n\n## <a id=\"Model\"></a> @Model(options: ModelOptions)\n\nThe decorator allows you to:\n- paste an existing SVG and use it as the model's markup\n- keep your SVG attributes in sync with your model's attributes\n- transform data using functions\n- introduce a new SVG attribute or change the behavior of an existing one\n\n```ts\nimport { dia } from '@joint/core';\nimport { Model } from '@joint/decorators';\n\n@Model({\n```\n```handlebars\n    template: `\n        <g>\n            <rect\n                width=\"calc(w)\"\n                height=\"calc(h)\"\n                :fill=\"{{color}}\"\n                stroke=\"black\"\n            />\n            <text\n                x=\"calc(0.5*w)\"\n                y=\"calc(0.5*h)\"\n                text-anchor=\"middle\"\n                text-vertical-anchor=\"middle\"\n                font-size=\"14\"\n                font-family=\"sans-serif\"\n                fill=\"black\"\n            >{{firstName}} {{lastName}}</text>\n        </g>\n    `,\n```\n```ts\n    attributes: {\n        color: 'red',\n        firstName: 'John',\n        lastName: 'Doe'\n    }\n})\nclass MyElement extends dia.Element {\n\n}\n```\n\n### ModelOptions\n\n| Option | Description | Optional\n|---|---|---|\n| [template](#Model.template) | the SVG string markup of the model | No |\n| [attributes](#Model.attributes) | the default attributes of the model | Yes |\n| [namespace](#Model.namespace) | the namespace for the model class to be added to | Yes |\n\n### <a id=\"Model.template\"></a> **template**\n\nThe decorator uses an SVG-based template syntax that allows you to declaratively bind the rendered DOM to the underlying model's data.\nAll templates are syntactically valid SVG that can be parsed by spec-compliant browsers and SVG parsers.\n\n> While using the SVG XML string in the markup attribute is not recommended (every cell view needs to parse the string and it might affect the performance), the parsing of the decorator's template runs only once per class (translating it into [JSON markup](https://resources.jointjs.com/docs/jointjs/v3.5/joint.html#dia.Cell.markup) and defining event listeners needed for reactivity).\n\n#### <a id=\"Model.template.text-interpolation\"></a> Text Interpolation\n\nThe most basic form of data binding is text interpolation using the \"Mustache\" syntax (double curly braces):\n\n```handlebars\n<text font-size=\"14\">{{ label }}</text>\n```\n\nThe mustache tag will be replaced with the value of the `label` property from the corresponding model's instance. It will also be updated whenever the `label` property changes.\n\n#### <a id=\"Model.template.attributes\"></a> Attributes Binding\n\nTo bind to an SVG attribute to a model's attribute, add a colon symbol (`:`) before the attribute's name.\n\n```handlebars\n<rect :fill=\"color\" />\n```\n\nThe colon symbol, `:`, instructs the decorator to keep the SVG attribute in sync with the model's attribute.\nThe model's `color` value can be set this way.\n\n```ts\nmodel.set('color', 'red');\n```\n\nIf the bound value is `null`, then the SVG attribute will be removed from the rendered element.\n\n```ts\nmodel.set('color', null);\n```\n\nIt's possible to use mustaches inside binding expressions\n    to combine multiple model's attributes into a single result.\n\n```handlebars\n<rect :fill=\"rgb({{red}},{{green}},{{blue}})\" />\n```\n\n#### <a id=\"Model.template.functions\"></a> Calling Functions\n\nThe value of an attribute can be modified with [functions](#Function) before set/display.\n\n```handlebars\n<rect :stroke=\"color\" :fill=\"lighten(color)\"/>\n<text>{{ capitalize(label) }}</text>\n```\n> Functions called inside binding expressions will be called every time the cell view updates, so they should not have any side effects, such as changing data or triggering asynchronous operations.\n\nIt is possible to call a component-exposed method inside a binding expression only if the method is decorated with the [Function](#Function) decorator.\n\nThe function can accept any number of additional arguments. Every such argument shall be parsable with the [JSON.parse](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) function.\n\n```ts\n@Model({\n    template: `\n        <text y=\"10\">{{ maxLength(label1, 20) }}</text>\n        <text y=\"30\">{{ maxLength(label2, 10) }}</text>\n    `\n})\nclass MyElement extends dia.Element {\n\n    @Function()\n    maxLength(value: string, max: number) {\n        return value.substr(0, max);\n    }\n}\n```\n\nMultiple dependencies can be defined using an array.\n\n```handlebars\n<path :d=\"data([param1, param2])\"/>\n```\n\n> The function is run every time one or more attributes in the dependency array are changed.\n\n```ts\n@Model({\n    attributes: {\n        param1: 20,\n        param2: 30\n    },\n    template: `\n        <path :d=\"data([param1, param2])\" stroke=\"white\" fill=\"transparent\" />\n    `\n})\nclass Arrow extends dia.Element {\n    @Function()\n    data(param1: number, param2: number): string {\n        return `\n                M ${param1} 0\n                L calc(w) calc(0.5*h)\n                L ${param1}  calc(h)\n                V calc(h/2 + ${param2 / 2})\n                H 0\n                v -${param2}\n                H ${param1}\n                Z\n        `;\n    }\n}\n```\n\n#### <a id=\"Model.template.selectors\"></a> Selectors\n\nIf you want to modify any of the template attributes programmatically, you must add the `@selector` attribute to the SVG element.\n\n```handlebars\n<rect @selector=\"body\" stroke=\"black\" fill=\"red\">\n```\n\n```ts\nmodel.attr(['body', 'fill'], 'blue');\n```\n\nThe `<g>` wrapper in the template is added automatically and always has a `@selector` equal to `root`. In case you want to add SVG attributes to the root group, wrap the template with one of them.\n\n```handlebars\n<g @selector=\"root\" data-tooltip=\"My Tooltip\">\n    <rect @selector=\"body\" stroke=\"black\" fill=\"red\">\n</g>\n```\n\nTo create selectors pointing to multiple SVG elements at once, use `@group-selector`.\n\n```handlebars\n<rect @group-selector=\"rectangles\" fill=\"red\">\n<rect @group-selector=\"rectangles\" fill=\"blue\">\n<rect @group-selector=\"rectangles\" fill=\"green\">\n```\n\n```ts\n// Change the stroke of all rectangles\nmodel.attr(['rectangles', 'stroke'], 'black');\n```\n\n#### <a id=\"Model.template.caveats\"></a> Caveats\n\nSome JointJS attributes expect their value to be an object ([fill](https://resources.jointjs.com/docs/jointjs/v3.5/joint.html#dia.attributes.textWrap) & [stroke](https://resources.jointjs.com/docs/jointjs/v3.5/joint.html#dia.attributes.stroke) gradient, [filters](https://resources.jointjs.com/docs/jointjs/v3.5/joint.html#dia.attributes.filter), [markers](https://resources.jointjs.com/docs/jointjs/v3.5/joint.html#dia.attributes.sourceMarker) and [textWrap](https://resources.jointjs.com/docs/jointjs/v3.5/joint.html#dia.attributes.textWrap)).\n\nThe solution is to define the property inside the [attributes](#Model.attributes) (mixing the template attributes with explicit model attributes).\n\n```ts\nconst selector = 'label';\n\n@Model({\n    attributes: {\n        title: 'My Title',\n        attrs: {\n            [selector]: {\n                textWrap: {\n                    maxLineCount: 1,\n                    ellipsis: true\n                }\n            }\n        }\n    },\n    template: `\n        <text @selector=\"${selector}\">{{title}}</text>\n    `\n})\nclass MyElement extends dia.Element {\n\n}\n```\n\n### <a id=\"Model.attributes\"></a> **attributes**\n\nThe default attributes of the model. When creating an instance of the model, any unspecified attributes will be set to their default value.\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { Model } from '@joint/decorators';\n\n@Model({\n    attributes: {\n        color: 'red'\n    }\n})\nclass MyElement extends dia.Element {\n\n}\n```\n\nis equivalent to\n\n```ts\nimport { dia, shapes } from '@joint/core';\n\nclass MyElement extends dia.Element {\n\n    defaults() {\n        const attributes = {\n            color: 'red'\n        };\n        return {\n            ...super.defaults,\n            ...attributes,\n            type: 'MyElement'\n        }\n    }\n}\n```\n\n###  <a id=\"Model.namespace\"></a>  **namespace**\n\nSyntactic sugar for adding a model to the namespace.\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { Model } from '@joint/decorators';\n\n@Model({\n    namespace: shapes\n})\nclass MyElement extends dia.Element {\n\n}\n```\n\nis equivalent to\n\n```ts\nimport { dia, shapes } from '@joint/core';\n\nclass MyElement extends dia.Element {\n\n}\n\nObject.assign(shapes, {\n    'MyElement': MyElement\n});\n```\n\n---\n\n## <a id=\"View\"></a> @View(options: ViewOptions)\n\n### ViewOptions\n\n| Option | Description | Optional |\n|---|---|---|\n| namespace | the namespace for the view class to be added to | Yes\n| models | an array of model classes this view is to be used with | Yes\n\nDefine a new cell view, which is automatically used by 2 different models.\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { View } from '@joint/decorators';\n\n@View({\n    namespace: shapes\n    models: [MyElement, MyOtherElement]\n})\nclass MyElementView extends dia.ElementView {\n\n}\n```\n\nis equivalent to\n\n```ts\nimport { dia, shapes } from '@joint/core';\n\nclass MyElementView extends dia.ElementView {\n\n}\n\nObject.assign(shapes, {\n    'MyElementView': MyElementView,\n    'MyOtherElementView': MyElementView\n});\n```\n---\n## <a id=\"Function\"></a> @Function(name?: string)\n\nDefine functions to transform data (e.g strings, amounts, dates) to be used within the [template](#Model.template).\n\n```ts\n@Model({\n    template: `\n        <text>{{ capitalize(name) }}</text>\n    `,\n    attributes: {\n        name: 'john',\n    }\n})\nclass MyElement extends dia.Element {\n\n    @Function()\n    capitalize(value: string) {\n        return value.charAt(0).toUpperCase() + value.slice(1);\n    }\n}\n```\n\n---\n\n## <a id=\"SVGAttribute\"></a> @SVGAttribute(attributeName: string)\n\nIntroduce new SVG attributes or redefine existing ones.\n\n```ts\nimport { dia, g, attributes } from '@joint/core';\nimport { Model, SVGAttribute } from '@joint/decorators';\n\n@Model({\n    attributes: {\n        width: 140,\n        height: 100\n    },\n    template: `\n        <rect\n            line-style=\"dashed\"\n            stroke-width=\"2\"\n        />\n    `,\n})\nclass MyElement extends dia.Element {\n\n    /* `stroke-dasharray` that adjusts based on the current node's `stroke-width` */\n    @SVGAttribute('line-style')\n    setStrokeDasharray(\n        this: dia.CellView,\n        value: string,\n        rect: g.Rect,\n        node: SVGElement,\n        nodeAttrs: attributes.SVGAttributes\n    ) {\n        const { strokeWidth = 1 } = nodeAttrs;\n        let pattern;\n        switch (value) {\n            case 'dashed': {\n                pattern = `${4 * strokeWidth},${2 * strokeWidth}`;\n                break;\n            }\n            case 'dotted': {\n                pattern = `${strokeWidth},${strokeWidth}`;\n                break;\n            }\n            case 'solid': {\n                pattern = 'none';\n                break;\n            }\n            default: {\n                throw new Error('Invalid line-style value.');\n            }\n        }\n        node.setAttribute('stroke-dasharray', pattern);\n    }\n}\n```\n### SVGAttribute function signature\n\n| Argument | Description | Example |\n|---|---|---|\n| value | the right-hand side of the template's attribute | `\"dashed\"` |\n| rect | a rectangle describing the coordinate system the node is rendered in (if no `ref` attribute is in use, the value is the model's bounding box relative to the model's position, otherwise it is the relative bounding box of the node referenced by the `ref` attribute) | `new g.Rect(0, 0, 140, 100)` |\n| node | a rendered DOM [SVGElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement) | `<rect/> as SVGElement` |\n| nodeAttrs | an object with all defined attributes of the node | `{ lineStyle: \"dashed\", strokeWidth: \"2\" }` |\n\n---\n## <a id=\"On\"></a> @On(eventName: string)\n\n Decorate an event handler in the context of the method it refers to.\n\n```ts\nclass MyElementView extends dia.ElementView {\n\n    @On('click')\n    onClick() {\n        console.log('click!', this.model.id);\n    }\n}\n```\n\n---\n\nCopyright © 2013-2026 client IO\n\n<!-- File: packages/joint-eslint-config/README.md (Tokens: ~137 | Category: plugin-manifest) -->\n# Shared package for using eslint config.\n\nConfiguring ESLint in multiple projects is a repetitive task, so we created this package to share a common ESLint configuration across all JointJS packages.\n\n### Issue with running `yarn lint`\n\nIf there is an issue with running `yarn lint`, try removing `node_modules/` and re-installing from monorepo root! This should fix the issue, as sometimes, when `yarn install` is used in a monorepo package directly, it **overwrites** default `eslint-configs`.\n\nTo avoid these issues, **use `yarn install` directly from monorepo root folder**, instead of per package.\n\n<!-- File: packages/joint-layout-directed-graph/README.md (Tokens: ~141 | Category: plugin-manifest) -->\n# JointJS Directed Graph Layout\n\nA module for automatic layout of directed graphs for *[JointJS](https://www.jointjs.com/)*.\n\nThis library fully depends on [JointJS](https://github.com/clientio/joint) (*>=4.0*), so please read its `README.md` before using this library.\n\n### Tests\n\nGenerate distribution files from the source code:\n```bash\nyarn run dist\n```\n\nTo run all tests:\n```bash\nyarn run test\n```\n\n## License\n\n[Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/)\n\nCopyright © 2013-2026 client IO\n\n<!-- File: packages/joint-layout-elk/README.md (Tokens: ~5082 | Category: plugin-manifest) -->\n# JointJS ELK Layout\n\nA module for automatic layout of *[JointJS](https://www.jointjs.com)* graphs using the [Eclipse Layout Kernel (ELK)](https://www.eclipse.org/elk/), via its JavaScript port [elkjs](https://github.com/kieler/elkjs).\n\nThis library depends on [JointJS](https://github.com/clientio/joint) (*>=4.0*), so please read its `README.md` before using this library.\n\n`layout()` is a one-off, asynchronous transform: it builds an ELK graph from a JointJS graph (embedded elements become nested containers at any depth, ports and link labels are included), runs ELK's layout algorithm, and writes the result (positions, sizes, link vertices/anchors, label positions) back onto the graph. It does not keep the graph laid out afterwards - call it again after further changes.\n\n## 🚀 Quick Start\n\n### Installation\n\n```bash\nnpm install @joint/layout-elk\n```\n\n#### UMD build (script tags)\n\n`dist/umd/index.js` keeps `@joint/core` and `elkjs` external - load both first: `@joint/core` as the `joint` global, and `elkjs/lib/elk.bundled.js` as the `ELK` global, which `layout()` runs on the main thread. The package then is `joint.layout.ELK`.\n\n```html\n<script src=\"node_modules/@joint/core/dist/joint.min.js\"></script>\n<script src=\"node_modules/elkjs/lib/elk.bundled.js\"></script>\n<script src=\"node_modules/@joint/layout-elk/dist/umd/index.min.js\"></script>\n<script>\n    joint.layout.ELK.layout(graph).then(({ bbox }) => { /* ... */ });\n</script>\n```\n\n### Basic Usage\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { layout } from '@joint/layout-elk';\n\nconst graph = new dia.Graph({}, { cellNamespace: shapes });\nconst paper = new dia.Paper({\n    model: graph,\n    cellViewNamespace: shapes,\n    el: document.getElementById('paper'),\n});\n\nconst rect1 = new shapes.standard.Rectangle({ id: 'a', size: { width: 80, height: 40 }, attrs: { label: { text: 'A' }}});\nconst rect2 = new shapes.standard.Rectangle({ id: 'b', size: { width: 80, height: 40 }, attrs: { label: { text: 'B' }}});\nconst link = new shapes.standard.Link({ source: { id: 'a' }, target: { id: 'b' }});\n\ngraph.addCells([rect1, rect2, link]);\n\nconst { bbox } = await layout(graph, {\n    elkLayoutOptions: {\n        'elk.algorithm': 'layered',\n        'elk.direction': 'RIGHT',\n        'elk.edgeRouting': 'ORTHOGONAL'\n    }\n});\n```\n\n## 📖 API Reference\n\n### `layout(graphOrCells, options?): Promise<LayoutResult>`\n\n- `graphOrCells`: `dia.Graph | dia.Cell[]` - the graph to lay out, or the cells to lay out.\n- `options?`: `LayoutOptions` - layout configuration (see below).\n\nA list of cells is both the selection and the order:\n\n- **Selection** - only the given elements are laid out. An element whose parent isn't given is laid out as a top-level element.\n- **Links** - a link is laid out only if both of the elements it connects are given too. One that isn't - including a link connected to a point, to another link, or one that is in no graph, whose ends cannot be resolved at all - is left out of the layout entirely: no `exportLink` call, no route, and its vertices, anchors and label positions are left as they are.\n- **Order** - the top-level elements follow the order they are given in, and so do each container's children. The links follow their order too, inside each container.\n- Elements and links go in the same list, in any order - a link may come before the elements it connects. A cell listed more than once is laid out once.\n- ELK identifies everything by a string, while a JointJS id may be a string or a number - so two cells, or two ports of one element, whose ids differ only in type (`5` and `'5'`) would share one id in the ELK graph. `layout()` rejects rather than lay one of them out and leave the other where it was.\n\nThe layout is applied in a single `'layout'` batch on the graph the cells belong to, so it emits one combined change rather than one per element, port and link. An element that is in no graph is laid out and positioned like any other, but contributes no change events - which makes it useful as a container that only exists to group others for the layout.\n\n```ts\n// Lay out a selection, with its embedded elements and the links between its elements.\n// Everything else in the graph is left alone.\nawait layout(graph.getSubgraph(selectedElements, { deep: true }));\n```\n\n```ts\ninterface LayoutResult {\n    bbox: g.Rect;      // Tight bounding box of the laid out graph\n    elkGraph: ElkNode;  // The raw ELK layout result (e.g. for junction points)\n}\n```\n\n### `LayoutOptions`\n\n```ts\ninterface LayoutOptions {\n    // The ELK instance to lay out with, e.g. one running in a Web Worker (see `createWorkerElk()` below).\n    elk?: WorkerElk | ELK; // Default: a shared instance running on the main thread\n    // ELK layout options, in either spelling (see \"Layout option values\" below).\n    elkLayoutOptions?: ElkLayoutOptions; // Default: { 'elk.algorithm': 'layered', 'elk.hierarchyHandling': 'INCLUDE_CHILDREN', 'elk.layered.considerModelOrder.portModelOrder': 'true', 'elk.json.edgeCoords': 'ROOT' }\n    // Aborts the layout - `layout()` rejects with the signal's reason and applies nothing.\n    signal?: AbortSignal;\n\n    // Export callbacks (JointJS graph -> ELK graph) - see below.\n    exportElement?: ExportElementCallback;\n    exportPort?: ExportPortCallback;\n    exportPortLabel?: ExportPortLabelCallback;\n    exportLink?: ExportLinkCallback;\n    exportLinkLabel?: ExportLinkLabelCallback;\n\n    // Import callbacks (ELK layout result -> JointJS graph) - see below.\n    setElementAttributes?: SetElementAttributesCallback;\n    setPortAttributes?: SetPortAttributesCallback;\n    setLinkAttributes?: SetLinkAttributesCallback;\n}\n```\n\n### Layout option values\n\nELK takes every option value as a string. This package takes that spelling and the JointJS one, per option, so one object can mix them:\n\n```ts\nelkLayoutOptions: {\n    'elk.spacing.nodeNode': 6,                  // or '6'\n    'elk.rectpacking.orderBySize': true,        // or 'true'\n    'elk.padding': { left: 10, vertical: 40 },  // or '[top=40,left=10,bottom=40,right=0]'\n    'elk.position': { x: 10, y: 20 },           // or '10,20'\n    'elk.nodeLabels.placement': ['INSIDE', 'V_TOP', 'H_LEFT'],  // or 'INSIDE V_TOP H_LEFT'\n    'elk.direction': 'RIGHT'                    // enums are strings either way\n}\n```\n\nThe string spelling stays supported: it is what the ELK reference page linked from each option uses, and what options that arrive as data - a saved diagram, a backend's config - are already shaped like.\n\n- **Numbers and booleans** take `6` / `true` on any option ELK types as one.\n- **Paddings and margins** (`elk.padding`, `elk.margins`, `elk.spacing.portsSurrounding`, `elk.nodeLabels.padding`) take a [`dia.Sides`](https://docs.jointjs.com/api/util/normalizeSides/): a number for every side, or an object naming `top`/`right`/`bottom`/`left`/`horizontal`/`vertical`. **A side the value does not name is `0`**, not the option's own default - ELK's own string parser reads `'[top=40]'` the same way. That default applies only when the option is not set at all.\n- **Points** (`elk.position`, `elk.nodeSize.minimum`, `elk.port.anchor`) take `{ x, y }`, `g.Point` included.\n- **Sets of enum values** (`elk.nodeLabels.placement`, `elk.portLabels.placement`, `elk.contentAlignment`, `elk.nodeSize.constraints`, `elk.nodeSize.options`) take an array of the option's values, typed against its enum - `[]` is the empty set. ELK itself takes them only as one string of the values; elkjs throws on an array. **ELK ignores the whole value, without an error, if any one of its values is not one it knows** - `'INSIDE BOGUS'` does not even apply `INSIDE` - so the array, which is type-checked, is the safer spelling.\n- Anything else goes to ELK as it is, so ELK reports a value it cannot use rather than this package guessing at it.\n\nThe same applies to the `layoutOptions` set on a draft in an [export callback](#export-callbacks).\n\nTo be held to the JointJS spelling alone, annotate your own options with the `Strict` type - there is no flag on this package to switch:\n\n```ts\nimport type { StrictElkLayoutOptions } from '@joint/layout-elk';\n\nconst options: StrictElkLayoutOptions = {\n    'elk.spacing.nodeNode': 6,\n    // Type error: the strict spelling takes `6`, not `'6'`.\n    'elk.layered.spacing.nodeNodeBetweenLayers': '40'\n};\n```\n\n`Native*` is the other direction - ELK's own spelling alone - and is what `toNativeElkNode()` produces. Each has a per-element counterpart: `StrictElkNodeLayoutOptions`, `NativeElkEdgeLayoutOptions`, and so on.\n\n#### `toNativeElkNode(node: ElkNode): NativeElkNode`\n\nReturns a new graph with every layout option written out as the string ELK takes - throughout: the node's own options, its labels', its ports' (and theirs), the edges filed under it (and theirs), and each of its children. The graph passed in is left as it is.\n\n`layout()` does this for you. Reach for it when you build an ELK graph of your own and want to write its options in the JointJS spelling too.\n\n### Seeing what ELK is given\n\n`layout()` builds the ELK graph, runs ELK on it and applies the result back. All three are one step - there is no public way to do them separately, so that what the ids in between stand for stays this package's business.\n\nTo see the graph itself, pass an `elk` of your own: it is any object with `layout()` and `terminate()` (see [Running ELK in a Web Worker](#running-elk-in-a-web-worker)), and it is handed exactly what ELK is handed - every option already written out as a string, so it pastes straight into [elk-live](https://rtsys.informatik.uni-kiel.de/elklive/json.html).\n\n```ts\nimport ELK from 'elkjs/lib/elk.bundled.js';\n\nconst realElk = new ELK();\n\nawait layout(graph, {\n    elk: {\n        layout: (elkGraph) => {\n            console.log(JSON.stringify(elkGraph, null, 2));\n            return realElk.layout(elkGraph);\n        },\n        terminate: () => {}\n    }\n});\n```\n\nThe same hook is how you run ELK somewhere else - a Web Worker, a server, a cache - or change the graph on its way there. `layout()` still applies the result for you.\n\nOne thing to know if you also use [`signal`](#aborting-a-layout): a `terminate()` is how an `elk` says it can be stopped, so `layout()` hands it the signal instead of merely ignoring a late result. The wrapper above cannot stop `elkjs`, so an abort only takes effect once ELK settles. Leave the wrapper out of the abort path, or forward the signal to something that honours it.\n\n### Export callbacks\n\nEach receives an ELK draft already populated with what this package computed for that element/port/link/label, to mutate in place (e.g. to set `elk.*` `layoutOptions`). Returning `false` instead drops it from the ELK graph entirely - for `exportElement`, its whole subtree (embeds, ports, any connected edge) goes with it, and for `exportPort`, the links connected to that port, which ELK can no longer route to. A link left out this way keeps the route it already had.\n\n```ts\ntype ExportElementCallback = (params: { element: dia.Element; elkNode: ElkNodeDraft }) => void | false;\ntype ExportPortCallback = (params: { portId: string; element: dia.Element; elkPort: ElkPortDraft }) => void | false;\ntype ExportPortLabelCallback = (params: { portId: string; element: dia.Element; elkPortLabel: ElkLabelDraft }) => void | false;\ntype ExportLinkCallback = (params: { link: dia.Link; elkEdge: ElkEdgeDraft }) => void | false;\ntype ExportLinkLabelCallback = (params: { link: dia.Link; labelIndex: number; elkEdgeLabel: ElkLabelDraft }) => void | false;\n```\n\n### Import callbacks\n\nEach applies the ELK-computed attributes itself, in place of the package's own default (`element.set(...)`/`element.portProp(...)`/`link.set(...)`) - use one to redirect where the result goes, e.g. into a `transition()`.\n\n```ts\ntype SetElementAttributesCallback = (params: { element: dia.Element; attributes: { position: dia.Point; size?: dia.Size }; elkNode: ElkNode }) => void;\ntype SetPortAttributesCallback = (params: { element: dia.Element; portId: dia.Cell.ID; attributes: { position: { args: dia.Point }; label?: { position: { args: dia.Point } } }; elkPort: ElkPort; hasMoved: boolean }) => void;\ntype SetLinkAttributesCallback = (params: { link: dia.Link; attributes: { vertices: dia.Point[]; source?: dia.Link.EndJSON; target?: dia.Link.EndJSON; labels?: dia.Link.Label[] }; elkEdge: ElkExtendedEdge }) => void;\n```\n\n### Running ELK in a Web Worker\n\nBy default, `layout()` runs ELK on the main thread - nothing to set up with a bundler or in Node, and it works anywhere (browsers, Node/SSR, tests, and the UMD build once `elkjs` is loaded - see \"UMD build\" above). ELK blocks the page while it runs, though: a few milliseconds for a small graph, but up to seconds for one with thousands of elements. To keep the page responsive, run ELK in a Web Worker instead - start one with `createWorkerElk()` and pass it to `layout()` as `elk`:\n\n```ts\nimport { layout, createWorkerElk } from '@joint/layout-elk';\n\nconst elk = createWorkerElk(() => new Worker(new URL('@joint/layout-elk/worker', import.meta.url), { type: 'module' }));\n\nawait layout(graph, { elk });\n\n// Once no longer needed.\nelk.terminate();\n```\n\n#### `createWorkerElk(createWorker: () => Worker): WorkerElk`\n\n`createWorker` starts the worker - running `@joint/layout-elk/worker`, the package's worker script (ELK's own, `elkjs/lib/elk-worker.min.js`). Starting it is up to you, since only your bundler knows where the script ends up:\n\n- **webpack 5, Vite** (dev server and builds) - `new Worker(new URL('@joint/layout-elk/worker', import.meta.url), { type: 'module' })`, as above. Both bundle the worker script as a file of its own. With webpack, keep `output.publicPath` at `'auto'` (the default), so the file is found wherever the app is served from.\n- **Vite** - also `import ElkWorker from '@joint/layout-elk/worker?worker'`, then `createWorkerElk(() => new ElkWorker())`.\n- **No bundler** (e.g. the UMD build) - serve a copy of `elkjs/lib/elk-worker.min.js`, then `createWorkerElk(() => new Worker('/path/to/elk-worker.min.js'))`.\n\nThe returned `WorkerElk`:\n\n- **Starts the worker on its first layout**, then lays out every graph in it, one at a time - share one instance between layouts.\n- **Stops an aborted layout** (see `signal` below) by terminating the worker, if it is busy with it - a new worker takes over the layouts still waiting.\n- **Rejects the layout the worker crashes during** (e.g. out of memory) - a new worker takes over the layouts still waiting.\n- **Rejects every layout when the worker fails to load** (e.g. its script isn't served where `createWorker` starts it from) or can't be started (e.g. a CSP `worker-src` that blocks it) - with an error saying so, rather than running ELK on the main thread. The next layout tries again with a new worker.\n- **`terminate()`** terminates the worker - layouts not settled yet are rejected, and a later layout starts a new worker.\n\nAny other ELK instance works as `elk` too, e.g. `elkjs`'s own `new ELK({ workerUrl })` (`elkjs/lib/elk-api.js`) - without stopping an aborted layout, though (see below).\n\n### Aborting a layout\n\n`layout()` is asynchronous, so the graph may change while ELK is still computing - pass an `AbortSignal` to drop a layout that is no longer wanted (or takes too long). An aborted `layout()` rejects with the signal's reason (an `AbortError` `DOMException` by default) and applies nothing to the graph.\n\n```ts\nlet controller: AbortController | undefined;\n\nasync function runLayout() {\n    // Only the latest layout is applied.\n    controller?.abort();\n    controller = new AbortController();\n    try {\n        await layout(graph, { signal: controller.signal });\n    } catch (error) {\n        if ((error as Error).name !== 'AbortError') throw error;\n    }\n}\n\n// Give up after 5 seconds.\nawait layout(graph, { signal: AbortSignal.timeout(5000) });\n```\n\nELK can't stop a layout in progress, so a layout a `createWorkerElk()` worker is busy with is stopped by terminating the worker - a new one takes over the layouts still waiting. A layout on the main thread, or in any other `elk` instance, keeps running - only its result is ignored.\n\n## ⚠️ Caveats & Known Limitations\n\n- **Edge coordinates are graph-absolute** - `layout()` sets `elk.json.edgeCoords: 'ROOT'`, so ELK returns every edge's route points and labels relative to the root, whichever container the edge is in, and the default import applies them as they are. Overriding it (e.g. `'CONTAINER'`) is allowed, but the default import then misplaces vertices, end anchors and labels of edges inside containers - convert them yourself in `setLinkAttributes` (from `elkEdge`). The same applies to the raw `elkGraph` in `layout()`'s result.\n- **Node labels are not supported** - ELK's node-label placement assumes labels are layout participants, whereas JointJS labels are attrs inside the shape. Link labels are supported.\n- **Element rotation is not taken into account** - an ELK node is a box with a position and a size, with no rotation, so an element is laid out by the geometry it would have at `angle: 0` and ELK reserves the wrong area for a rotated one. `layout()` warns the first time it sees one.\n- **Ports keep their JointJS-computed position by default** - every element with ports is exported with `elk.portConstraints: 'FIXED_POS'`, so ELK keeps each port where the element's port groups place it and edges route to/from that exact spot. Opt into ELK repositioning/reordering them by overriding it (e.g. `'FIXED_SIDE'`/`'FREE'`) in `exportElement` - setting it in `elkLayoutOptions` has no effect, since the per-node value takes precedence. `layout()` also defaults `elk.layered.considerModelOrder.portModelOrder` to `'true'`, so that a port freed this way keeps the order of the element's `ports.items`; override it in `elkLayoutOptions` to let ELK reorder them to reduce edge crossings.\n- **Asynchronous** - unlike `@joint/layout-directed-graph`, `layout()` returns a `Promise`, since `elkjs` computes layouts asynchronously - even on the main thread.\n- **Main thread by default** - without an `elk` option, ELK runs on the main thread and blocks the page while it runs - see \"Running ELK in a Web Worker\" above. In the ESM build, the main-thread copy of ELK (`elkjs/lib/elk.bundled.js`) is imported dynamically, so bundlers split it into a chunk of its own, only loaded by the first layout without an `elk` option. The UMD build can't load a chunk - it uses the `ELK` global instead, which the page has to load first (see \"UMD build\" above).\n\n## 📄 License\n\n[Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/)\n\nThe code in this package is licensed under the Mozilla Public License 2.0, same as the rest of JointJS. It contains no ELK code: it only calls ELK through its API, and its TypeScript option types link to [ELK's option reference](https://eclipse.dev/elk/reference/options.html) instead of reproducing it.\n\nIt depends on [`elkjs`](https://github.com/kieler/elkjs), which is dual-licensed under the [Eclipse Public License 2.0](https://github.com/kieler/elkjs/blob/master/LICENSE.md) or GPL-3.0-or-later (`EPL-2.0 OR GPL-3.0-or-later`) - you can use it under the EPL-2.0. `elkjs` is installed as a regular dependency and kept external to this package's own builds (ESM and UMD) - it is never copied or inlined into them. An application that bundles this package does ship `elkjs` code though (the main-thread chunk, and the Web Worker file if it uses `@joint/layout-elk/worker`), under that license: keep `elkjs`'s license notice with it (e.g. with your bundler's license extraction), and note where its source is available (it is published on [GitHub](https://github.com/kieler/elkjs) and [npm](https://www.npmjs.com/package/elkjs)).\n\nCopyright © 2013-2026 client IO\n\n<!-- File: packages/joint-layout-msagl/README.md (Tokens: ~2586 | Category: plugin-manifest) -->\n# JointJS MSAGL Layout\n\nA module for automatic layout of *[JointJS](https://www.jointjs.com)* graphs using the [Microsoft Automatic Graph Layout (MSAGL)](https://github.com/microsoft/msagljs).\n\nThis library fully depends on [JointJS](https://github.com/clientio/joint) (*>=4.0*), so please read its `README.md` before using this library.\n\nThis library provides hierarchical (Sugiyama) layout with support for nested subgraphs and multiple edge routing modes.\n\n## 🚀 Quick Start\n\n### Installation\n\n```bash\nnpm install @joint/layout-msagl\n```\n\n### Basic Usage\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { layout, EdgeRoutingMode } from '@joint/layout-msagl';\n\n// Create your JointJS graph\nconst graph = new dia.Graph({}, { cellNamespace: shapes });\nconst paper = new dia.Paper({\n    model: graph,\n    cellViewNamespace: shapes,\n    el: document.getElementById('paper'),\n});\nconst rect1 = new shapes.standard.Rectangle({\n    id: 'a',\n    size: { width: 80, height: 40 },\n    attrs: { label: { text: 'A' } }\n});\nconst rect2 = new shapes.standard.Rectangle({\n    id: 'b',\n    size: { width: 80, height: 40 },\n    attrs: { label: { text: 'B' } }\n});\nconst link = new shapes.standard.Link({ source: { id: 'a' }, target: { id: 'b' } });\n\ngraph.addCells([rect1, rect2, link]);\n\n// Or with custom options\nlayout(graph, {\n    layerSeparation: 60,\n    nodeSeparation: 30,\n    edgeRoutingMode: EdgeRoutingMode.Rectilinear\n});\n```\n\n## 📖 API Reference\n\n### Main Functions\n\n#### `layout(graphOrCells, options?): LayoutResult`\n\nMain layout function with full customization options.\n\n**Parameters:**\n- `graphOrCells`: `dia.Graph | dia.Cell[]` - JointJS graph or array of cells to layout\n- `options?`: `Options` - Layout configuration (see below)\n\n**Returns:** `LayoutResult`\n\n```ts\ninterface LayoutResult {\n    bbox: g.Rect;           // Tight bounding box of the laid out graph\n    msGraph: Graph;         // Underlying MSAGL graph data structure\n    msGeomGraph: GeomGraph; // Geometry instance used by MSAGL to layout graph\n}\n```\n\n### Options Interface\n\n```ts\n// Callback type definitions\ntype GetSizeCallback = (element: dia.Element) => dia.Size;\ntype GetLabelSizeCallback = (cell: dia.Cell) => dia.Size | undefined;\ntype SetPositionCallback = (element: dia.Element, position: dia.Point) => void;\ntype SetVerticesCallback = (link: dia.Link, vertices: dia.Point[]) => void;\ntype SetLabelsCallback = (link: dia.Link, labelBBox: dia.BBox, points: dia.Point[]) => void;\ntype SetAnchorCallback = (link: dia.Link, linkEndPoint: dia.Point, bbox: dia.BBox, endType: 'source' | 'target') => void;\ntype SetClusterSizeCallback = (element: dia.Element, size: dia.Size) => void;\n\ninterface Options {\n    // Layout direction\n    layerDirection?: LayerDirectionEnum; // Default: LayerDirectionEnum.TB\n    // Spacing\n    layerSeparation?: number; // Default: 40\n    nodeSeparation?: number; // Default: 20\n    // Edge routing\n    edgeRoutingMode?: EdgeRoutingMode; // Default: EdgeRoutingMode.Rectilinear\n    polylinePadding?: number; // Default: 1\n    // Rectilinear self-loop offset\n    // Vertical pixel offset applied to the two inner vertices of a self-edge\n    // when edgeRoutingMode is Rectilinear. Default: 10\n    rectilinearSelfEdgeOffset?: number;\n    // Grid and origin\n    gridSize?: number; // Default: 0\n    x?: number; // Default: 0\n    y?: number; // Default: 0\n    clusterPadding?: dia.Sides; // Default: 10\n    // Element sizing callbacks\n    getSize?: GetSizeCallback; // Default: element.size()\n    getLabelSize?: GetLabelSizeCallback; // Default: element.get('labelSize')\n    // Callbacks for customizing how the layout is applied\n    setPosition?: SetPositionCallback; // Default: element.position(x, y)\n    setVertices?: boolean | SetVerticesCallback; // Default: true\n    setLabels?: boolean | SetLabelsCallback; // Default: true\n    setAnchor?: boolean | SetAnchorCallback; // Default: true\n    setClusterSize?: SetClusterSizeCallback; // Default: element.size(size)\n}\n```\n\n### Enums\n\n#### `LayerDirectionEnum`\n- `TB` - Top to Bottom (default)\n- `BT` - Bottom to Top\n- `LR` - Left to Right\n- `RL` - Right to Left\n\n#### `EdgeRoutingMode`\n- `Rectilinear` - Orthogonal edges with right angles (default)\n- `SplineBundling` - Smooth curved edges\n\n## 🎯 Examples\n\n### Basic Hierarchical Layout\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { layout, LayerDirectionEnum, EdgeRoutingMode } from '@joint/layout-msagl';\n\nconst graph = new dia.Graph({}, { cellNamespace: shapes });\nconst paper = new dia.Paper({\n    model: graph,\n    cellViewNamespace: shapes,\n    el: document.getElementById('paper'),\n});\n\n// Create elements\nconst elements = ['A', 'B', 'C', 'D'].map(id =>\n    new shapes.standard.Rectangle({\n        id,\n        size: { width: 80, height: 40 },\n        attrs: { label: { text: id } }\n    })\n);\n\n// Create links to form a hierarchy: A -> B, A -> C, B -> D, C -> D\nconst links = [\n    new shapes.standard.Link({ source: { id: 'A' }, target: { id: 'B' } }),\n    new shapes.standard.Link({ source: { id: 'A' }, target: { id: 'C' } }),\n    new shapes.standard.Link({ source: { id: 'B' }, target: { id: 'D' } }),\n    new shapes.standard.Link({ source: { id: 'C' }, target: { id: 'D' } })\n];\n\ngraph.addCells([...elements, ...links]);\n\n// Apply layout\nconst bbox = layout(graph, {\n    layerDirection: LayerDirectionEnum.TB,\n    layerSeparation: 50,\n    nodeSeparation: 30,\n    edgeRoutingMode: EdgeRoutingMode.Rectilinear\n});\n```\n\n### Nested Subgraphs\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { layout } from '@joint/layout-msagl';\n\nconst graph = new dia.Graph({}, { cellNamespace: shapes });\nconst paper = new dia.Paper({\n    model: graph,\n    cellViewNamespace: shapes,\n    el: document.getElementById('paper'),\n});\n\nconst parent = new shapes.standard.Rectangle({\n    id: 'parent',\n    size: { width: 200, height: 150 }\n});\n\nconst child1 = new shapes.standard.Rectangle({\n    id: 'child1',\n    size: { width: 60, height: 30 }\n});\n\nconst child2 = new shapes.standard.Rectangle({\n    id: 'child2',\n    size: { width: 60, height: 30 }\n});\n\n// Embed children in parent\nparent.embed(child1);\nparent.embed(child2);\n\n// Add internal link between children\nconst internalLink = new shapes.standard.Link({\n    source: { id: 'child1' },\n    target: { id: 'child2' }\n});\n\ngraph.addCells([parent, child1, child2, internalLink]);\n\n// Layout will automatically handle the subgraph\nlayout(graph);\n```\n\n### Edge Routing with Labels\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { layout } from '@joint/layout-msagl';\n\nconst graph = new dia.Graph({}, { cellNamespace: shapes });\nconst paper = new dia.Paper({\n    model: graph,\n    cellViewNamespace: shapes,\n    el: document.getElementById('paper'),\n});\n\nconst rect1 = new shapes.standard.Rectangle({\n    id: 'A',\n    size: { width: 80, height: 40 },\n    attrs: { label: { text: 'A' } }\n});\n\nconst rect2 = new shapes.standard.Rectangle({\n    id: 'B',\n    size: { width: 80, height: 40 },\n    attrs: { label: { text: 'B' } }\n});\n\n// Set label size on links for proper label positioning\nconst link = new shapes.standard.Link({\n    source: { id: 'A' },\n    target: { id: 'B' },\n    labels: [\n        {\n            attrs: {\n                text: {\n                    text: 'Label'\n                }\n            }\n        }\n    ],\n    labelSize: { width: 80, height: 20 } // Required for label layout\n});\n\ngraph.addCells([rect1, rect2, link]);\n\nlayout(graph);\n```\n\n### Custom Callbacks\n\n```ts\nimport { dia, util } from '@joint/core';\n\nlayout(graph, {\n    // Custom positioning with animation\n    setPosition: (element: dia.Element, position: dia.Point) => {\n        element.transition('position', position, {\n            duration: 500,\n            timingFunction: util.timing.cubic,\n            valueFunction: util.interpolate.object\n        });\n    }\n});\n```\n\n## 🔧 Advanced Features\n\n### Label Positioning\n\nThe layout automatically positions labels on links and reserves space for element labels in subgraphs. Label sizing is controlled by the `getLabelSize` callback:\n\n**Default behavior** - reads from the `labelSize` property:\n```ts\nlink.set('labelSize', { width: 100, height: 20 });\nparentElement.set('labelSize', { width: 120, height: 25 });\n```\n\n**Custom label sizing** - provide your own callback:\n```ts\nlayout(graph, {\n    getLabelSize: (cell) => {\n        if (cell.isLink()) {\n            // Calculate link label size based on text content\n            const text = cell.label(0)?.attrs?.text?.text || '';\n            return { width: text.length * 8, height: 20 };\n        } else {\n            // Calculate element label size\n            const text = cell.attr('label/text') || '';\n            return { width: text.length * 10, height: 25 };\n        }\n    }\n});\n```\n\nIf `getLabelSize` returns `undefined` for a cell, no space is reserved for its label.\n\n## ⚠️ Caveats & Known Limitations\n\n- **Rectilinear self‑loops** – When `edgeRoutingMode` is set to `Rectilinear`, self‑edges use a configurable vertical offset controlled by `rectilinearSelfEdgeOffset` (default `10`). This is a stop‑gap until upstream `msagljs` provides native rectilinear self‑loop geometry.\n- **Subgraph resizing** – Parent elements that embed other elements are resized by the layout to tightly pack all their children.\n- **Subgraph layout direction** - Layout inside subgraphs is always set to `TB` (Top-to-Bottom) direction, as other directions can cause layout issues.\n- **Link labels in subgraphs** – Link labels within subgraphs may be positioned incorrectly, despite the layout correctly reserving space for them.\n- **Obstacle padding (first‑bend distance)** – MSAGL exposes an `edgeRoutingSettings.Padding` option intended to keep edges a minimum distance away from obstacles (nodes) and thus control where the first bend occurs. In msagljs this setting is currently not working.\n- **ID handling** - MSAGL expects IDs to be string values so `id: 1` and `id: '1'` are considered the same.\n\n## 📄 License\n\n[Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/)\n\nCopyright © 2013-2026 client IO\n\n<!-- File: packages/joint-mock-svg/README.md (Tokens: ~403 | Category: plugin-manifest) -->\n# JointJS Mock SVG\n\nMocks of those SVG globals that JSDOM does not implement but *[JointJS](https://www.jointjs.com/)* relies on, including `SVGAngle`, `createSVGMatrix()`, `getBBox()`, and `transform.baseVal`.\n\nThis package is **test-runner agnostic**: it depends on no test runner and uses no spies, so it can be consumed from any of them.\n\n## Usage\n\nImport it once, before your tests run, in a DOM environment:\n\n```js\nimport '@joint/mock-svg';\n```\n\nThe import is side-effectful: it installs the mocks on `globalThis` and exports no value. Note that a DOM is required - without it the import fails.\n\nWhere that import belongs depends on your runner - it needs to run before any test file touches the SVG globals, which usually means a setup-file option rather than an import in the test itself.\n\n## TypeScript\n\n`getBBox()`, `getScreenCTM()`, `getComputedTextLength()` and `transform` are mocked on `SVGElement`, one level wider than where DOM types (`lib.DOM`) declare them, because JSDOM makes `<rect>`, `<path>` and `<text>` plain `SVGElement`s. Reading one of them off `SVGElement.prototype` - like when spying on the mocks - therefore needs the exported `MockedSVGElement` type. For example:\n\n```ts\nimport type { MockedSVGElement } from '@joint/mock-svg';\n\njest.spyOn(SVGElement.prototype as MockedSVGElement, 'getBBox')\n    .mockReturnValue({ x: 0, y: 0, width: 100, height: 50 } as DOMRect);\n```\n\n## License\n\nThe *@joint/mock-svg* library is licensed under the [MIT License](https://github.com/clientIO/joint/blob/master/packages/joint-mock-svg/LICENSE).\n\nCopyright © 2013-2026 client IO\n\n<!-- File: packages/joint-react/README.md (Tokens: ~1796 | Category: plugin-manifest) -->\n<p align=\"center\">\n  <img src=\"https://cdn.prod.website-files.com/63061d4ee85b5a18644f221c/633045c1d726c7116dcbe582_JJS_logo.svg\" alt=\"JointJS\" height=\"56\" />\n</p>\n\n<h1 align=\"center\">JointJS for React</h1>\n\n<p align=\"center\">\n  <strong>Production-scale diagramming for React.</strong><br/>\n  A first-class React integration for <a href=\"https://jointjs.com\">JointJS</a> — real components and hooks for building complex diagramming apps, with full control over the data model, rendering, and interactions, using the React patterns you already know.\n</p>\n\n<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/@joint/react\"><img src=\"https://img.shields.io/npm/v/@joint/react?style=flat-square&color=0EA5E9\" alt=\"npm\" /></a>\n  <a href=\"https://www.npmjs.com/package/@joint/react\"><img src=\"https://img.shields.io/npm/types/@joint/react?style=flat-square&color=3178C6\" alt=\"types\" /></a>\n  <a href=\"https://www.npmjs.com/package/@joint/react\"><img src=\"https://img.shields.io/npm/dm/@joint/react?style=flat-square&color=F59E0B\" alt=\"downloads\" /></a>\n  <a href=\"./LICENSE\"><img src=\"https://img.shields.io/npm/l/@joint/react?style=flat-square&color=10B981\" alt=\"license\" /></a>\n</p>\n\n<p align=\"center\">\n  🚀 <a href=\"#quick-start\">Quick start</a> ·\n  🧩 <a href=\"https://www.jointjs.com/demos/ai-workflow-builder\">Live Example</a> ·\n  📖 <a href=\"#learn-more\">Learn more</a>\n</p>\n\n[![Use Cases](https://github.com/user-attachments/assets/fc6de738-3fad-48f7-99aa-ba8c3729d9b1)](https://github.com/user-attachments/assets/fc6de738-3fad-48f7-99aa-ba8c3729d9b1)\n\n---\n\nBuilt on the proven JointJS core, you get a mature diagramming UI library and an idiomatic React layer in one. Like JointJS, it's open source.\n\n## Why JointJS for React\n\n- **A real data model.** A serializable graph you can query, transform, and persist — the backbone for complex, stateful diagram applications, not just visual nodes.\n- **Full control.** Complete control over how diagrams render and behave — shapes, ports, links, routing, and custom interactions.\n- **Production-grade UX.** The interaction details end users expect come built in: connection snapping, link routing that avoids obstacles, element and link tools, highlighters for hover and selection states. Good UX is designed into every feature, not left for you to assemble.\n- **Performance at scale.** Smooth interaction on large graphs with thousands of nodes.\n- **Idiomatic React.** Real components and hooks that fit naturally into your existing React app and tooling.\n\n## Installation\n\n```bash\nnpm install @joint/react     # or pnpm add / yarn add / bun add\n```\n\nPeer dependency: **React 18 or 19**.\n\n## Quick start\n\n```tsx\nimport { GraphProvider, Paper, HTMLBox, type CellRecord } from '@joint/react';\n\ninterface NodeData {\n  label: string;\n}\n\nconst initialCells: ReadonlyArray<CellRecord<NodeData>> = [\n  { id: '1', type: 'element', position: { x: 40, y: 40 }, data: { label: 'Hello' } },\n  { id: '2', type: 'element', position: { x: 280, y: 180 }, data: { label: 'World' } },\n  { id: '1 → 2', type: 'link', source: { id: '1' }, target: { id: '2' } },\n];\n\nfunction renderElement(data: NodeData) {\n  return <HTMLBox>{data.label}</HTMLBox>;\n}\n\nexport default function Diagram() {\n  return (\n    <GraphProvider initialCells={initialCells}>\n      <Paper style={{ width: '100%', height: 600 }} renderElement={renderElement} />\n    </GraphProvider>\n  );\n}\n```\n\n`GraphProvider` holds the graph state, `Paper` renders it, and `renderElement` turns each node's data into a React component. See [Learn more](#learn-more) for the full getting-started guide.\n\n## When is JointJS for React the right choice?\n\nJointJS for React is built for production-scale diagramming — applications where the diagram is a core part of the product and has to handle real complexity: a rich data model, large graphs, domain-specific shapes, and deep control over interactions and rendering.\n\nProduction-scale is not only about node count. Most workflow tools top out at tens of nodes, yet their users still expect a polished editing experience: precise connections, clear feedback on hover and selection, links that route cleanly around obstacles. JointJS for React treats that end-user UX as part of the library, so you ship it instead of building it.\n\nIt's designed to be the layer you build on as your requirements grow, from the first prototype through to a mature, scaling application.\n\n## Use cases\n\nJointJS for React is used to build production diagramming applications across many domains:\n\n| Domain | Examples |\n|---|---|\n| Workflow & process | Flowchart editors, process automation, activity and sequence diagrams |\n| AI workflow builders | Node-based AI pipeline and agent builders, marketing automation studios |\n| BPMN | Business process modelers, swimlanes, Visio BPMN import/export |\n| Data modeling | ER diagrams, database schema design, data lineage and mapping |\n| UML & software modeling | Class, sequence, statechart and use-case diagrams |\n| Industrial / SCADA & HMI | SCADA interfaces, HMI control panels, P&ID and process diagrams |\n| Energy networks | Power grid and substation diagrams, telecom and fiber (DWDM) network design |\n| Electronic design | Circuit and logic design, wiring and cable harness diagrams, schematic capture |\n| Org charts | Org charts, reporting hierarchies, team and headcount planning |\n| Timelines | Project and milestone timelines, Gantt and PERT charts |\n\nExplore the [demos](https://www.jointjs.com/demos) to see them in action.\n\n## Learn more\n\n- 📖 [Learn JointJS for React](https://docs.jointjs.com/react)\n- 🧾 [API reference](https://docs.jointjs.com/api-react)\n- 🖼️ [Examples](https://www.jointjs.com/demos)\n- 📚 [Storybook](https://react.jointjs.com/learn/)\n- 🤖 [MCP server](#using-jointjs-for-react-with-ai-coding-agents)\n- 💬 [Ask a question or share feedback](https://github.com/clientIO/joint/discussions)\n\n## Using JointJS for React with AI coding agents\n\nJointJS is built to work well with AI-assisted development.\n\n**MCP server** — connect your AI coding agent (Claude Code, Cursor, and other MCP-compatible tools) to the JointJS Model Context Protocol server so it can search the official docs and demos while you build:\n\n```\nhttps://mcp.jointjs.com/mcp\n```\n\nIt exposes `search_docs` and `search_demos`, so your agent writes correct JointJS for React code against the real API instead of guessing.\n\n## Commercial version\n\nThe open-source library covers core diagramming. **JointJS+ for React** is the commercial extension that takes the end-user experience further with production-ready plugins — stencils, toolbars, snaplines, keyboard shortcuts, undo/redo, advanced layouts, import/export, and more — plus professional demo apps (including source code) and direct support from the team that builds the library. [Start a free 30-day trial.](https://www.jointjs.com/free-trial)\n\n## License\n\nJointJS for React is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).\n\nCopyright © 2013-2026 client IO\n\n<!-- File: packages/joint-router-avoid/README.md (Tokens: ~2249 | Category: plugin-manifest) -->\n# JointJS Avoid Router\n\nA module that routes *[JointJS](https://www.jointjs.com)* links using [libavoid](https://github.com/mjwybrow/libavoid), a C++ library for automatic, obstacle-avoiding orthogonal connector routing, compiled to WebAssembly via [libavoid-js](https://github.com/Aksem/libavoid-js).\n\nThis library fully depends on [JointJS](https://github.com/clientio/joint) (*>=4.0*), so please read its `README.md` before using this library.\n\nlibavoid maintains a single incremental router shared by the whole graph: it tracks every element as an obstacle and every link as a connector, and reroutes affected connectors whenever obstacles move. This package wraps that behavior in a `RouterService` that listens to a `dia.Graph` and, whenever libavoid computes a new route for a link, applies it directly to that link's `vertices` and source/target anchors - there is no `router: { name: ... }` attribute to set on links; once a link is connected to two elements on a routed graph, it is routed automatically. See [`examples/avoid-router-ts`](https://github.com/clientIO/joint/tree/master/examples/avoid-router-ts) for a full demo.\n\n## 🚀 Quick Start\n\n### Installation\n\n```bash\nnpm install @joint/router-avoid\n```\n\n### Basic Usage\n\n```ts\nimport { dia, shapes } from '@joint/core';\nimport { initAvoidRouter } from '@joint/router-avoid';\n\nconst graph = new dia.Graph({}, { cellNamespace: shapes });\nconst paper = new dia.Paper({\n    model: graph,\n    cellViewNamespace: shapes,\n    el: document.getElementById('paper'),\n});\n\n// Loads the libavoid-js WebAssembly module (if not already loaded), then\n// registers every element/link currently in the graph as an avoid\n// obstacle/connector and keeps them in sync with future graph changes.\nconst routerService = await initAvoidRouter(graph, {\n    shapeBufferDistance: 20,\n    idealNudgingDistance: 10,\n});\n\n// ... add elements and links to the graph - links connected to two\n// elements are picked up and routed automatically, no `router`\n// attribute needed:\n// new shapes.standard.Link({ source: { id: a.id }, target: { id: b.id } });\n\n// Stop routing this graph and release the underlying resources\n// (e.g. terminate a Worker thread) when they're no longer needed.\n// routerService.destroy();\n```\n\n`initAvoidRouter()` resolves once the module is loaded and the current graph content has been registered with libavoid; the routes themselves are still computed asynchronously and applied as they come in - listen for the `link:routed` event to know when a specific link's route has settled (see below).\n\nLoaded as a script tag rather than via ESM, the same functions are available as `joint.routers.avoid.initAvoidRouter` / `joint.routers.avoid.loadAvoidRouter` on the UMD build.\n\n## 📖 API Reference\n\n### `loadAvoidRouter(filePath?): Promise<void>`\n\nLoads the `libavoid-js` WebAssembly module. `initAvoidRouter()` calls this automatically if the module isn't loaded yet, so calling it directly is only needed to preload the module ahead of time. Accepts an optional path to the `libavoid.wasm` file if it is not served from its default location. Safe to call multiple times - later calls resolve once the first load completes.\n\n### `initAvoidRouter(graph, options?): Promise<RouterService>`\n\nLoads the module (via `loadAvoidRouter()`, if needed) and creates a `RouterService` for `graph`. The returned service is not started - call `start()` to keep the graph continuously routed, or `routeAll()`/`routeSubgraph()` for a one-shot routing pass.\n\n- `graph`: `dia.Graph` - the graph to route.\n- `options?`: `InitAvoidOptions`\n\n```ts\ninterface InitAvoidOptions {\n    shapeBufferDistance?: number;  // Default: 10 - spacing added around shapes when computing obstacles, and used as the fallback route's margin around elements\n    idealNudgingDistance?: number; // Default: 5 - spacing used to nudge apart overlapping connector segments\n    worker?: boolean | WorkerOptions; // Default: false - run libavoid inside a Web Worker instead of the main thread; pass an object to configure the Worker\n    libavoidFilePath?: string;     // Forwarded to loadAvoidRouter()\n    trackLink?: (params: { link: dia.Link }) => boolean;          // Which links to route; return false to exclude one. Default: all\n    trackElement?: (params: { element: dia.Element }) => boolean; // Which elements to track as avoid obstacles; return false to exclude one. Default: all\n    // First refusal on a link avoid can't route because one of its ends\n    // isn't connected to a cell ('unconnected'), is connected to another\n    // link ('unsupported'), or is connected to an element excluded via\n    // `trackElement` ('untracked'). Return true to take over routing that\n    // link yourself; otherwise the built-in `rightAngle`-based fallback\n    // route is applied.\n    interceptUnroutableLink?: (params: { link: dia.Link, reason: 'unconnected' | 'untracked' | 'unsupported' }) => boolean;\n    // Override how computed route attributes are applied to a link, e.g. to\n    // route the update through a command manager. Default: link.set()\n    setRouteAttributes?: (params: { link, attributes, origin, routing, unroutableReason }) => void;\n    changeFlag?: string;           // Default: 'avoidRouter' - name of the opt flag marking this router's own link.set() calls\n}\n\ninterface WorkerOptions {\n    debounceTime?: number; // Default: 100 - ms the Worker waits after the last received change before recomputing routes in a single batch; 0 applies every change immediately\n}\n```\n\n### `RouterService` (returned by `initAvoidRouter()`)\n\nThe object responsible for keeping libavoid's internal obstacle/connector graph in sync with the `dia.Graph`, applying computed routes to links, and emitting routing events. It mixes in JointJS's `mvc.Events` (`on`/`off`/`trigger`).\n\n- `start()` / `stop()` - start/stop listening to graph changes (added/removed cells, moved/resized elements, reconnected links). `start()` also syncs every cell the graph already holds.\n- `isStarted` - whether the service is currently listening to its graph.\n- `routeAll()` - one-shot: routes every cell currently in the graph, resolving once all routes are applied. No graph listener is attached. Throws while started. Resolves with a `RoutingResult` - `{ status: 'done' }`, or `{ status: 'cancelled' }` if the pass was interrupted by `destroy()`. Overlapping calls are queued and run one after another.\n- `routeSubgraph(cells)` - one-shot: routes only `cells`, independently of the rest of the graph (cells outside the array are neither routed nor obstacles). Throws while started. Resolves with the same `RoutingResult` as `routeAll()`.\n- `changeFlag` - the `opt` flag name marking this router's own `link.set()` calls, for filtering them out of your own `change` listeners.\n- `destroy()` - stops routing the graph, cancels open routing cycles, and releases the resources held by the service and its provider (e.g. terminates a Worker thread). Do not use the instance afterwards.\n\n**Events:**\n\n- `link:routing` `(link)` - emitted when a link's route is (re-)computing.\n- `link:routed` `(link, { origin, reason })` - emitted once a link's route has been applied. `origin` is `'avoid'` or `'fallback'`; `reason` is set when the fallback route was applied because the link was unroutable.\n- `link:routing:cancelled` `(link)` - emitted when a link with an open `link:routing` cycle becomes unroutable (e.g. disconnected) before libavoid produced a route for it.\n- `idle` `()` - emitted when there are no more open routing cycles.\n\n## ⚠️ Caveats & Known Limitations\n\n- **Asynchronous setup** - `initAvoidRouter()` loads the WebAssembly module and performs the initial graph sync asynchronously; it must be awaited before the graph is considered routed.\n- **Bundler configuration** - `libavoid-js` ships its logic and the `libavoid.wasm` binary as separate files, and `worker: true` additionally loads this package's own worker script as a module. Consuming applications are responsible for ensuring both are served alongside the rest of the bundle (e.g. via a copy plugin for your bundler of choice).\n- **Fallback routing** - libavoid does not expose a way to check whether a computed route is valid, so a heuristic is used. When the route is deemed invalid, or when a link isn't connected to elements on both ends, the built-in `rightAngle`-based fallback route is applied instead (unless `interceptUnroutableLink` takes over).\n- **One `RouterService` per graph** - constructing more than one `RouterService` for the same `dia.Graph` results in both instances applying routes to the same links; call `destroy()` on a previous instance before creating a new one for the same graph.\n- **Custom vertices (checkpoints)** are not currently supported.\n\n## 📄 License\n\nThe code in this package is licensed under the [Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/), same as the rest of JointJS.\n\nIt depends on [`libavoid-js`](https://www.npmjs.com/package/libavoid-js), a WebAssembly build of [libavoid](https://github.com/mjwybrow/adaptagrams), which is licensed under the **GNU LGPL 2.1 or later** - applications shipping this package also ship the `libavoid.wasm` binary under that license. Keep the binary a separately served file (as described under bundler configuration above); don't configure your bundler to inline it into your application bundle.\n\nCopyright © 2013-2026 client IO\n\nThis package depends on [`libavoid-js`](https://github.com/Aksem/libavoid-js), which is licensed under the [LGPL-2.1-or-later license](https://github.com/Aksem/libavoid-js?tab=LGPL-2.1-1-ov-file#readme).\n\n<!-- File: packages/joint-shapes-general-tools/README.md (Tokens: ~110 | Category: plugin-manifest) -->\n# JointJS General Shapes Tools\n\nA collection of ECMAScript / TypeScript tools for use with general *[JointJS](https://www.jointjs.com/)* shapes.\n\nThis library fully depends on [JointJS](https://github.com/clientio/joint) (*>=3.5*), so please read its `README.md` before using this library.\n\n## License\n\n[Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/)\n\nCopyright © 2013-2026 client IO\n\n<!-- File: packages/joint-shapes-general/README.md (Tokens: ~106 | Category: plugin-manifest) -->\n# JointJS General Shapes\n\nA collection of general ECMAScript / TypeScript shapes for *[JointJS](https://www.jointjs.com/)*.\n\nThis library fully depends on [JointJS](https://github.com/clientio/joint) (*>=3.5*), so please read its `README.md` before using this library.\n\n## License\n\n[Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/)\n\nCopyright © 2013-2026 client IO\n\n<!-- File: packages/joint-vitest-plugin-mock-svg/README.md (Tokens: ~741 | Category: plugin-manifest) -->\n# JointJS Mock SVG plugin for Vitest\n\nA Vitest plugin to mock SVG methods not implemented in JSDOM used by *[JointJS](https://www.jointjs.com/)*.\n\n## Usage\n\nImport the plugin in your `vitest.config.js`:\n\n```js\nimport { defineConfig } from 'vitest/config';\nimport mockSVG from '@joint/vitest-plugin-mock-svg'\n\nexport default defineConfig({\n  plugins: [mockSVG()],\n})\n```\n\nThe mocks themselves come from [`@joint/mock-svg`](https://github.com/clientIO/joint/tree/master/packages/joint-mock-svg) as plain functions. If your test needs to assert on one of them, or change what the mock returns, use `vi.spyOn()`.\n\n### Changing what a mock returns\n\nThe default `getBBox()` returns an all-zero rect, which leaves `checkVisibility()` false and `util.breakText()` with nothing to measure. If your test needs one, give the mock a size object as a return value:\n\n```js\nvi.spyOn(SVGElement.prototype, 'getBBox')\n  .mockReturnValue({ x: 0, y: 0, width: 100, height: 20 });\n```\n\n`mockRestore()`, `vi.restoreAllMocks()` and `restoreMocks: true` all put the mock back afterwards, so an override stays local to its test and the suite needs no `beforeEach` to reinstall anything.\n\nThe same spy also records calls. This lets you assert whether JointJS measured anything at all:\n\n```js\nconst getBBox = vi.spyOn(SVGElement.prototype, 'getBBox');\nrender(<MyDiagram />);\nexpect(getBBox).toHaveBeenCalled();\n```\n\n`vi.spyOn` reaches nested members, as well - for example `vi.spyOn(SVGElement.prototype.transform.baseVal, 'appendItem')`.\n\nTo swap a mock implementation permanently, reassign it:\n\n```js\nSVGElement.prototype.getComputedTextLength = () => 42;\n```\n\nRestore the previous value yourself if the change should not outlive the file.\n\n### Driving `ResizeObserver`\n\nThe mocked observer accepts a callback and never calls it. To deliver entries, or to assert on `observe`, provide your own:\n\n```js\nglobalThis.ResizeObserver = class {\n  observe = vi.fn();\n  unobserve = vi.fn();\n  disconnect = vi.fn();\n  constructor(callback) { this.callback = callback; }\n  resize(entries) { this.callback(entries, this); }\n};\n```\n\n## TypeScript\n\n`getBBox()`, `getScreenCTM()`, `getComputedTextLength()` and `transform` are mocked on `SVGElement`, one level wider than where DOM types (`lib.DOM`) declare them, because JSDOM makes `<rect>`, `<path>` and `<text>` plain `SVGElement`s. Reading one of them off `SVGElement.prototype` - as every example above does - therefore needs the re-exported `MockedSVGElement` type. For example:\n\n```ts\nimport type { MockedSVGElement } from '@joint/vitest-plugin-mock-svg';\n\nvi.spyOn(SVGElement.prototype as MockedSVGElement, 'getBBox')\n  .mockReturnValue({ x: 0, y: 0, width: 100, height: 20 } as DOMRect);\n```\n\n## License\n\nThe *@joint/vitest-plugin-mock-svg* library is licensed under the [MIT License](https://github.com/clientIO/joint/blob/master/packages/joint-vitest-plugin-mock-svg/LICENSE).\n\nCopyright © 2013-2026 client IO\n</yakaai_skills>"}