{"_id":"@alesik/uml-diagram","_rev":"4-457fa582f9549a290aa712fe2ef05249","name":"@alesik/uml-diagram","dist-tags":{"latest":"0.2.3"},"versions":{"0.1.0":{"name":"@alesik/uml-diagram","version":"0.1.0","keywords":["uml","diagram","class-diagram","visualization","d3","svg","interactive","graph"],"author":{"name":"Aliaksandr Lesik"},"license":"MIT","_id":"@alesik/uml-diagram@0.1.0","maintainers":[{"name":"alesik","email":"skmne94@gmail.com"}],"homepage":"https://github.com/skmne/uml-diagram#readme","bugs":{"url":"https://github.com/skmne/uml-diagram/issues"},"dist":{"shasum":"d9425a9888d1c14bab7f62bcb265bf73f811226a","tarball":"https://registry.npmjs.org/@alesik/uml-diagram/-/uml-diagram-0.1.0.tgz","fileCount":4,"integrity":"sha512-i3dcB2PdaOWWwOTu6LFGKL99pKgHyglncS1BuoiPLsqtbX9nwa65lJ83IK1Q/cGiJfOupWTSRyxPSLH0J0fkxQ==","signatures":[{"sig":"MEUCIFVoGrS3+JIMPNlOLFHJ6eFNwQWx9MFkgRPdyZCEYAO3AiEA3wy5ltwQAnhstZF1T4n4WVcRywfRq7L5CxirEUEWg8g=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":385414},"main":"dist/main.js","module":"dist/main.js","gitHead":"e636a5b7d29383650fbbc95712c42595f69f2460","scripts":{"test":"jest","build":"webpack --mode production","watch":"webpack --mode development --watch","build:dev":"webpack --mode development","prepublishOnly":"npm run build"},"_npmUser":{"name":"alesik","email":"skmne94@gmail.com"},"repository":{"url":"git+https://github.com/skmne/uml-diagram.git","type":"git"},"_npmVersion":"10.9.3","description":"A lightweight JavaScript library for rendering interactive UML class diagrams using D3.js","directories":{},"_nodeVersion":"22.18.0","dependencies":{"d3":"^7.0.0","lodash":"^4.17.21"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^30.2.0","webpack":"^5.87.0","babel-jest":"^30.2.0","@babel/core":"^7.29.0","webpack-cli":"^5.1.4","@babel/preset-env":"^7.29.0","jest-environment-jsdom":"^30.2.0"},"peerDependencies":{"d3":"^7.0.0"},"_npmOperationalInternal":{"tmp":"tmp/uml-diagram_0.1.0_1771165616396_0.3251806985685799","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@alesik/uml-diagram","version":"0.2.1","keywords":["uml","diagram","class-diagram","visualization","d3","svg","interactive","graph"],"author":{"name":"Aliaksandr Lesik"},"license":"MIT","_id":"@alesik/uml-diagram@0.2.1","maintainers":[{"name":"alesik","email":"skmne94@gmail.com"}],"homepage":"https://github.com/skmne/uml-diagram#readme","bugs":{"url":"https://github.com/skmne/uml-diagram/issues"},"dist":{"shasum":"56df8aaf566aad4259c4cf8c03e440eb68009175","tarball":"https://registry.npmjs.org/@alesik/uml-diagram/-/uml-diagram-0.2.1.tgz","fileCount":4,"integrity":"sha512-SsYFI69LHt+vkHdheZUShclonsuSKlOPCJaCR2YL0H0RD71qlGIcJ9bEsk3ZaNTcrd6SGYZoYk67uYg52ATDBA==","signatures":[{"sig":"MEUCIQCVO2VR4rRkK9QSb+YZAtrnGvO2q9wlyxVKyLQkWu07YwIgZQeAHALuZIQjlxadFg1dqu9mLHA6LRtcBiIQ5Yc4ZHc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":72510},"main":"dist/main.js","module":"dist/main.js","gitHead":"2d36fe4163e8e944cd608bf33a9da0440cb9fd2a","scripts":{"test":"jest","build":"webpack --mode production","watch":"webpack --mode development --watch","build:dev":"webpack --mode development","prepublishOnly":"npm run build"},"_npmUser":{"name":"alesik","email":"skmne94@gmail.com"},"repository":{"url":"git+https://github.com/skmne/uml-diagram.git","type":"git"},"_npmVersion":"10.9.3","description":"A lightweight JavaScript library for rendering interactive UML class diagrams using D3.js","directories":{},"_nodeVersion":"22.18.0","dependencies":{"d3":"^7.0.0","lodash":"^4.17.21"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^30.2.0","webpack":"^5.87.0","babel-jest":"^30.2.0","@babel/core":"^7.29.0","webpack-cli":"^5.1.4","@babel/preset-env":"^7.29.0","jest-environment-jsdom":"^30.2.0"},"peerDependencies":{"d3":"^7.0.0"},"_npmOperationalInternal":{"tmp":"tmp/uml-diagram_0.2.1_1781442965956_0.4641248414039214","host":"s3://npm-registry-packages-npm-production"}},"0.2.2":{"name":"@alesik/uml-diagram","version":"0.2.2","keywords":["uml","diagram","class-diagram","visualization","d3","svg","interactive","graph"],"author":{"name":"Aliaksandr Lesik"},"license":"MIT","_id":"@alesik/uml-diagram@0.2.2","maintainers":[{"name":"alesik","email":"skmne94@gmail.com"}],"homepage":"https://github.com/skmne/uml-diagram#readme","bugs":{"url":"https://github.com/skmne/uml-diagram/issues"},"dist":{"shasum":"a4552ee31818ae1e446e46db378aaa24e87b29bd","tarball":"https://registry.npmjs.org/@alesik/uml-diagram/-/uml-diagram-0.2.2.tgz","fileCount":4,"integrity":"sha512-nysR/8pRHZfR3rwJBsT4X8TzSRPtjskguriPzcTLaGzB3EzwoO8CcliplQ1Il8a86xIGDTCsqc7jICPjSlYR/Q==","signatures":[{"sig":"MEUCIEHoSqzsO48pP5t8wJevjOpIk/zAsDOqbVvrII1Q3sNuAiEA7HA7EWPewL1kmycaTRakNIBBIg+05XLKwpjH2GTqhco=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":74376},"main":"dist/main.js","module":"dist/main.js","gitHead":"6246f3beb4b7a85f2407498af315fe428c09a27f","scripts":{"test":"jest","build":"webpack --mode production","watch":"webpack --mode development --watch","build:dev":"webpack --mode development","prepublishOnly":"npm run build"},"_npmUser":{"name":"alesik","email":"skmne94@gmail.com"},"repository":{"url":"git+https://github.com/skmne/uml-diagram.git","type":"git"},"_npmVersion":"10.9.3","description":"A lightweight JavaScript library for rendering interactive UML class diagrams using D3.js","directories":{},"_nodeVersion":"22.18.0","dependencies":{"d3":"^7.0.0","lodash":"^4.17.21"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^30.2.0","webpack":"^5.87.0","babel-jest":"^30.2.0","@babel/core":"^7.29.0","webpack-cli":"^5.1.4","@babel/preset-env":"^7.29.0","jest-environment-jsdom":"^30.2.0"},"peerDependencies":{"d3":"^7.0.0"},"_npmOperationalInternal":{"tmp":"tmp/uml-diagram_0.2.2_1781459006017_0.8745451026585331","host":"s3://npm-registry-packages-npm-production"}},"0.2.3":{"name":"@alesik/uml-diagram","version":"0.2.3","description":"A lightweight JavaScript library for rendering interactive UML class diagrams using D3.js","main":"dist/main.js","module":"dist/main.js","scripts":{"test:browser":"npm run build && playwright test","test":"jest","build":"webpack --mode production","build:dev":"webpack --mode development","watch":"webpack --mode development --watch","prepublishOnly":"npm run build"},"keywords":["uml","diagram","class-diagram","visualization","d3","svg","interactive","graph"],"author":{"name":"Aliaksandr Lesik"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/skmne/uml-diagram.git"},"bugs":{"url":"https://github.com/skmne/uml-diagram/issues"},"homepage":"https://github.com/skmne/uml-diagram#readme","devDependencies":{"@babel/core":"^7.29.0","@babel/preset-env":"^7.29.0","@playwright/test":"^1.63.0","babel-jest":"^30.2.0","jest":"^30.2.0","jest-environment-jsdom":"^30.2.0","webpack":"^5.87.0","webpack-cli":"^5.1.4"},"dependencies":{"d3":"^7.0.0","lodash":"^4.17.21"},"peerDependencies":{"d3":"^7.0.0"},"_id":"@alesik/uml-diagram@0.2.3","gitHead":"b2e25413a8a33a7835cab91a748db76d75c0248c","_nodeVersion":"22.18.0","_npmVersion":"10.9.3","dist":{"integrity":"sha512-q2yynGG8fTNF2Zdhdwdl8PW+puTm74Vaq7+0k892Ph/FpBu9YcPgqZOduIQnV1OFaQ9LB79I2w8YN+kEpF+Z8g==","shasum":"02d814414a81e5fca03a6220f52016120387a9c9","tarball":"https://registry.npmjs.org/@alesik/uml-diagram/-/uml-diagram-0.2.3.tgz","fileCount":4,"unpackedSize":91726,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCICBcuaGmbayqBIveX0csxMFMMcaC53ou9dxScG0p0/n2AiBU8rYRfbqRyhTzMn6nKT7tJDKim1jpdusGMkydEbwkjQ=="}]},"_npmUser":{"name":"alesik","email":"skmne94@gmail.com"},"directories":{},"maintainers":[{"name":"alesik","email":"skmne94@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/uml-diagram_0.2.3_1788805051549_0.3421147994246807"},"_hasShrinkwrap":false}},"time":{"created":"2026-02-15T14:26:56.289Z","modified":"2026-09-07T18:17:31.863Z","0.1.0":"2026-02-15T14:26:56.556Z","0.2.1":"2026-06-14T13:16:06.094Z","0.2.2":"2026-06-14T17:43:26.159Z","0.2.3":"2026-09-07T18:17:31.691Z"},"bugs":{"url":"https://github.com/skmne/uml-diagram/issues"},"author":{"name":"Aliaksandr Lesik"},"license":"MIT","homepage":"https://github.com/skmne/uml-diagram#readme","keywords":["uml","diagram","class-diagram","visualization","d3","svg","interactive","graph"],"repository":{"type":"git","url":"git+https://github.com/skmne/uml-diagram.git"},"description":"A lightweight JavaScript library for rendering interactive UML class diagrams using D3.js","maintainers":[{"name":"alesik","email":"skmne94@gmail.com"}],"readme":"# UML Diagram Library\r\n\r\nA lightweight, interactive JavaScript library for rendering UML class diagrams using D3.js and SVG.\r\n\r\n## Features\r\n\r\n- 🎨 **Interactive Diagrams** - Zoom, pan, and drag nodes\r\n- 🎯 **Flexible Styling** - Customize colors, fonts, and node sizes\r\n- 📦 **Lightweight** - Minimal dependencies\r\n- 🔧 **Easy Integration** - Works with vanilla JS, modules, or bundlers\r\n- 🎮 **Keyboard Controls** - WASD navigation, spacebar to center\r\n\r\n## Installation\r\n\r\n```bash\r\nnpm install @alesik/uml-diagram\r\n```\r\n\r\nOr via CDN (when published):\r\n```html\r\n<script src=\"https://unpkg.com/@alesik/uml-diagram/dist/main.js\"></script>\r\n```\r\n\r\n## Quick Start\r\n\r\n### HTML + Script Tag\r\n\r\n```html\r\n<svg id=\"uml-diagram\" width=\"800\" height=\"600\"></svg>\r\n<script src=\"./node_modules/@alesik/uml-diagram/dist/main.js\"></script>\r\n<script>\r\n  const svgElement = document.querySelector(\"#uml-diagram\");\r\n  const diagram = new UMLDiagram.default(svgElement);\r\n  \r\n  diagram.setStyle({\r\n    nodeForeground: \"#333\",\r\n    nodeBackground: \"#fff\",\r\n    fontFamily: \"Arial, sans-serif\",\r\n    fontSize: \"12px\",\r\n    nodeWidth: 200,\r\n  });\r\n  \r\n  diagram.build();\r\n</script>\r\n```\r\n\r\n### ES Modules\r\n\r\n```javascript\r\nimport Diagram from '@alesik/uml-diagram';\r\n\r\nconst svgElement = document.querySelector(\"#uml-diagram\");\r\nconst diagram = new Diagram(svgElement);\r\n\r\ndiagram.setStyle({\r\n  nodeForeground: \"#333\",\r\n  nodeBackground: \"#fff\",\r\n  fontFamily: \"Arial, sans-serif\",\r\n  fontSize: \"12px\",\r\n  nodeWidth: 200,\r\n});\r\n\r\ndiagram.build();\r\n```\r\n\r\n## API Reference\r\n\r\n### Constructor\r\n\r\n```javascript\r\nconst diagram = new Diagram(svgElement);\r\n```\r\n\r\n- `svgElement` - An SVG DOM element where the diagram will be rendered\r\n\r\nAn optional second argument configures interaction for this instance:\r\n\r\n```javascript\r\nconst diagram = new Diagram(svgElement, {\r\n  highlightIncidentLinksOnClick: true, // Default: false\r\n  snapToNodes: true,                  // Default: true\r\n  snapThreshold: 6,                   // Screen pixels; default: 6\r\n});\r\n```\r\n\r\nWhen enabled, clicking a class selects its incoming/outgoing links too. With the default `false`, a class click selects only that class. This flag does not affect direct link clicks, `setHighlight({ includeIncidentLinks: true })`, or `addItems(data, { highlight: true })`.\r\n\r\n### Alignment while dragging\r\n\r\nNodes snap to other nodes' left/right/top/bottom edges and horizontal/vertical centers while dragging. Each axis independently chooses the nearest alignment within `snapThreshold` screen pixels, including when the SVG is zoomed or scaled. This works for nodes with different widths/heights; it does not move neighboring nodes or rearrange the diagram automatically.\r\n\r\nDashed guides appear while snapped and disappear on release, when moving beyond the threshold, or when zooming/redrawing. Hold **Alt** while dragging to bypass snapping temporarily. The cursor's unsnapped coordinates are retained, so moving away releases the snap instead of accumulating position errors.\r\n\r\nSet `snapToNodes: false` in the constructor to keep free dragging. `snapThreshold` accepts a finite nonnegative number (default `6`). Customize guide colors with the style API, including CSS variables:\r\n\r\n```javascript\r\ndiagram.setStyle({ alignmentGuideColor: 'var(--diagram-guide, #e11d8d)' });\r\n```\r\n\r\nThe final snapped coordinates are normal node positions in `getData()`, `nodeMoved`, and `layoutChanged`; movement events still fire once at the end of the drag. Guides are temporary, do not intercept clicks, do not affect the export bounds, and never appear in exported SVG or data. Highlight selections are preserved throughout dragging. See `examples/basic-usage.html` to try this alongside highlighting.\r\n\r\n### Methods\r\n\r\n#### `setStyle(style)`\r\n\r\nConfigure the visual appearance of the diagram.\r\nYou can also call this after `build()` to update the rendered SVG before exporting it.\r\n\r\n```javascript\r\ndiagram.setStyle({\r\n  nodeForeground: \"#0f0f0f\",      // Node border color\r\n  nodeBackground: \"#bfcace\",      // Node background color\r\n  fontFamily: \"Arial, sans-serif\", // Font family\r\n  fontSize: \"12px\",               // Font size\r\n  fontColor: \"#0f0f0f\",          // Text color\r\n  nodeWidth: 200,                // Default node width\r\n  nodeHeight: 50,                // Default node height\r\n});\r\n```\r\n\r\n#### `build()`\r\n\r\nBuild and render the diagram. Call this after setting up the diagram.\r\n\r\n```javascript\r\ndiagram.build();\r\n```\r\n\r\n#### `setData(data)`\r\n\r\nSet the diagram data (nodes and links).\r\n\r\n```javascript\r\ndiagram.setData({\r\n  nodes: [\r\n    {\r\n      id: \"Class1\",\r\n      name: \"MyClass\",\r\n      namespace: null,\r\n      width: 200,\r\n      height: 100,\r\n    },\r\n  ],\r\n  links: [\r\n    {\r\n      source: \"Class1\",\r\n      target: \"Class2\",\r\n      type: \"Inheritance\",\r\n    },\r\n  ],\r\n});\r\n```\r\n\r\n#### `getData()`\r\n\r\nGet a snapshot of the current diagram data.\r\n\r\nThe returned object contains copies of nodes and links, so changing it will not mutate the internal diagram state. Node `x` and `y` values reflect the current coordinates, including positions changed by drag-and-drop.\r\n\r\n```javascript\r\nconst data = diagram.getData();\r\nconsole.log(data.nodes.map(node => ({ id: node.id, x: node.x, y: node.y })));\r\n```\r\n\r\nReturns:\r\n\r\n```typescript\r\n{\r\n  nodes: [\r\n    {\r\n      id: string;\r\n      name: string;\r\n      x: number;\r\n      y: number;\r\n      width: number;\r\n      height: number;\r\n    },\r\n  ];\r\n  links: [\r\n    {\r\n      source: string;\r\n      target: string;\r\n      type: string;\r\n    },\r\n  ];\r\n}\r\n```\r\n\r\n#### `setHighlight(selection)`, `clearHighlight()`, `getHighlight()`\r\n\r\nHighlight a temporary selection without changing data, layout, or persistent styling:\r\n\r\n```javascript\r\ndiagram.setHighlight({\r\n  nodeIds: ['Service', 'pkg.Logger'],\r\n  links: [{ source: 'Service', target: 'pkg.Logger', type: 'Directed Association' }],\r\n  includeIncidentLinks: false,\r\n});\r\nconst selection = diagram.getHighlight(); // { nodeIds, links }; fresh arrays and objects\r\ndiagram.clearHighlight();\r\n```\r\n\r\nEach call replaces the previous selection. Unknown/removed items are ignored and duplicates are deduplicated. Empty input (including `setHighlight()`) clears the selection. Nodes use their stable IDs, never display names. Links have no separate ID in this library: their identity is the exact directed tuple `(source, target, type)`. Different relationship types and reverse links remain distinct; duplicate links with the same tuple are highlighted together.\r\n\r\n`includeIncidentLinks: true` selects all incoming and outgoing links touching the selected nodes, without selecting neighboring nodes. Explicit links work even with an empty `nodeIds` list. Incident links are resolved when `setHighlight` is called; newly inserted links are not automatically selected later.\r\n\r\nA click on a node (including its text) selects that node; if the constructor option `highlightIncidentLinksOnClick` is `true`, it also selects all incoming/outgoing links. A click on a link selects only that link. Shift+click adds or removes items from the current selection. With the flag enabled, selecting a node includes its incident links and deselecting it removes those links except ones touching another selected node. With the flag disabled, Shift+click on a node leaves selected links unchanged. Shift+click on a link always toggles that link independently. A plain click on empty SVG canvas clears highlighting; Shift+click on empty canvas leaves it unchanged. External controls, context menus, node dragging, panning and zooming preserve the selection. There is no timeout. Highlighting survives redraws and `addItems`; `removeItems` prunes removed nodes and their links. It is excluded from `getData()`, `layoutChanged` payloads and SVG export, and highlighting alone never emits `layoutChanged`. Programmatic `setHighlight` continues to use the explicit `includeIncidentLinks` option (default `false`).\r\n\r\nConfigure highlights through the existing style API:\r\n\r\n```javascript\r\ndiagram.setStyle({\r\n  highlightNodeOutline: 'var(--diagram-accent, #2563eb)',\r\n  highlightNodeFill: null, // Optional subtle fill; null preserves the normal fill\r\n  highlightLinkColor: 'var(--diagram-accent, #2563eb)',\r\n  highlightStrokeWidth: 3,\r\n});\r\n```\r\n\r\nDefaults are `#2563eb` for outline/link color, `null` for fill and `3` for stroke width. Highlight color values retain CSS variables, so variables inherited by the SVG can respond to light/dark themes without resetting the selection. A supplied node fill retains the normal fill opacity. Clearing restores the original item styles; calling `setStyle` while selected updates the underlying default theme as usual.\r\n\r\nSubscribe with the existing event API:\r\n\r\n```javascript\r\nconst unsubscribe = diagram.on('highlightChanged', ({ nodeIds, links, reason }) => {\r\n  // Synchronize your sidebar selection here without dispatching its selection action again.\r\n  sidebar.setSelectedIds(nodeIds);\r\n});\r\n```\r\n\r\nEvents are synchronous and fire only when the effective set changes, irrespective of input ordering or duplicates. Reasons are `api` (`setHighlight`/`clearHighlight` or highlighted addition), `click` (node/link click or Shift+click), `background` (empty canvas click), and `removal` (deleted items pruned during redraw). Every listener receives its own snapshot. Reapplying the same effective selection emits nothing, preventing feedback loops; a sidebar should still avoid writing a different selection back from its event handler. Call the returned function to unsubscribe.\r\n\r\nTo highlight only newly expanded dependencies, compute the difference **before** inserting:\r\n\r\n```javascript\r\nconst key = ({ source, target, type }) => JSON.stringify([source, target, type]);\r\nconst before = diagram.getData();\r\nconst knownNodes = new Set(before.nodes.map(node => node.id));\r\nconst knownLinks = new Set(before.links.map(key));\r\nconst added = {\r\n  nodes: expanded.nodes.filter(node => !knownNodes.has(node.id)),\r\n  links: expanded.links.filter(link => !knownLinks.has(key(link))),\r\n};\r\ndiagram.addItems(added);\r\ndiagram.setHighlight({ nodeIds: added.nodes.map(node => node.id), links: added.links });\r\n```\r\n\r\nThe consumer should also deduplicate its incoming batch and ensure link endpoints exist. `addItems` does not automatically highlight unless explicitly requested with `{ highlight: true }`, so initial diagram restoration stays unselected. The existing library stores diagram data/default styles in a singleton; highlighting is instance-local, but this change does not make multiple diagrams' data independent.\r\n\r\n#### `exportSvg(style)`\r\n\r\nExport the diagram SVG as a string. By default, export fits the SVG `viewBox`, `width`, and `height` to the full diagram content, so nodes outside the current viewport and the current zoom/pan transform do not crop the exported SVG.\r\n\r\nPass optional export-only colors to serialize the diagram differently from the on-screen theme.\r\n\r\nExport operates on a detached SVG copy: it never changes the rendered diagram or its highlighting. Temporary highlights are always omitted; there is no `includeHighlight` option.\r\n\r\n```javascript\r\nconst svgString = diagram.exportSvg({\r\n  background: \"#ffffff\",\r\n  nodeForeground: \"#111111\",\r\n  nodeBackground: \"#ffffff\",\r\n  fontColor: \"#111111\",\r\n  fitContent: true,\r\n  padding: 24,\r\n});\r\n```\r\n\r\nThis is useful when the diagram is displayed as light elements on a dark background, but exported as dark elements on a light background.\r\n\r\n`fitContent` defaults to `true`. Use `padding` to control the margin around the exported content. To export the current SVG viewport instead, pass `fitContent: false`.\r\n\r\nPrint-friendly export example:\r\n\r\n```javascript\r\nconst svg = diagram.exportSvg({\r\n  background: \"#ffffff\",\r\n  nodeForeground: \"#111111\",\r\n  nodeBackground: \"#ffffff\",\r\n  fontColor: \"#111111\",\r\n  padding: 24,\r\n});\r\n```\r\n\r\n#### `addItems(data, options?)`\r\n\r\nAdd new nodes and links to the existing diagram.\r\n\r\n```javascript\r\ndiagram.addItems({\r\n  nodes: [{ id: \"NewClass\", name: \"NewClass\" }],\r\n  links: [{ source: \"Class1\", target: \"NewClass\", type: \"Association\" }],\r\n});\r\n```\r\n\r\nThe optional settings object defaults to `{ highlight: false }`. Pass `highlight: true` to replace the selection with the new node IDs and link identities in this insertion:\r\n\r\n```javascript\r\ndiagram.addItems({\r\n  nodes: [{ id: 'Logger', name: 'Logger' }],\r\n  links: [{ source: 'Service', target: 'Logger', type: 'Directed Association' }],\r\n}, { highlight: true });\r\n```\r\n\r\nA settings class is also available. Both forms have the same behavior:\r\n\r\n```javascript\r\nimport Diagram, { AddItemsOptions } from '@alesik/uml-diagram';\r\n\r\nconst options = new AddItemsOptions({ highlight: true });\r\ndiagram.addItems(newData, options);\r\n\r\n// With the browser UMD build, the class is available on the constructor:\r\nconst browserOptions = new UMLDiagram.AddItemsOptions({ highlight: true });\r\n```\r\n\r\nOnly identities absent before insertion are highlighted. Duplicate existing IDs/links are not considered new; an empty addition or a batch with no new identities preserves the previous selection. This option does not deduplicate the inserted data or change existing append behavior. The highlight event (reason `api`) occurs after rendering and before the usual `layoutChanged` event. Settings are not stored in nodes, links or exported data. Existing calls with one argument preserve highlighting as before.\r\n\r\n#### `on(eventName, listener)`\r\n\r\nSubscribe to diagram events. Returns an unsubscribe function.\r\n\r\nSupported events:\r\n\r\n- `\"highlightChanged\"` - emitted when the effective temporary node/link selection changes; see highlighting above.\r\n- `\"layoutChanged\"` - emitted after a drag-and-drop move ends, and after `addItems()` or `removeItems()` changes the diagram.\r\n- `\"nodeMoved\"` - emitted after a single node drag-and-drop move ends.\r\n- `\"nodeContextMenu\"` - emitted after right-clicking a node.\r\n\r\n```javascript\r\nconst unsubscribe = diagram.on(\"layoutChanged\", (data) => {\r\n  saveLayout(data.nodes.map(node => ({\r\n    id: node.id,\r\n    x: node.x,\r\n    y: node.y\r\n  })));\r\n});\r\n\r\nunsubscribe();\r\n```\r\n\r\n`layoutChanged` listeners receive the same data shape as `getData()`. The event is emitted after drag ends, not on every drag tick.\r\n\r\n```javascript\r\ndiagram.on(\"nodeMoved\", ({ node, data }) => {\r\n  console.log(node.id, node.x, node.y);\r\n  console.log(data.nodes);\r\n});\r\n\r\ndiagram.on(\"nodeContextMenu\", ({ node, data, event }) => {\r\n  console.log(node.id, node);\r\n  console.log(data.nodes);\r\n  console.log(event.clientX, event.clientY);\r\n});\r\n```\r\n\r\n#### `removeItems(itemIds)`\r\n\r\nRemove nodes and their associated links.\r\n\r\n```javascript\r\ndiagram.removeItems([\"Class1\", \"Class2\"]);\r\n```\r\n\r\n#### `getZoom()`\r\n\r\nGet the zoom controller for programmatic zoom/pan operations.\r\n\r\n```javascript\r\nconst zoom = diagram.getZoom();\r\nzoom.zoomIn();\r\nzoom.zoomOut();\r\nzoom.resetZoom();\r\nzoom.panLeft();\r\nzoom.panRight();\r\nzoom.panUp();\r\nzoom.panDown();\r\nzoom.center();\r\n```\r\n\r\n## Data Format\r\n\r\n### Node\r\n\r\n```typescript\r\n{\r\n  id: string;           // Unique identifier (required)\r\n  name: string;         // Display name (required)\r\n  namespace?: string;   // Optional namespace\r\n  width?: number;       // Node width (default: from style)\r\n  height?: number;      // Node height (default: from style)\r\n  x?: number;           // Initial X coordinate (optional - auto-placed if not provided)\r\n  y?: number;           // Initial Y coordinate (optional - auto-placed if not provided)\r\n  group?: number;       // Optional grouping\r\n}\r\n```\r\n\r\n**Initial Coordinates**: You can specify `x` and `y` coordinates to control node placement. If not provided, the library uses an improved auto-placement algorithm that minimizes line crossings by organizing nodes in layers based on their connections.\r\n\r\n### Link\r\n\r\n```typescript\r\n{\r\n  source: string;       // Source node ID (required)\r\n  target: string;      // Target node ID (required)\r\n  type: string;        // Link type: \"Inheritance\", \"Realization\", \"Association\", etc.\r\n}\r\n```\r\n\r\n## Examples\r\n\r\nSee the `examples/` directory for complete working examples:\r\n\r\n- `basic-usage.html` - Setup, add-and-highlight dependencies, external multi-class selection, clearing on empty canvas, zoom and export. Serve the repository over HTTP so its ES modules can load.\r\n\r\n![more examples](image.png)\r\n\r\n## Development\r\n\r\n```bash\r\n# Install dependencies\r\nnpm install\r\n\r\n# Build for development\r\nnpm run build:dev\r\n\r\n# Build for production\r\nnpm run build\r\n\r\n# Watch mode\r\nnpm run watch\r\n\r\n# Unit tests\r\nnpm test -- --runInBand\r\n\r\n# Browser tests (builds first; requires locally installed Google Chrome)\r\nnpm run test:browser\r\n```\r\n\r\nBrowser tests use Playwright as a development dependency only. Set `PLAYWRIGHT_CHANNEL=msedge` to use installed Edge instead. The test server binds to a temporary port on `127.0.0.1` and serves the basic usage example.\r\n\r\n## Browser Support\r\n\r\n- Chrome (latest)\r\n- Firefox (latest)\r\n- Safari (latest)\r\n- Edge (latest)\r\n\r\n## Dependencies\r\n\r\n- **d3** (^7.0.0) - For SVG manipulation and interactions\r\n- **lodash** (^4.17.21) - Utility functions\r\n\r\n## License\r\n\r\nMIT License - see [LICENSE](LICENSE) file for details.\r\n\r\n## Contributing\r\n\r\nContributions are welcome! Please feel free to submit a Pull Request.\r\n\r\n## Changelog\r\n\r\n### 0.2.3\r\n- Add node alignment snapping with temporary edge/center guides, zoom-aware tolerance, and Alt bypass (`snapToNodes`, `snapThreshold`, `alignmentGuideColor`).\r\n- Make incident-link selection on class clicks opt-in with the instance option `highlightIncidentLinksOnClick` (default `false`); enable it explicitly in basic usage.\r\n- Support click selection and Shift+click toggling of nodes and links.\r\n- Add optional `addItems(data, { highlight: true })` behavior and the `AddItemsOptions` settings class.\r\n- Avoid emitting node movement/layout events for clicks without dragging.\r\n- Add temporary node/link highlighting and `highlightChanged` events.\r\n- Preserve highlight state through redraw, drag and zoom; clear on an empty canvas click.\r\n- Export SVG from a detached copy with temporary highlighting omitted.\r\n- Demonstrate highlighting and external selection in basic usage; add unit/browser coverage.\r\n- Remove VS Code-specific stroke overrides from node dragging.\r\n\r\n### 0.1.0\r\n- Initial release\r\n- Basic UML diagram rendering\r\n- Interactive zoom and pan\r\n- Node drag and drop\r\n- Customizable styling\r\n","readmeFilename":"README.md"}