excalidraw-gen 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +320 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +107 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/exporter/index.d.ts +4 -0
  7. package/dist/exporter/index.js +33 -0
  8. package/dist/exporter/index.js.map +1 -0
  9. package/dist/layout/arrow-router.d.ts +14 -0
  10. package/dist/layout/arrow-router.js +171 -0
  11. package/dist/layout/arrow-router.js.map +1 -0
  12. package/dist/layout/dag.d.ts +13 -0
  13. package/dist/layout/dag.js +158 -0
  14. package/dist/layout/dag.js.map +1 -0
  15. package/dist/layout/grid.d.ts +3 -0
  16. package/dist/layout/grid.js +52 -0
  17. package/dist/layout/grid.js.map +1 -0
  18. package/dist/layout/index.d.ts +5 -0
  19. package/dist/layout/index.js +21 -0
  20. package/dist/layout/index.js.map +1 -0
  21. package/dist/normalizer/index.d.ts +5 -0
  22. package/dist/normalizer/index.js +37 -0
  23. package/dist/normalizer/index.js.map +1 -0
  24. package/dist/parser/index.d.ts +5 -0
  25. package/dist/parser/index.js +154 -0
  26. package/dist/parser/index.js.map +1 -0
  27. package/dist/renderer/elements.d.ts +4 -0
  28. package/dist/renderer/elements.js +210 -0
  29. package/dist/renderer/elements.js.map +1 -0
  30. package/dist/renderer/index.d.ts +2 -0
  31. package/dist/renderer/index.js +76 -0
  32. package/dist/renderer/index.js.map +1 -0
  33. package/dist/renderer/seed.d.ts +5 -0
  34. package/dist/renderer/seed.js +18 -0
  35. package/dist/renderer/seed.js.map +1 -0
  36. package/dist/templates/index.d.ts +4 -0
  37. package/dist/templates/index.js +93 -0
  38. package/dist/templates/index.js.map +1 -0
  39. package/dist/templates/themes.d.ts +3 -0
  40. package/dist/templates/themes.js +90 -0
  41. package/dist/templates/themes.js.map +1 -0
  42. package/dist/types/index.d.ts +222 -0
  43. package/dist/types/index.js +6 -0
  44. package/dist/types/index.js.map +1 -0
  45. package/dist/validator/index.d.ts +4 -0
  46. package/dist/validator/index.js +132 -0
  47. package/dist/validator/index.js.map +1 -0
  48. package/dist/validator/preflight.d.ts +10 -0
  49. package/dist/validator/preflight.js +138 -0
  50. package/dist/validator/preflight.js.map +1 -0
  51. package/package.json +47 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nixon Kurian
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,320 @@
1
+ # excalidraw-gen
2
+
3
+ A CLI tool that generates [Excalidraw](https://excalidraw.com)-compatible `.excalidraw` JSON files from structured JSON **or YAML** input specs.
4
+
5
+ Define your diagram with nodes, edges, types, colors, sizes, and arrow styles — get a fully-formed Excalidraw diagram you can open immediately in the browser or desktop app.
6
+
7
+ ---
8
+
9
+ ## Features
10
+
11
+ - **JSON and YAML input** — both formats supported natively
12
+ - **Per-node style overrides** — color, size, shape, opacity, fill pattern, border style
13
+ - **Per-edge style overrides** — stroke color, dash style, thickness, opacity
14
+ - **DAG layout** — BFS layered layout with Kahn's algorithm (handles cyclic graphs without hanging)
15
+ - **Dynamic node sizing** — node height auto-expands for multi-line or long labels
16
+ - **Grid layout** — simple √n-column grid for non-hierarchical diagrams
17
+ - **Two templates** — `flowchart` and `architecture` with distinct node styles
18
+ - **Three themes** — `default`, `pastel`, `dark`
19
+ - **Step-routed elbow arrows** — clearly exit the correct edge of the source node
20
+ - **Accurate arrow labels** — labels placed at the geometric midpoint of the elbow path
21
+ - **Pre-flight validation** — catches Excalidraw JSON issues before writing the file
22
+
23
+ ---
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ git clone https://github.com/your-username/excalidraw-gen.git
29
+ cd excalidraw-gen
30
+ npm install
31
+ npm run build
32
+ ```
33
+
34
+ To use globally:
35
+
36
+ ```bash
37
+ npm link
38
+ ```
39
+
40
+ ---
41
+
42
+ ## Usage
43
+
44
+ ```bash
45
+ excalidraw-gen generate <input> [options]
46
+ ```
47
+
48
+ | Option | Default | Description |
49
+ |---|---|---|
50
+ | `--template` | `flowchart` | `flowchart` or `architecture` |
51
+ | `--theme` | `default` | `default`, `pastel`, or `dark` |
52
+ | `--layout` | `dag` | `dag` or `grid` |
53
+ | `--out` | stdout | Output file path (`.excalidraw`) |
54
+ | `--max-nodes <n>` | `200` | Reject diagrams with more nodes than this |
55
+
56
+ ### Examples
57
+
58
+ ```bash
59
+ # JSON flowchart
60
+ excalidraw-gen generate examples/simple-flowchart.json \
61
+ --template flowchart --out out/flow.excalidraw
62
+
63
+ # YAML input
64
+ excalidraw-gen generate examples/pipeline.yaml --out out/pipeline.excalidraw
65
+
66
+ # Architecture diagram with dark theme
67
+ excalidraw-gen generate examples/architecture.json \
68
+ --template architecture --theme dark --out out/arch.excalidraw
69
+
70
+ # Styled flowchart with per-node/edge colors
71
+ excalidraw-gen generate examples/styled-flowchart.json --out out/styled.excalidraw
72
+
73
+ # Print to stdout (pipe-friendly)
74
+ excalidraw-gen generate examples/simple-flowchart.json
75
+ ```
76
+
77
+ Open the `.excalidraw` file at [excalidraw.com](https://excalidraw.com) via **Open** → select file.
78
+
79
+ ---
80
+
81
+ ## Input Format
82
+
83
+ Input can be **JSON** or **YAML**. Both support the same fields.
84
+
85
+ ### JSON
86
+
87
+ ```json
88
+ {
89
+ "type": "flowchart",
90
+ "title": "My Diagram",
91
+ "nodes": [
92
+ {
93
+ "id": "start",
94
+ "label": "Start",
95
+ "type": "start",
96
+ "style": {
97
+ "backgroundColor": "#a5d8ff",
98
+ "strokeColor": "#1971c2",
99
+ "shape": "ellipse",
100
+ "width": 160,
101
+ "height": 60
102
+ }
103
+ },
104
+ { "id": "process", "label": "Do Something", "type": "process" },
105
+ {
106
+ "id": "decide",
107
+ "label": "OK?",
108
+ "type": "decision",
109
+ "style": { "strokeStyle": "dashed", "strokeWidth": 3 }
110
+ },
111
+ { "id": "end", "label": "End", "type": "end" }
112
+ ],
113
+ "edges": [
114
+ { "from": "start", "to": "process" },
115
+ { "from": "process", "to": "decide", "label": "Check" },
116
+ {
117
+ "from": "decide",
118
+ "to": "end",
119
+ "label": "Yes",
120
+ "style": { "strokeColor": "#2f9e44", "strokeWidth": 2 }
121
+ }
122
+ ]
123
+ }
124
+ ```
125
+
126
+ ### YAML
127
+
128
+ ```yaml
129
+ type: flowchart
130
+ title: My Diagram
131
+
132
+ nodes:
133
+ - id: start
134
+ label: Start
135
+ type: start
136
+ style:
137
+ backgroundColor: "#a5d8ff"
138
+ strokeColor: "#1971c2"
139
+ shape: ellipse
140
+
141
+ - id: process
142
+ label: Do Something
143
+ type: process
144
+
145
+ edges:
146
+ - from: start
147
+ to: process
148
+ label: Go
149
+ style:
150
+ strokeColor: "#7048e8"
151
+ strokeStyle: dashed
152
+ ```
153
+
154
+ ---
155
+
156
+ ## Style Overrides
157
+
158
+ ### Per-node `style` fields
159
+
160
+ | Field | Type | Description |
161
+ |---|---|---|
162
+ | `backgroundColor` | `string` | Fill color (hex, e.g. `"#ff6b6b"` or `"transparent"`) |
163
+ | `strokeColor` | `string` | Border color |
164
+ | `strokeWidth` | `number` | Border thickness in px |
165
+ | `strokeStyle` | `"solid"` \| `"dashed"` \| `"dotted"` | Border style |
166
+ | `fillStyle` | `"solid"` \| `"hachure"` \| `"cross-hatch"` \| `"zigzag"` | Fill pattern |
167
+ | `shape` | `"rectangle"` \| `"ellipse"` | Override the template shape |
168
+ | `width` | `number` | Node width in px |
169
+ | `height` | `number` | Node height in px (auto-computed if omitted) |
170
+ | `opacity` | `number` | Opacity 0–100 |
171
+
172
+ ### Per-edge `style` fields
173
+
174
+ | Field | Type | Description |
175
+ |---|---|---|
176
+ | `strokeColor` | `string` | Arrow color |
177
+ | `strokeStyle` | `"solid"` \| `"dashed"` \| `"dotted"` | Line style |
178
+ | `strokeWidth` | `number` | Thickness in px |
179
+ | `opacity` | `number` | Opacity 0–100 |
180
+
181
+ ---
182
+
183
+ ## Node Types
184
+
185
+ ### Flowchart template
186
+
187
+ | Type | Default style |
188
+ |---|---|
189
+ | `start` / `end` | Ellipse |
190
+ | `process` | Rectangle (purple) |
191
+ | `decision` | Dashed orange rectangle |
192
+ | `io` | Rectangle (teal) |
193
+ | `subprocess` | Rectangle (green) |
194
+
195
+ ### Architecture template
196
+
197
+ | Type | Default style |
198
+ |---|---|
199
+ | `service` / `api` | Rectangle (purple) |
200
+ | `db` / `database` | Rectangle (green) |
201
+ | `queue` | Rectangle (yellow) |
202
+ | `cache` | Rectangle (orange) |
203
+ | `gateway` / `orchestrator` | Bold rectangle (red) |
204
+ | `frontend` | Rectangle (blue) |
205
+ | `user` / `actor` | Ellipse |
206
+ | `external` | Rectangle (light red) |
207
+ | `ml` / `ai` | Rectangle (purple haze) |
208
+ | `monitor` | Rectangle (green) |
209
+
210
+ > **Note:** No `diamond` shape exists in Excalidraw raw JSON. Decision nodes use a dashed orange rectangle as the canonical alternative.
211
+
212
+ ---
213
+
214
+ ## Development
215
+
216
+ ### Scripts
217
+
218
+ ```bash
219
+ npm run build # Compile TypeScript → dist/
220
+ npm run dev # Run directly via ts-node (no build needed)
221
+ npm test # Run all tests (vitest)
222
+ npm run test:watch # Watch mode
223
+ npm run lint # Type-check without emitting
224
+ ```
225
+
226
+ ### Project structure
227
+
228
+ ```
229
+ src/
230
+ cli.ts # Commander CLI entrypoint
231
+ types/ # Shared TypeScript types (NodeStyle, EdgeStyle, etc.)
232
+ parser/ # JSON + YAML input → Diagram object
233
+ validator/ # Input validation (errors + warnings)
234
+ normalizer/ # Defaults, ID cleanup, duplicate-edge detection
235
+ layout/
236
+ dag.ts # Kahn's topological sort + BFS DAG layout
237
+ grid.ts # Grid layout
238
+ arrow-router.ts # Elbow arrow anchor + step point calculation
239
+ renderer/
240
+ elements.ts # Shape / text / arrow element factories (with style merging)
241
+ index.ts # Full render orchestration
242
+ seed.ts # Deterministic ID hashing (FNV-1a)
243
+ templates/
244
+ index.ts # flowchart + architecture template definitions
245
+ themes.ts # default / pastel / dark color overlays
246
+ exporter/ # Assemble ExcalidrawFile, pre-flight check, write
247
+ validator/
248
+ preflight.ts # Post-render element sanity checks
249
+ examples/
250
+ simple-flowchart.json # 5-node login flow
251
+ styled-flowchart.json # Payment flow with per-node/edge style overrides
252
+ architecture.json # 8-node 3-tier web app
253
+ pipeline.yaml # CI/CD pipeline (YAML format)
254
+ ```
255
+
256
+ ---
257
+
258
+ ## Contributing
259
+
260
+ ### 1. Fork and clone
261
+
262
+ ```bash
263
+ git clone https://github.com/your-username/excalidraw-gen.git
264
+ cd excalidraw-gen
265
+ npm install
266
+ ```
267
+
268
+ ### 2. Create a branch
269
+
270
+ ```bash
271
+ git checkout -b feat/your-feature-name
272
+ ```
273
+
274
+ Use a descriptive prefix: `feat/`, `fix/`, `docs/`, `test/`, `refactor/`.
275
+
276
+ ### 3. Make your changes
277
+
278
+ - Keep changes focused — one concern per PR
279
+ - Match the existing code style (TypeScript strict mode, no `any`)
280
+ - Do not add comments or docstrings to code you didn't change
281
+
282
+ ### 4. Run tests and build
283
+
284
+ ```bash
285
+ npm run build && npm test
286
+ ```
287
+
288
+ Both must pass with zero errors before submitting.
289
+
290
+ ### 5. Submit a pull request
291
+
292
+ Open a PR against `main` with a clear description of what changes and why.
293
+
294
+ ### Adding a new template
295
+
296
+ 1. Add a `TemplateDefinition` in `src/templates/index.ts` using `registerTemplate()`
297
+ 2. Add theme overrides in `src/templates/themes.ts` if needed
298
+ 3. Add an example JSON or YAML file under `examples/`
299
+ 4. Add tests in `tests/renderer.test.ts`
300
+
301
+ ### Adding a new layout engine
302
+
303
+ 1. Implement `(diagram: Diagram) => LayoutNode[]` in `src/layout/`
304
+ 2. Register it in `src/layout/index.ts` using `registerLayoutEngine()`
305
+ 3. Add tests in `tests/layout.test.ts`
306
+
307
+ ### Known Excalidraw raw JSON constraints
308
+
309
+ - No `type: "diamond"` — use dashed rectangles for decision nodes
310
+ - Labels require **two elements**: the shape with `boundElements` + a separate `text` with `containerId`
311
+ - Arrows must have `roughness: 0`, `roundness: null`, `elbowed: true`
312
+ - Arrow `x, y` is the **edge point** of the source shape, not the center
313
+ - `points[0]` is always `[0, 0]`; all other points are relative offsets
314
+
315
+ ---
316
+
317
+ ## License
318
+
319
+ [MIT](LICENSE)
320
+
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const fs_1 = require("fs");
5
+ const commander_1 = require("commander");
6
+ const index_js_1 = require("./parser/index.js");
7
+ const index_js_2 = require("./validator/index.js");
8
+ const index_js_3 = require("./normalizer/index.js");
9
+ const index_js_4 = require("./templates/index.js");
10
+ const themes_js_1 = require("./templates/themes.js");
11
+ const index_js_5 = require("./layout/index.js");
12
+ const index_js_6 = require("./renderer/index.js");
13
+ const index_js_7 = require("./exporter/index.js");
14
+ const preflight_js_1 = require("./validator/preflight.js");
15
+ const program = new commander_1.Command();
16
+ program
17
+ .name("excalidraw-gen")
18
+ .description("Generate Excalidraw-compatible JSON diagrams from structured input specs")
19
+ .version("0.1.0");
20
+ program
21
+ .command("generate <input>")
22
+ .description("Generate an Excalidraw diagram from a JSON spec file")
23
+ .option("--template <name>", "Diagram template: flowchart | architecture", "flowchart")
24
+ .option("--theme <name>", "Color theme: default | pastel | dark", "default")
25
+ .option("--layout <type>", "Layout algorithm: dag | grid", "dag")
26
+ .option("--out <filepath>", "Output file path (default: stdout)")
27
+ .option("--no-deterministic", "Disable deterministic mode (enabled by default)")
28
+ .option("--max-nodes <n>", "Maximum nodes allowed", (v) => parseInt(v, 10), 200)
29
+ .action((inputPath, opts) => {
30
+ const options = {
31
+ template: opts.template,
32
+ theme: opts.theme,
33
+ layout: opts.layout,
34
+ out: opts.out,
35
+ deterministic: opts.deterministic,
36
+ maxNodes: opts.maxNodes,
37
+ };
38
+ try {
39
+ // ── 1. Read input ──────────────────────────────────────────────────
40
+ let inputContent;
41
+ try {
42
+ inputContent = (0, fs_1.readFileSync)(inputPath, "utf-8");
43
+ }
44
+ catch {
45
+ process.stderr.write(`Error: Cannot read file "${inputPath}"\n`);
46
+ process.exit(1);
47
+ }
48
+ // ── 2. Parse ───────────────────────────────────────────────────────
49
+ let diagram;
50
+ try {
51
+ diagram = (0, index_js_1.parse)(inputContent);
52
+ }
53
+ catch (err) {
54
+ if (err instanceof index_js_1.ParseError) {
55
+ process.stderr.write(`Parse error: ${err.message}\n`);
56
+ process.exit(1);
57
+ }
58
+ throw err;
59
+ }
60
+ // ── 3. Validate (input) ────────────────────────────────────────────
61
+ const { errors, warnings } = (0, index_js_2.validate)(diagram, { maxNodes: options.maxNodes });
62
+ for (const warn of warnings) {
63
+ process.stderr.write(`Warning: ${warn}\n`);
64
+ }
65
+ if (errors.length > 0) {
66
+ for (const err of errors) {
67
+ process.stderr.write(`Error: ${err}\n`);
68
+ }
69
+ process.exit(1);
70
+ }
71
+ // ── 4. Normalize ───────────────────────────────────────────────────
72
+ const { diagram: normalDiagram, warnings: normWarnings } = (0, index_js_3.normalize)(diagram);
73
+ for (const warn of normWarnings) {
74
+ process.stderr.write(`Warning: ${warn}\n`);
75
+ }
76
+ // ── 5. Layout ──────────────────────────────────────────────────────
77
+ const layoutNodes = (0, index_js_5.runLayout)(normalDiagram, options.layout);
78
+ // ── 6. Template + Theme ────────────────────────────────────────────
79
+ const baseTemplate = (0, index_js_4.getTemplate)(options.template);
80
+ const template = (0, themes_js_1.applyThemeToTemplate)(baseTemplate, options.theme);
81
+ // ── 7. Render ──────────────────────────────────────────────────────
82
+ const elements = (0, index_js_6.render)(normalDiagram, layoutNodes, template);
83
+ // ── 8. Export ──────────────────────────────────────────────────────
84
+ if (options.out) {
85
+ (0, index_js_7.exportToFile)(elements, options.theme, options.out);
86
+ process.stderr.write(`✓ Diagram written to ${options.out}\n`);
87
+ }
88
+ else {
89
+ (0, index_js_7.exportToStdout)(elements, options.theme);
90
+ }
91
+ }
92
+ catch (err) {
93
+ if (err instanceof preflight_js_1.PreflightError) {
94
+ process.stderr.write(`${err.message}\n`);
95
+ process.exit(1);
96
+ }
97
+ if (err instanceof Error) {
98
+ process.stderr.write(`Unexpected error: ${err.message}\n`);
99
+ }
100
+ else {
101
+ process.stderr.write(`Unexpected error: ${String(err)}\n`);
102
+ }
103
+ process.exit(1);
104
+ }
105
+ });
106
+ program.parse(process.argv);
107
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { ExcalidrawElement, ExcalidrawFile, Theme } from "../types/index.js";
2
+ export declare function buildExcalidrawFile(elements: ExcalidrawElement[], theme: Theme): ExcalidrawFile;
3
+ export declare function exportToFile(elements: ExcalidrawElement[], theme: Theme, outputPath: string): void;
4
+ export declare function exportToStdout(elements: ExcalidrawElement[], theme: Theme): void;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildExcalidrawFile = buildExcalidrawFile;
4
+ exports.exportToFile = exportToFile;
5
+ exports.exportToStdout = exportToStdout;
6
+ const fs_1 = require("fs");
7
+ const preflight_js_1 = require("../validator/preflight.js");
8
+ const themes_js_1 = require("../templates/themes.js");
9
+ function buildExcalidrawFile(elements, theme) {
10
+ return {
11
+ type: "excalidraw",
12
+ version: 2,
13
+ source: "excalidraw-gen",
14
+ elements,
15
+ appState: {
16
+ gridSize: 20,
17
+ viewBackgroundColor: (0, themes_js_1.getViewBackground)(theme),
18
+ },
19
+ files: {},
20
+ };
21
+ }
22
+ function exportToFile(elements, theme, outputPath) {
23
+ (0, preflight_js_1.preflightValidate)(elements);
24
+ const file = buildExcalidrawFile(elements, theme);
25
+ const json = JSON.stringify(file, null, 2);
26
+ (0, fs_1.writeFileSync)(outputPath, json, "utf-8");
27
+ }
28
+ function exportToStdout(elements, theme) {
29
+ (0, preflight_js_1.preflightValidate)(elements);
30
+ const file = buildExcalidrawFile(elements, theme);
31
+ process.stdout.write(JSON.stringify(file, null, 2) + "\n");
32
+ }
33
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/exporter/index.ts"],"names":[],"mappings":";;AAKA,kDAeC;AAED,oCASC;AAED,wCAOC;AAxCD,2BAAmC;AAEnC,4DAA8D;AAC9D,sDAA2D;AAE3D,SAAgB,mBAAmB,CACjC,QAA6B,EAC7B,KAAY;IAEZ,OAAO;QACL,IAAI,EAAE,YAAY;QAClB,OAAO,EAAE,CAAC;QACV,MAAM,EAAE,gBAAgB;QACxB,QAAQ;QACR,QAAQ,EAAE;YACR,QAAQ,EAAE,EAAE;YACZ,mBAAmB,EAAE,IAAA,6BAAiB,EAAC,KAAK,CAAC;SAC9C;QACD,KAAK,EAAE,EAAE;KACV,CAAC;AACJ,CAAC;AAED,SAAgB,YAAY,CAC1B,QAA6B,EAC7B,KAAY,EACZ,UAAkB;IAElB,IAAA,gCAAiB,EAAC,QAAQ,CAAC,CAAC;IAC5B,MAAM,IAAI,GAAG,mBAAmB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAClD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC3C,IAAA,kBAAa,EAAC,UAAU,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;AAC3C,CAAC;AAED,SAAgB,cAAc,CAC5B,QAA6B,EAC7B,KAAY;IAEZ,IAAA,gCAAiB,EAAC,QAAQ,CAAC,CAAC;IAC5B,MAAM,IAAI,GAAG,mBAAmB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAClD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;AAC7D,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { LayoutNode, ArrowRoute } from "../types/index.js";
2
+ /**
3
+ * Minimum y-difference to consider nodes as being on different rows.
4
+ * Matches SPACING_Y * 0.4 from the layout engine (150 * 0.4 = 60).
5
+ * Exported so the renderer can use the same threshold for stagger grouping.
6
+ */
7
+ export declare const VERTICAL_THRESHOLD = 60;
8
+ /**
9
+ * Route an elbow arrow from source → target nodes.
10
+ *
11
+ * @param sourceEdgeIndex 0-based index of this arrow among all arrows sharing the same source edge
12
+ * @param totalFromSource total count of arrows leaving from the same edge of source
13
+ */
14
+ export declare function routeArrow(source: LayoutNode, target: LayoutNode, sourceEdgeIndex?: number, totalFromSource?: number): ArrowRoute;