diagram-contracts 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.
package/dist/index.mjs ADDED
@@ -0,0 +1,4970 @@
1
+ // src/schema/index.ts
2
+ var TOKEN_CATALOG = {
3
+ widthSize: ["wrap", "fill"],
4
+ heightSize: ["wrap", "fill"],
5
+ align: ["start", "center", "end", "stretch"],
6
+ justify: ["start", "center", "end", "spaceBetween", "spaceAround", "spaceEvenly"],
7
+ contentAlign: [
8
+ "topStart",
9
+ "top",
10
+ "topEnd",
11
+ "start",
12
+ "center",
13
+ "end",
14
+ "bottomStart",
15
+ "bottom",
16
+ "bottomEnd"
17
+ ],
18
+ axis: ["vertical", "horizontal"],
19
+ // diagram-contracts specific
20
+ shape: [
21
+ "rect",
22
+ "roundedRect",
23
+ "circle",
24
+ "ellipse",
25
+ "diamond",
26
+ "parallelogram",
27
+ "capsule",
28
+ "cylinder",
29
+ "hexagon",
30
+ "triangle"
31
+ ],
32
+ anchorPosition: [
33
+ "topLeft",
34
+ "top",
35
+ "topRight",
36
+ "left",
37
+ "center",
38
+ "right",
39
+ "bottomLeft",
40
+ "bottom",
41
+ "bottomRight",
42
+ "auto"
43
+ ],
44
+ routeStyle: ["orthogonal", "straight", "curved"],
45
+ arrowHead: ["none", "arrow", "filledArrow", "diamond", "filledDiamond", "circle", "filledCircle"],
46
+ strokeStyle: ["solid", "dashed", "dotted"],
47
+ constraintStrength: ["required", "strong", "normal", "weak"],
48
+ textPosition: ["inside", "above", "below", "start", "end"],
49
+ textOverflow: ["clip", "ellipsis"],
50
+ fontWeight: ["regular", "medium", "bold"],
51
+ imageFit: ["contain", "cover", "fill", "none"],
52
+ cellHAlign: ["left", "center", "right"],
53
+ cellVAlign: ["top", "center", "bottom"],
54
+ geometryRole: ["layout", "align", "connect", "hit", "measure"],
55
+ geometryTarget: ["self", "icon"]
56
+ };
57
+ var PROPERTY_CATALOG = {
58
+ // ── identity ──────────────────────────────────────────────────────────
59
+ id: { type: "string", category: "identity", description: "Stable identifier for node/group. Used by constraints, diff, diagnostics" },
60
+ // ── size ──────────────────────────────────────────────────────────────
61
+ width: { type: "string | number", category: "size", description: "wrap / fill / fixed value" },
62
+ height: { type: "string | number", category: "size", description: "wrap / fill / fixed value" },
63
+ minWidth: { type: "number", category: "size" },
64
+ maxWidth: { type: "number | string", category: "size", notes: "fill allowed" },
65
+ minHeight: { type: "number", category: "size" },
66
+ maxHeight: { type: "number | string", category: "size", notes: "fill allowed" },
67
+ aspectRatio: { type: "number", category: "size", description: "width / height ratio" },
68
+ weight: { type: "number", category: "size", description: "Flex ratio in HStack / VStack" },
69
+ // ── spacing ───────────────────────────────────────────────────────────
70
+ padding: { type: "string | number", category: "spacing", description: "Inner padding. Token or per-side" },
71
+ gap: { type: "string | number", category: "spacing", description: "Gap between children" },
72
+ // ── alignment ─────────────────────────────────────────────────────────
73
+ align: { type: "string", category: "alignment", enum: "align", description: "Cross-axis alignment" },
74
+ justify: { type: "string", category: "alignment", enum: "justify", description: "Main-axis alignment" },
75
+ selfAlign: { type: "string", category: "alignment", enum: "align", description: "Self alignment override" },
76
+ contentAlign: { type: "string", category: "alignment", enum: "contentAlign", description: "Child content alignment (Box / Overlay)" },
77
+ axis: { type: "string", category: "alignment", enum: "axis", description: "Orientation axis" },
78
+ // ── visual ────────────────────────────────────────────────────────────
79
+ background: { type: "string", category: "visual", description: "Container background color token" },
80
+ foreground: { type: "string", category: "visual", description: "Foreground color token (text / icon propagation)" },
81
+ opacity: { type: "number", category: "visual", description: "Opacity 0..1" },
82
+ variant: { type: "string", category: "visual", description: "Theme variant" },
83
+ visible: { type: "boolean", category: "visual", description: "Visibility" },
84
+ // ── shape-visual ──────────────────────────────────────────────────────
85
+ shape: { type: "string", category: "shape-visual", lowering: "attribute", enum: "shape", description: "Visual shape. Layout always uses bounding rect" },
86
+ radius: { type: "string | number", category: "shape-visual", description: "Corner radius. Effective with shape: rect / roundedRect" },
87
+ fill: { type: "string", category: "shape-visual", lowering: "attribute", notes: "color token" },
88
+ stroke: { type: "string", category: "shape-visual", lowering: "attribute", notes: "color token" },
89
+ strokeWidth: { type: "number", category: "shape-visual", lowering: "attribute" },
90
+ strokeStyle: { type: "string", category: "shape-visual", lowering: "attribute", enum: "strokeStyle" },
91
+ shadow: { type: "string", category: "shape-visual", description: "Shadow token" },
92
+ // ── text ──────────────────────────────────────────────────────────────
93
+ text: { type: "string", category: "text", description: "Text content (normalized from child text nodes in IR)" },
94
+ textStyle: { type: "string", category: "text", description: "Typography token" },
95
+ textAlign: { type: "string", category: "text", description: "Text alignment (start / center / end)" },
96
+ fontSize: { type: "string | number", category: "text", description: "Font size" },
97
+ fontWeight: { type: "string", category: "text", enum: "fontWeight" },
98
+ lineLimit: { type: "number | string", category: "text", description: "Max lines. 'none' for unlimited" },
99
+ textOverflow: { type: "string", category: "text", enum: "textOverflow" },
100
+ textPosition: { type: "string", category: "text", lowering: "attribute", enum: "textPosition", description: "Label position on connector" },
101
+ // ── geometry ─────────────────────────────────────────────────────────
102
+ geometry: { type: "object", category: "geometry", description: "Geometry role mapping: { layout, align, connect, hit, measure } \u2192 target (self | icon | child id)" },
103
+ anchor: { type: "string", category: "constraint", lowering: "none", description: "Reference target for alignment; other targets become movers" },
104
+ // ── connector ─────────────────────────────────────────────────────────
105
+ from: { type: "string", category: "connector", lowering: "attribute", description: "Source node id or nodeId:role reference" },
106
+ to: { type: "string", category: "connector", lowering: "attribute", description: "Target node id or nodeId:role reference" },
107
+ fromAnchor: { type: "string", category: "connector", lowering: "attribute", enum: "anchorPosition" },
108
+ toAnchor: { type: "string", category: "connector", lowering: "attribute", enum: "anchorPosition" },
109
+ route: { type: "string", category: "connector", lowering: "attribute", enum: "routeStyle" },
110
+ cornerRadius: { type: "number", category: "connector", lowering: "attribute", description: "Corner rounding for orthogonal routing" },
111
+ curvature: { type: "number", category: "connector", lowering: "attribute", description: "Control point distance ratio for curved routing (0.1..1.0, default 0.33)" },
112
+ startArrow: { type: "string", category: "connector", lowering: "attribute", enum: "arrowHead" },
113
+ endArrow: { type: "string", category: "connector", lowering: "attribute", enum: "arrowHead" },
114
+ arrowSize: { type: "number", category: "connector", lowering: "attribute", description: "Arrow marker scale factor (default 1)" },
115
+ strokeDasharray: { type: "string", category: "shape-visual", lowering: "attribute", description: "Custom SVG stroke-dasharray. Overrides strokeStyle" },
116
+ // ── constraint ────────────────────────────────────────────────────────
117
+ targets: { type: "array", category: "constraint", lowering: "none", notes: "array<string>. Supports nodeId:role syntax for geometry role references" },
118
+ mover: { type: "string", category: "constraint", lowering: "none" },
119
+ strength: { type: "string", category: "constraint", lowering: "none", enum: "constraintStrength" },
120
+ strategy: { type: "string", category: "constraint", lowering: "none", description: "Size equalization strategy: max | min | first" },
121
+ // ── table ─────────────────────────────────────────────────────────────
122
+ colAlign: { type: "string", category: "alignment", enum: "cellHAlign", description: "Cell horizontal alignment (left / center / right)" },
123
+ rowAlign: { type: "string", category: "alignment", enum: "cellVAlign", description: "Cell vertical alignment (top / center / bottom)" },
124
+ headerRows: { type: "number", category: "structure", description: "Number of header rows (visual distinction)" },
125
+ // ── asset ─────────────────────────────────────────────────────────────
126
+ src: { type: "string", category: "asset", description: "Image / SVG source reference" },
127
+ fit: { type: "string", category: "asset", enum: "imageFit", description: "Fit method" },
128
+ name: { type: "string", category: "asset", lowering: "attribute", description: "Icon token or asset name" }
129
+ };
130
+ var ELEMENT_CATALOG = {
131
+ // ── structure ─────────────────────────────────────────────────────────
132
+ document: {
133
+ tier: "core",
134
+ category: "structure",
135
+ properties: ["id", "width", "height", "padding", "background", "variant"],
136
+ children: {
137
+ kind: "list",
138
+ items: [
139
+ "hstack",
140
+ "vstack",
141
+ "overlay",
142
+ "grid",
143
+ "flow",
144
+ "absolute",
145
+ "box",
146
+ "group",
147
+ "table",
148
+ "connector",
149
+ "alignConstraint",
150
+ "equalizeWidth",
151
+ "equalizeHeight",
152
+ "minGap",
153
+ "keepInsideConstraint",
154
+ "orderConstraint"
155
+ ]
156
+ }
157
+ },
158
+ group: {
159
+ tier: "core",
160
+ category: "structure",
161
+ properties: ["id", "visible", "padding", "gap", "background", "opacity", "geometry", "variant"],
162
+ children: { kind: "any" }
163
+ },
164
+ // ── layout ────────────────────────────────────────────────────────────
165
+ box: {
166
+ tier: "core",
167
+ category: "layout",
168
+ properties: [
169
+ "id",
170
+ "visible",
171
+ "width",
172
+ "height",
173
+ "minWidth",
174
+ "maxWidth",
175
+ "minHeight",
176
+ "maxHeight",
177
+ "padding",
178
+ "gap",
179
+ "contentAlign",
180
+ "foreground",
181
+ "fill",
182
+ "stroke",
183
+ "strokeWidth",
184
+ "strokeStyle",
185
+ "radius",
186
+ "shadow",
187
+ "opacity",
188
+ "shape",
189
+ "selfAlign",
190
+ "geometry",
191
+ "variant"
192
+ ],
193
+ children: { kind: "any" }
194
+ },
195
+ hstack: {
196
+ tier: "core",
197
+ category: "layout",
198
+ properties: [
199
+ "id",
200
+ "visible",
201
+ "gap",
202
+ "align",
203
+ "justify",
204
+ "padding",
205
+ "width",
206
+ "height",
207
+ "minWidth",
208
+ "maxWidth",
209
+ "minHeight",
210
+ "maxHeight",
211
+ "geometry",
212
+ "variant"
213
+ ],
214
+ children: { kind: "any" }
215
+ },
216
+ vstack: {
217
+ tier: "core",
218
+ category: "layout",
219
+ properties: [
220
+ "id",
221
+ "visible",
222
+ "gap",
223
+ "align",
224
+ "justify",
225
+ "padding",
226
+ "width",
227
+ "height",
228
+ "minWidth",
229
+ "maxWidth",
230
+ "minHeight",
231
+ "maxHeight",
232
+ "geometry",
233
+ "variant"
234
+ ],
235
+ children: { kind: "any" }
236
+ },
237
+ overlay: {
238
+ tier: "core",
239
+ category: "layout",
240
+ properties: ["id", "visible", "gap", "contentAlign", "padding", "width", "height", "background", "variant"],
241
+ children: { kind: "any" }
242
+ },
243
+ grid: {
244
+ tier: "core",
245
+ category: "layout",
246
+ properties: [
247
+ "id",
248
+ "visible",
249
+ "gap",
250
+ "align",
251
+ "justify",
252
+ "padding",
253
+ "width",
254
+ "height",
255
+ "minWidth",
256
+ "maxWidth",
257
+ "minHeight",
258
+ "maxHeight",
259
+ "variant"
260
+ ],
261
+ children: { kind: "any" },
262
+ notes: "Track definitions normalized as graph fragments. v1 starts with even tracks"
263
+ },
264
+ flow: {
265
+ tier: "core",
266
+ category: "layout",
267
+ properties: [
268
+ "id",
269
+ "visible",
270
+ "gap",
271
+ "padding",
272
+ "width",
273
+ "height",
274
+ "minWidth",
275
+ "maxWidth",
276
+ "align",
277
+ "variant"
278
+ ],
279
+ children: { kind: "any" }
280
+ },
281
+ absolute: {
282
+ tier: "optional",
283
+ category: "layout",
284
+ properties: ["id", "visible", "padding", "width", "height", "variant"],
285
+ children: { kind: "any" }
286
+ },
287
+ spacer: {
288
+ tier: "core",
289
+ category: "layout",
290
+ properties: ["id", "width", "height", "weight"],
291
+ children: { kind: "none" }
292
+ },
293
+ divider: {
294
+ tier: "core",
295
+ category: "layout",
296
+ properties: ["id", "axis", "stroke", "strokeWidth", "strokeStyle"],
297
+ children: { kind: "none" }
298
+ },
299
+ // ── text ──────────────────────────────────────────────────────────────
300
+ text: {
301
+ tier: "core",
302
+ category: "text",
303
+ properties: [
304
+ "id",
305
+ "text",
306
+ "visible",
307
+ "textStyle",
308
+ "textAlign",
309
+ "fontSize",
310
+ "fontWeight",
311
+ "lineLimit",
312
+ "textOverflow",
313
+ "foreground",
314
+ "background",
315
+ "opacity",
316
+ "padding",
317
+ "variant"
318
+ ],
319
+ children: { kind: "text" }
320
+ },
321
+ label: {
322
+ tier: "core",
323
+ category: "text",
324
+ properties: [
325
+ "id",
326
+ "text",
327
+ "visible",
328
+ "textStyle",
329
+ "textAlign",
330
+ "fontSize",
331
+ "fontWeight",
332
+ "textPosition",
333
+ "foreground",
334
+ "background",
335
+ "opacity",
336
+ "padding",
337
+ "radius",
338
+ "variant"
339
+ ],
340
+ children: { kind: "text" }
341
+ },
342
+ // ── media ─────────────────────────────────────────────────────────────
343
+ image: {
344
+ tier: "core",
345
+ category: "media",
346
+ properties: ["id", "visible", "src", "fit", "width", "height", "aspectRatio", "radius", "opacity", "variant"],
347
+ children: { kind: "none" }
348
+ },
349
+ icon: {
350
+ tier: "core",
351
+ category: "media",
352
+ properties: ["id", "visible", "name", "foreground", "opacity", "variant"],
353
+ children: { kind: "none" }
354
+ },
355
+ svgAsset: {
356
+ tier: "core",
357
+ category: "media",
358
+ properties: ["id", "visible", "src", "width", "height", "opacity", "variant"],
359
+ children: { kind: "none" }
360
+ },
361
+ // ── table ───────────────────────────────────────────────────────────
362
+ table: {
363
+ tier: "core",
364
+ category: "layout",
365
+ properties: [
366
+ "id",
367
+ "visible",
368
+ "width",
369
+ "height",
370
+ "padding",
371
+ "gap",
372
+ "headerRows",
373
+ "fill",
374
+ "stroke",
375
+ "strokeWidth",
376
+ "variant"
377
+ ],
378
+ children: { kind: "list", items: ["row"] },
379
+ notes: "Table layout. Column widths equalized via columnWidthResolve (max of cell intrinsic widths). Row heights equalized via rowHeightResolve. No special table algorithm."
380
+ },
381
+ row: {
382
+ tier: "core",
383
+ category: "layout",
384
+ properties: ["id", "visible", "fill", "variant"],
385
+ children: { kind: "list", items: ["cell"] }
386
+ },
387
+ cell: {
388
+ tier: "core",
389
+ category: "layout",
390
+ properties: [
391
+ "id",
392
+ "visible",
393
+ "width",
394
+ "height",
395
+ "padding",
396
+ "colAlign",
397
+ "rowAlign",
398
+ "fill",
399
+ "stroke",
400
+ "strokeWidth",
401
+ "foreground",
402
+ "variant"
403
+ ],
404
+ children: { kind: "any" },
405
+ notes: "Table cell. colAlign (left/center/right) and rowAlign (top/center/bottom) control content positioning within the cell"
406
+ },
407
+ // ── connector ─────────────────────────────────────────────────────────
408
+ connector: {
409
+ tier: "core",
410
+ category: "connector",
411
+ properties: [
412
+ "id",
413
+ "visible",
414
+ "from",
415
+ "to",
416
+ "fromAnchor",
417
+ "toAnchor",
418
+ "route",
419
+ "cornerRadius",
420
+ "curvature",
421
+ "startArrow",
422
+ "endArrow",
423
+ "arrowSize",
424
+ "stroke",
425
+ "strokeWidth",
426
+ "strokeStyle",
427
+ "strokeDasharray",
428
+ "opacity",
429
+ "variant"
430
+ ],
431
+ children: { kind: "list", items: ["label"] },
432
+ notes: "Child Labels are annotation text on the connector. Label.textPosition specifies position"
433
+ },
434
+ // ── constraint ────────────────────────────────────────────────────────
435
+ alignConstraint: {
436
+ tier: "core",
437
+ category: "constraint",
438
+ properties: ["id", "targets", "axis", "align", "anchor", "mover", "strength"],
439
+ children: { kind: "none" },
440
+ notes: "axis: vertical + align: center = centerX alignment. axis: horizontal + align: center = centerY alignment"
441
+ },
442
+ equalizeWidth: {
443
+ tier: "core",
444
+ category: "constraint",
445
+ properties: ["id", "targets", "strategy", "strength"],
446
+ children: { kind: "none" },
447
+ notes: "Equalize widths of all targets. strategy: max (default) | min | first"
448
+ },
449
+ equalizeHeight: {
450
+ tier: "core",
451
+ category: "constraint",
452
+ properties: ["id", "targets", "strategy", "strength"],
453
+ children: { kind: "none" },
454
+ notes: "Equalize heights of all targets. strategy: max (default) | min | first"
455
+ },
456
+ minGap: {
457
+ tier: "optional",
458
+ category: "constraint",
459
+ properties: ["id", "targets", "gap", "axis", "strength"],
460
+ children: { kind: "none" }
461
+ },
462
+ keepInsideConstraint: {
463
+ tier: "optional",
464
+ category: "constraint",
465
+ properties: ["id", "targets", "strength"],
466
+ children: { kind: "none" }
467
+ },
468
+ orderConstraint: {
469
+ tier: "core",
470
+ category: "constraint",
471
+ properties: ["id", "targets", "axis", "strength"],
472
+ children: { kind: "none" },
473
+ notes: "targets array order is source order. axis: vertical = top-to-bottom, horizontal = left-to-right"
474
+ }
475
+ };
476
+
477
+ // src/ir/index.ts
478
+ import { z } from "zod";
479
+ var IrNodeSchema = z.lazy(
480
+ () => z.object({
481
+ kind: z.string(),
482
+ id: z.string().optional(),
483
+ props: z.record(z.string(), z.unknown()),
484
+ children: z.array(IrNodeSchema).optional()
485
+ })
486
+ );
487
+ function buildValidPropsSet(kind) {
488
+ const element = ELEMENT_CATALOG[kind];
489
+ if (!element) return /* @__PURE__ */ new Set();
490
+ const props = new Set(element.properties);
491
+ if (element.children.kind === "text") {
492
+ props.add("text");
493
+ }
494
+ return props;
495
+ }
496
+ function validateNode(node, path) {
497
+ if (!(node.kind in ELEMENT_CATALOG)) {
498
+ throw new Error(`Invalid element kind '${node.kind}' at ${path}`);
499
+ }
500
+ const validProps = buildValidPropsSet(node.kind);
501
+ const unknownProps = Object.keys(node.props).filter((k) => !validProps.has(k));
502
+ if (unknownProps.length > 0) {
503
+ throw new Error(
504
+ `Unknown properties for '${node.kind}' at ${path}: ${unknownProps.join(", ")}`
505
+ );
506
+ }
507
+ if (node.children) {
508
+ for (let i = 0; i < node.children.length; i++) {
509
+ validateNode(node.children[i], `${path}.children[${i}]`);
510
+ }
511
+ }
512
+ }
513
+ function parseIr(json) {
514
+ const parsed = IrNodeSchema.parse(json);
515
+ validateNode(parsed, "root");
516
+ return parsed;
517
+ }
518
+
519
+ // src/parser/tokenizer.ts
520
+ var SHAPE_PATTERNS = [
521
+ { shape: "capsule", open: "([", close: "])" },
522
+ { shape: "cylinder", open: "[(", close: ")]" },
523
+ { shape: "circle", open: "((", close: "))" },
524
+ { shape: "hexagon", open: "[[", close: "]]" },
525
+ { shape: "roundedRect", open: "[", close: "]" },
526
+ { shape: "diamond", open: "{", close: "}" },
527
+ { shape: "rect", open: "(", close: ")" }
528
+ ];
529
+ function parseNodeShape(rest) {
530
+ let shapeOverride;
531
+ let cursor = 0;
532
+ if (rest.startsWith("@")) {
533
+ const atMatch = rest.match(/^@(\w+)/);
534
+ if (!atMatch) return null;
535
+ shapeOverride = atMatch[1];
536
+ cursor = atMatch[0].length;
537
+ }
538
+ const remaining = rest.slice(cursor);
539
+ for (const { shape, open, close } of SHAPE_PATTERNS) {
540
+ if (!remaining.startsWith(open)) continue;
541
+ const contentStart = open.length;
542
+ const closeIdx = remaining.indexOf(close, contentStart);
543
+ if (closeIdx === -1) continue;
544
+ const afterClose = remaining.slice(closeIdx + close.length);
545
+ if (afterClose.length > 0 && /^[a-zA-Z0-9_-]/.test(afterClose)) continue;
546
+ let label = remaining.slice(contentStart, closeIdx);
547
+ if (label.startsWith('"') && label.endsWith('"')) {
548
+ label = label.slice(1, -1);
549
+ }
550
+ return {
551
+ label,
552
+ shape: shapeOverride ?? shape,
553
+ shapeOverride,
554
+ consumed: cursor + closeIdx + close.length
555
+ };
556
+ }
557
+ return null;
558
+ }
559
+ function matchEdgeOp(s) {
560
+ const trimmed = s.trimStart();
561
+ for (const op of ["==>", "-.->", "-->", "---", "~~~"]) {
562
+ if (!trimmed.startsWith(op)) continue;
563
+ let after = trimmed.slice(op.length);
564
+ let label;
565
+ if (op !== "~~~" && after.startsWith("|")) {
566
+ const pipeEnd = after.indexOf("|", 1);
567
+ if (pipeEnd !== -1) {
568
+ label = after.slice(1, pipeEnd).trim();
569
+ after = after.slice(pipeEnd + 1);
570
+ }
571
+ }
572
+ return { op, label, rest: after };
573
+ }
574
+ return null;
575
+ }
576
+ function parseNodeRef(s) {
577
+ const trimmed = s.trimStart();
578
+ const idMatch = trimmed.match(/^([a-zA-Z_][a-zA-Z0-9_-]*)/);
579
+ if (!idMatch) return null;
580
+ const nodeId = idMatch[1];
581
+ let after = trimmed.slice(idMatch[0].length);
582
+ const shapeParsed = parseNodeShape(after);
583
+ let def;
584
+ if (shapeParsed) {
585
+ after = after.slice(shapeParsed.consumed);
586
+ def = { label: shapeParsed.label, shape: shapeParsed.shape, shapeOverride: shapeParsed.shapeOverride };
587
+ }
588
+ if (after.startsWith(":::")) {
589
+ const classMatch = after.slice(3).match(/^([a-zA-Z_][a-zA-Z0-9_-]*)/);
590
+ if (classMatch) {
591
+ if (def) {
592
+ def.variant = classMatch[1];
593
+ } else {
594
+ def = { label: nodeId, shape: "roundedRect", variant: classMatch[1] };
595
+ }
596
+ after = after.slice(3 + classMatch[0].length);
597
+ }
598
+ }
599
+ return { nodeId, def, rest: after };
600
+ }
601
+ function tokenizeLine(line, lineNum) {
602
+ const trimmed = line.trim();
603
+ if (trimmed === "") return null;
604
+ if (trimmed.startsWith("%%")) {
605
+ return { type: "comment", raw: line, line: lineNum };
606
+ }
607
+ const dirMatch = trimmed.match(/^(?:flowchart|graph)\s+(TD|TB|LR)$/i);
608
+ if (dirMatch) {
609
+ const dirRaw = dirMatch[1].toUpperCase();
610
+ const dir = dirRaw === "TB" ? "TD" : dirRaw;
611
+ if (dir !== "TD" && dir !== "LR") {
612
+ return null;
613
+ }
614
+ return { type: "direction", raw: line, line: lineNum, dir };
615
+ }
616
+ const subMatch = trimmed.match(/^subgraph\s+(\S+)(?:\s+\[([^\]]*)\])?\s*$/);
617
+ if (subMatch) {
618
+ return {
619
+ type: "subgraphStart",
620
+ raw: line,
621
+ line: lineNum,
622
+ subgraphId: subMatch[1],
623
+ label: subMatch[2] ?? void 0
624
+ };
625
+ }
626
+ if (trimmed === "end") {
627
+ return { type: "subgraphEnd", raw: line, line: lineNum };
628
+ }
629
+ const localDirMatch = trimmed.match(/^direction\s+(TD|TB|LR)$/i);
630
+ if (localDirMatch) {
631
+ const dirRaw = localDirMatch[1].toUpperCase();
632
+ const dir = dirRaw === "TB" ? "TD" : dirRaw;
633
+ return { type: "localDirection", raw: line, line: lineNum, dir };
634
+ }
635
+ const classDefMatch = trimmed.match(/^classDef\s+(\S+)\s+(.+)$/);
636
+ if (classDefMatch) {
637
+ return {
638
+ type: "classDef",
639
+ raw: line,
640
+ line: lineNum,
641
+ className: classDefMatch[1],
642
+ styles: classDefMatch[2]
643
+ };
644
+ }
645
+ if (trimmed.startsWith("@")) {
646
+ return parseDirective(trimmed, line, lineNum);
647
+ }
648
+ return parseEdgeOrNodeLine(trimmed, line, lineNum);
649
+ }
650
+ function parseDirective(trimmed, raw, lineNum) {
651
+ const sameWidthMatch = trimmed.match(/^@same-width(?:\s+(\w+))?:\s*(.+)$/);
652
+ if (sameWidthMatch) {
653
+ const strategy = sameWidthMatch[1] ?? "max";
654
+ const targets = sameWidthMatch[2].split(",").map((t) => t.trim()).filter(Boolean);
655
+ return {
656
+ type: "directive",
657
+ raw,
658
+ line: lineNum,
659
+ directiveType: "same-width",
660
+ strategy,
661
+ targets
662
+ };
663
+ }
664
+ const sameHeightMatch = trimmed.match(/^@same-height(?:\s+(\w+))?:\s*(.+)$/);
665
+ if (sameHeightMatch) {
666
+ const strategy = sameHeightMatch[1] ?? "max";
667
+ const targets = sameHeightMatch[2].split(",").map((t) => t.trim()).filter(Boolean);
668
+ return {
669
+ type: "directive",
670
+ raw,
671
+ line: lineNum,
672
+ directiveType: "same-height",
673
+ strategy,
674
+ targets
675
+ };
676
+ }
677
+ const gapMatch = trimmed.match(/^@gap\s+min=(\d+):\s*(.+)$/);
678
+ if (gapMatch) {
679
+ const min = parseInt(gapMatch[1], 10);
680
+ const targets = gapMatch[2].split(",").map((t) => t.trim()).filter(Boolean);
681
+ return {
682
+ type: "directive",
683
+ raw,
684
+ line: lineNum,
685
+ directiveType: "gap",
686
+ min,
687
+ targets
688
+ };
689
+ }
690
+ const moverMatch = trimmed.match(/^@mover\s+(\S+):\s*(.+)$/);
691
+ if (moverMatch) {
692
+ const moverId = moverMatch[1];
693
+ const targets = moverMatch[2].split(",").map((t) => t.trim()).filter(Boolean);
694
+ return {
695
+ type: "directive",
696
+ raw,
697
+ line: lineNum,
698
+ directiveType: "mover",
699
+ moverId,
700
+ targets
701
+ };
702
+ }
703
+ return null;
704
+ }
705
+ function parseEdgeOrNodeLine(trimmed, raw, lineNum) {
706
+ const firstRef = parseNodeRef(trimmed);
707
+ if (!firstRef) return null;
708
+ let cursor = trimmed.length - firstRef.rest.length;
709
+ let remaining = firstRef.rest;
710
+ const edgeMatch = matchEdgeOp(remaining);
711
+ if (!edgeMatch) {
712
+ if (!firstRef.def) {
713
+ return null;
714
+ }
715
+ return {
716
+ type: "nodeDef",
717
+ raw,
718
+ line: lineNum,
719
+ nodeId: firstRef.nodeId,
720
+ label: firstRef.def.label,
721
+ shape: firstRef.def.shape,
722
+ variant: firstRef.def.variant,
723
+ shapeOverride: firstRef.def.shapeOverride
724
+ };
725
+ }
726
+ if (edgeMatch.op === "~~~") {
727
+ return parseInvisibleEdgeChain(trimmed, firstRef, raw, lineNum);
728
+ }
729
+ return parseVisibleEdgeChain(trimmed, firstRef, raw, lineNum);
730
+ }
731
+ function parseInvisibleEdgeChain(input, firstRef, raw, lineNum) {
732
+ const segments = [];
733
+ let currentId = firstRef.nodeId;
734
+ let remaining = firstRef.rest;
735
+ while (true) {
736
+ const edgeMatch = matchEdgeOp(remaining);
737
+ if (!edgeMatch || edgeMatch.op !== "~~~") break;
738
+ remaining = edgeMatch.rest;
739
+ const nextRef = parseNodeRef(remaining);
740
+ if (!nextRef) break;
741
+ segments.push({ fromId: currentId, toId: nextRef.nodeId });
742
+ currentId = nextRef.nodeId;
743
+ remaining = nextRef.rest;
744
+ }
745
+ return { type: "invisibleEdge", raw, line: lineNum, segments };
746
+ }
747
+ function parseVisibleEdgeChain(input, firstRef, raw, lineNum) {
748
+ const segments = [];
749
+ let currentId = firstRef.nodeId;
750
+ let currentDef = firstRef.def;
751
+ let remaining = firstRef.rest;
752
+ while (true) {
753
+ const edgeMatch = matchEdgeOp(remaining);
754
+ if (!edgeMatch || edgeMatch.op === "~~~") break;
755
+ remaining = edgeMatch.rest;
756
+ let colonLabel;
757
+ const nextRef = parseNodeRef(remaining);
758
+ if (!nextRef) break;
759
+ remaining = nextRef.rest;
760
+ const colonMatch = remaining.match(/^\s*:\s*(.+?)$/);
761
+ if (colonMatch) {
762
+ colonLabel = colonMatch[1].trim();
763
+ remaining = "";
764
+ }
765
+ const seg = {
766
+ fromId: currentId,
767
+ toId: nextRef.nodeId,
768
+ edgeType: edgeMatch.op,
769
+ edgeLabel: edgeMatch.label ?? colonLabel
770
+ };
771
+ if (currentDef) {
772
+ seg.fromDef = currentDef;
773
+ }
774
+ if (nextRef.def) {
775
+ seg.toDef = nextRef.def;
776
+ }
777
+ segments.push(seg);
778
+ currentId = nextRef.nodeId;
779
+ currentDef = nextRef.def;
780
+ }
781
+ return { type: "edge", raw, line: lineNum, segments };
782
+ }
783
+ function tokenize(source) {
784
+ const lines = source.split("\n");
785
+ const tokens = [];
786
+ for (let i = 0; i < lines.length; i++) {
787
+ const token = tokenizeLine(lines[i], i + 1);
788
+ if (token) {
789
+ tokens.push(token);
790
+ }
791
+ }
792
+ return tokens;
793
+ }
794
+
795
+ // src/parser/parser.ts
796
+ function parseDrawText(source) {
797
+ const diagnostics = [];
798
+ const tokens = tokenize(source);
799
+ let rootDirection = "TD";
800
+ const nodeMap = /* @__PURE__ */ new Map();
801
+ const connectors = [];
802
+ const constraints = [];
803
+ const classDefMap = /* @__PURE__ */ new Map();
804
+ const scopeStack = [];
805
+ const groups = [];
806
+ const rootNodeIds = [];
807
+ function currentDirection() {
808
+ for (let i = scopeStack.length - 1; i >= 0; i--) {
809
+ if (scopeStack[i].direction) return scopeStack[i].direction;
810
+ }
811
+ return rootDirection;
812
+ }
813
+ function registerNode(id, label, shape, variant) {
814
+ if (!nodeMap.has(id)) {
815
+ nodeMap.set(id, { id, label, shape, variant });
816
+ }
817
+ if (scopeStack.length > 0) {
818
+ const top = scopeStack[scopeStack.length - 1];
819
+ if (!top.nodeIds.includes(id)) top.nodeIds.push(id);
820
+ } else {
821
+ if (!rootNodeIds.includes(id)) rootNodeIds.push(id);
822
+ }
823
+ }
824
+ function ensureNode(id) {
825
+ if (nodeMap.has(id)) return;
826
+ nodeMap.set(id, { id, label: id, shape: "roundedRect" });
827
+ if (scopeStack.length > 0) {
828
+ const top = scopeStack[scopeStack.length - 1];
829
+ if (!top.nodeIds.includes(id)) top.nodeIds.push(id);
830
+ } else {
831
+ if (!rootNodeIds.includes(id)) rootNodeIds.push(id);
832
+ }
833
+ }
834
+ for (const token of tokens) {
835
+ switch (token.type) {
836
+ case "comment":
837
+ break;
838
+ case "direction":
839
+ rootDirection = token.dir;
840
+ break;
841
+ case "nodeDef": {
842
+ const t = token;
843
+ registerNode(t.nodeId, t.label, t.shape, t.variant);
844
+ break;
845
+ }
846
+ case "edge": {
847
+ const t = token;
848
+ for (const seg of t.segments) {
849
+ processEdgeSegment(seg);
850
+ }
851
+ break;
852
+ }
853
+ case "invisibleEdge": {
854
+ const t = token;
855
+ for (const seg of t.segments) {
856
+ ensureNode(seg.fromId);
857
+ ensureNode(seg.toId);
858
+ }
859
+ const dir = currentDirection();
860
+ const allIds = /* @__PURE__ */ new Set();
861
+ for (const seg of t.segments) {
862
+ allIds.add(seg.fromId);
863
+ allIds.add(seg.toId);
864
+ }
865
+ constraints.push(buildAlignConstraint([...allIds], dir));
866
+ break;
867
+ }
868
+ case "subgraphStart": {
869
+ const t = token;
870
+ scopeStack.push({
871
+ id: t.subgraphId,
872
+ label: t.label,
873
+ nodeIds: []
874
+ });
875
+ break;
876
+ }
877
+ case "subgraphEnd": {
878
+ if (scopeStack.length === 0) {
879
+ diagnostics.push({
880
+ level: "warning",
881
+ message: "Unexpected 'end' without matching subgraph",
882
+ path: `line:${token.line}`
883
+ });
884
+ break;
885
+ }
886
+ const frame = scopeStack.pop();
887
+ groups.push({ frame, children: [...frame.nodeIds] });
888
+ if (scopeStack.length > 0) {
889
+ const parent = scopeStack[scopeStack.length - 1];
890
+ if (!parent.nodeIds.includes(frame.id)) parent.nodeIds.push(frame.id);
891
+ } else {
892
+ if (!rootNodeIds.includes(frame.id)) rootNodeIds.push(frame.id);
893
+ }
894
+ break;
895
+ }
896
+ case "localDirection": {
897
+ const t = token;
898
+ if (scopeStack.length > 0) {
899
+ scopeStack[scopeStack.length - 1].direction = t.dir;
900
+ } else {
901
+ rootDirection = t.dir;
902
+ }
903
+ break;
904
+ }
905
+ case "classDef": {
906
+ const t = token;
907
+ classDefMap.set(t.className, t.styles);
908
+ break;
909
+ }
910
+ case "directive": {
911
+ const t = token;
912
+ processDirective(t);
913
+ break;
914
+ }
915
+ }
916
+ }
917
+ function processEdgeSegment(seg) {
918
+ if (seg.fromDef) {
919
+ registerNode(seg.fromId, seg.fromDef.label, seg.fromDef.shape, seg.fromDef.variant);
920
+ } else {
921
+ ensureNode(seg.fromId);
922
+ }
923
+ if (seg.toDef) {
924
+ registerNode(seg.toId, seg.toDef.label, seg.toDef.shape, seg.toDef.variant);
925
+ } else {
926
+ ensureNode(seg.toId);
927
+ }
928
+ const connectorProps = {
929
+ from: seg.fromId,
930
+ to: seg.toId
931
+ };
932
+ switch (seg.edgeType) {
933
+ case "-->":
934
+ connectorProps.endArrow = "arrow";
935
+ break;
936
+ case "---":
937
+ connectorProps.startArrow = "none";
938
+ connectorProps.endArrow = "none";
939
+ break;
940
+ case "-.->":
941
+ connectorProps.strokeStyle = "dashed";
942
+ connectorProps.endArrow = "arrow";
943
+ break;
944
+ case "==>":
945
+ connectorProps.strokeWidth = 3;
946
+ connectorProps.endArrow = "arrow";
947
+ break;
948
+ }
949
+ const children = [];
950
+ if (seg.edgeLabel) {
951
+ children.push({
952
+ kind: "label",
953
+ props: { text: seg.edgeLabel }
954
+ });
955
+ }
956
+ const connector = {
957
+ kind: "connector",
958
+ props: connectorProps
959
+ };
960
+ if (children.length > 0) {
961
+ connector.children = children;
962
+ }
963
+ connectors.push(connector);
964
+ }
965
+ function processDirective(t) {
966
+ switch (t.directiveType) {
967
+ case "same-width": {
968
+ const props = { targets: t.targets };
969
+ if (t.strategy) props.strategy = t.strategy;
970
+ constraints.push({ kind: "equalizeWidth", props });
971
+ break;
972
+ }
973
+ case "same-height": {
974
+ const props = { targets: t.targets };
975
+ if (t.strategy) props.strategy = t.strategy;
976
+ constraints.push({ kind: "equalizeHeight", props });
977
+ break;
978
+ }
979
+ case "gap": {
980
+ const props = {
981
+ targets: t.targets,
982
+ gap: t.min
983
+ };
984
+ constraints.push({ kind: "minGap", props });
985
+ break;
986
+ }
987
+ case "mover": {
988
+ const props = {
989
+ targets: t.targets,
990
+ mover: t.moverId
991
+ };
992
+ constraints.push({ kind: "alignConstraint", props });
993
+ break;
994
+ }
995
+ }
996
+ }
997
+ const stackKind = rootDirection === "LR" ? "hstack" : "vstack";
998
+ const groupNodes = /* @__PURE__ */ new Map();
999
+ for (const g of groups) {
1000
+ const dir = g.frame.direction ?? rootDirection;
1001
+ const innerKind = dir === "LR" ? "hstack" : "vstack";
1002
+ const groupChildren = [];
1003
+ const innerStack = {
1004
+ kind: innerKind,
1005
+ props: { gap: 60, align: "center" },
1006
+ children: []
1007
+ };
1008
+ for (const childId of g.children) {
1009
+ const childGroup = groupNodes.get(childId);
1010
+ if (childGroup) {
1011
+ innerStack.children.push(childGroup);
1012
+ } else {
1013
+ const node = nodeMap.get(childId);
1014
+ if (node) {
1015
+ innerStack.children.push(buildBox(node));
1016
+ }
1017
+ }
1018
+ }
1019
+ groupChildren.push(innerStack);
1020
+ const groupNode = {
1021
+ kind: "group",
1022
+ id: g.frame.id,
1023
+ props: {},
1024
+ children: groupChildren
1025
+ };
1026
+ if (g.frame.label) {
1027
+ groupNode.props.label = g.frame.label;
1028
+ }
1029
+ groupNodes.set(g.frame.id, groupNode);
1030
+ }
1031
+ const rootChildren = [];
1032
+ for (const id of rootNodeIds) {
1033
+ const groupNode = groupNodes.get(id);
1034
+ if (groupNode) {
1035
+ rootChildren.push(groupNode);
1036
+ } else {
1037
+ const node = nodeMap.get(id);
1038
+ if (node) {
1039
+ rootChildren.push(buildBox(node));
1040
+ }
1041
+ }
1042
+ }
1043
+ const rootStack = {
1044
+ kind: stackKind,
1045
+ props: { gap: 60, align: "center" },
1046
+ children: rootChildren
1047
+ };
1048
+ const documentChildren = [rootStack, ...connectors, ...constraints];
1049
+ const document = {
1050
+ kind: "document",
1051
+ props: { padding: 20 },
1052
+ children: documentChildren
1053
+ };
1054
+ return { ir: document, diagnostics };
1055
+ }
1056
+ function buildBox(node) {
1057
+ const props = { shape: node.shape };
1058
+ if (node.variant) {
1059
+ props.variant = node.variant;
1060
+ }
1061
+ return {
1062
+ kind: "box",
1063
+ id: node.id,
1064
+ props,
1065
+ children: [
1066
+ { kind: "text", props: { text: node.label } }
1067
+ ]
1068
+ };
1069
+ }
1070
+ function buildAlignConstraint(targets, direction) {
1071
+ const axis = direction === "TD" ? "vertical" : "horizontal";
1072
+ return {
1073
+ kind: "alignConstraint",
1074
+ props: {
1075
+ targets,
1076
+ axis,
1077
+ align: "center"
1078
+ }
1079
+ };
1080
+ }
1081
+
1082
+ // src/compiler/compiler.ts
1083
+ import ts from "typescript";
1084
+
1085
+ // src/graph/rule-engine.ts
1086
+ import { load } from "js-yaml";
1087
+
1088
+ // src/graph/graph-helpers.ts
1089
+ function slotId(nodeId, kind) {
1090
+ return `${nodeId}.${kind}`;
1091
+ }
1092
+ function ensureSlot(graph, nodeId, kind, value) {
1093
+ const id = slotId(nodeId, kind);
1094
+ let slot = graph.slots.get(id);
1095
+ if (!slot) {
1096
+ slot = { id, value, nodeId, slotKind: kind };
1097
+ graph.slots.set(id, slot);
1098
+ } else if (value !== void 0 && slot.value === void 0) {
1099
+ slot.value = value;
1100
+ }
1101
+ return slot;
1102
+ }
1103
+ function addCandidate(graph, targetSlotId, value, source, priority, operationId) {
1104
+ const candidate = { targetSlotId, value, source, priority, operationId };
1105
+ graph.candidates.push(candidate);
1106
+ let bySlot = graph.candidatesBySlot.get(targetSlotId);
1107
+ if (!bySlot) {
1108
+ bySlot = [];
1109
+ graph.candidatesBySlot.set(targetSlotId, bySlot);
1110
+ }
1111
+ bySlot.push(candidate);
1112
+ if (operationId) {
1113
+ let byOp = graph.candidatesByOperation.get(operationId);
1114
+ if (!byOp) {
1115
+ byOp = [];
1116
+ graph.candidatesByOperation.set(operationId, byOp);
1117
+ }
1118
+ byOp.push(candidate);
1119
+ }
1120
+ return candidate;
1121
+ }
1122
+ function addOperation(graph, id, op, inputs, outputs, compute) {
1123
+ const operation = { id, op, inputs, outputs, compute };
1124
+ graph.operations.set(id, operation);
1125
+ for (const inputSlotId of inputs) {
1126
+ graph.dependencies.push({ fromSlotId: inputSlotId, toOperationId: id });
1127
+ }
1128
+ return operation;
1129
+ }
1130
+ function addResolver(graph, targetSlotId, policy) {
1131
+ if (!graph.resolvers.has(targetSlotId)) {
1132
+ graph.resolvers.set(targetSlotId, { slotId: targetSlotId, policy });
1133
+ }
1134
+ }
1135
+ function getNumericProp(node, prop, fallback) {
1136
+ const val = node.props[prop];
1137
+ if (typeof val === "number") return val;
1138
+ if (typeof val === "string") {
1139
+ const n = parseFloat(val);
1140
+ if (!isNaN(n)) return n;
1141
+ }
1142
+ return fallback;
1143
+ }
1144
+ function getSizeProp(node, prop) {
1145
+ const val = node.props[prop];
1146
+ if (typeof val === "number") return val;
1147
+ if (val === "wrap") return "wrap";
1148
+ if (val === "fill") return "fill";
1149
+ if (typeof val === "string") {
1150
+ const n = parseFloat(val);
1151
+ if (!isNaN(n)) return n;
1152
+ }
1153
+ return "wrap";
1154
+ }
1155
+ function isConstraintKind(kind) {
1156
+ return kind === "alignConstraint" || kind === "equalizeWidth" || kind === "equalizeHeight" || kind === "minGap" || kind === "keepInsideConstraint" || kind === "orderConstraint";
1157
+ }
1158
+ function registerSizeSlots(node, graph, parentId) {
1159
+ const id = node.id;
1160
+ const widthIntent = getSizeProp(node, "width");
1161
+ const heightIntent = getSizeProp(node, "height");
1162
+ ensureSlot(graph, id, "resolvedWidth");
1163
+ ensureSlot(graph, id, "resolvedHeight");
1164
+ ensureSlot(graph, id, "localX");
1165
+ ensureSlot(graph, id, "localY");
1166
+ if (typeof widthIntent === "number") {
1167
+ ensureSlot(graph, id, "contentWidth", widthIntent);
1168
+ addCandidate(graph, slotId(id, "contentWidth"), widthIntent, `${node.kind}(${id}).explicit`, "required");
1169
+ addCandidate(graph, slotId(id, "resolvedWidth"), widthIntent, `${node.kind}(${id}).explicit`, "normal");
1170
+ } else if (widthIntent === "fill" && parentId) {
1171
+ ensureSlot(graph, parentId, "availableWidth");
1172
+ }
1173
+ if (typeof heightIntent === "number") {
1174
+ ensureSlot(graph, id, "contentHeight", heightIntent);
1175
+ addCandidate(graph, slotId(id, "contentHeight"), heightIntent, `${node.kind}(${id}).explicit`, "required");
1176
+ addCandidate(graph, slotId(id, "resolvedHeight"), heightIntent, `${node.kind}(${id}).explicit`, "normal");
1177
+ } else if (heightIntent === "fill" && parentId) {
1178
+ ensureSlot(graph, parentId, "availableHeight");
1179
+ }
1180
+ }
1181
+
1182
+ // src/graph/ops.ts
1183
+ function opSum(inputs) {
1184
+ return [inputs.reduce((a, b) => a + b, 0)];
1185
+ }
1186
+ function opMax(inputs) {
1187
+ if (inputs.length === 0) return [0];
1188
+ return [Math.max(...inputs)];
1189
+ }
1190
+ function opClamp(value, min, max) {
1191
+ return Math.min(Math.max(value, min), max);
1192
+ }
1193
+ function opConstant(value) {
1194
+ return () => [value];
1195
+ }
1196
+ var SimpleTextMeasurer = class {
1197
+ measure(text, fontSize, maxWidth) {
1198
+ const charWidth = 0.6 * fontSize;
1199
+ const lineHeight = 1.4 * fontSize;
1200
+ if (!text) {
1201
+ return { width: 0, height: lineHeight, lines: [""] };
1202
+ }
1203
+ const lines = wrapLines(text, maxWidth, (t) => t.length * charWidth);
1204
+ const width = Math.max(...lines.map((l) => l.length * charWidth));
1205
+ const height = lines.length * lineHeight;
1206
+ return { width, height, lines };
1207
+ }
1208
+ };
1209
+ function wrapLines(text, maxWidth, measureFn) {
1210
+ const words = text.split(/\s+/);
1211
+ const lines = [];
1212
+ if (maxWidth !== void 0 && maxWidth > 0) {
1213
+ let currentLine = "";
1214
+ for (const word of words) {
1215
+ const candidate = currentLine ? `${currentLine} ${word}` : word;
1216
+ if (measureFn(candidate) > maxWidth && currentLine) {
1217
+ lines.push(currentLine);
1218
+ currentLine = word;
1219
+ } else {
1220
+ currentLine = candidate;
1221
+ }
1222
+ }
1223
+ if (currentLine) lines.push(currentLine);
1224
+ } else {
1225
+ lines.push(text);
1226
+ }
1227
+ return lines;
1228
+ }
1229
+
1230
+ // src/graph/rule-engine.ts
1231
+ var CONSTRAINT_RULES_YAML = `
1232
+ # Constraint Rules \u2014 declarative definition of constraint behavior
1233
+ #
1234
+ # No code here. Each constraint declares which slots it references, which slots
1235
+ # receive emitted candidates, and which operation computes values.
1236
+ #
1237
+ # rule-engine.ts reads this definition and generically generates graph primitives.
1238
+
1239
+ alignConstraint:
1240
+ operation: world_align
1241
+ description: "Align movers to reference absolute coordinates (cross-container)"
1242
+ axis_mapping:
1243
+ horizontal:
1244
+ world_slot: worldY
1245
+ local_slot: localY
1246
+ size_slot: resolvedHeight
1247
+ vertical:
1248
+ world_slot: worldX
1249
+ local_slot: localX
1250
+ size_slot: resolvedWidth
1251
+ reference: "targets[0]"
1252
+ movers: "mover ?? targets[1:]"
1253
+ priority_from: strength
1254
+ default_priority: required
1255
+
1256
+ equalizeWidth:
1257
+ operation: equalize
1258
+ description: "Equalize width across all targets per strategy"
1259
+ slot: resolvedWidth
1260
+ content_slot: contentWidth
1261
+ targets: "targets"
1262
+ strategy_from: strategy
1263
+ default_strategy: max
1264
+ priority_from: strength
1265
+ default_priority: strong
1266
+
1267
+ equalizeHeight:
1268
+ operation: equalize
1269
+ description: "Equalize height across all targets per strategy"
1270
+ slot: resolvedHeight
1271
+ content_slot: contentHeight
1272
+ targets: "targets"
1273
+ strategy_from: strategy
1274
+ default_strategy: max
1275
+ priority_from: strength
1276
+ default_priority: strong
1277
+
1278
+ orderConstraint:
1279
+ operation: enforce_order
1280
+ description: "Emit candidates so slot values ascend in targets order"
1281
+ axis_to_slot:
1282
+ horizontal: localX
1283
+ vertical: localY
1284
+ axis_to_size_slot:
1285
+ horizontal: resolvedWidth
1286
+ vertical: resolvedHeight
1287
+ targets: "targets"
1288
+ priority_from: strength
1289
+ default_priority: strong
1290
+ resolver_policy: highestPriority
1291
+
1292
+ minGap:
1293
+ operation: enforce_min_gap
1294
+ description: "Guarantee minimum gap between targets"
1295
+ axis_to_position_slot:
1296
+ horizontal: localX
1297
+ vertical: localY
1298
+ axis_to_size_slot:
1299
+ horizontal: resolvedWidth
1300
+ vertical: resolvedHeight
1301
+ min_gap_from: gap
1302
+ targets: "targets"
1303
+ priority_from: strength
1304
+ default_priority: strong
1305
+ resolver_policy: highestPriority
1306
+
1307
+ keepInsideConstraint:
1308
+ operation: clamp_inside
1309
+ description: "Clamp positions so contents stay inside container rect"
1310
+ container: "targets[0]"
1311
+ contents: "targets[1:]"
1312
+ position_slots: [localX, localY]
1313
+ size_slots: [resolvedWidth, resolvedHeight]
1314
+ priority_from: strength
1315
+ default_priority: strong
1316
+ resolver_policy: highestPriority
1317
+ `;
1318
+ var LAYOUT_RULES_YAML = `
1319
+ # Layout Rules \u2014 graph fragment generation rules for layout elements
1320
+ #
1321
+ # Declaratively defines which slots each layout element generates and which
1322
+ # operations compute child position and size.
1323
+ #
1324
+ # The stack rule treats vstack/hstack uniformly via axis parameters.
1325
+
1326
+ # ---------------------------------------------------------------------------
1327
+ # style_props \u2014 renderer-only properties (kind \u2192 props[])
1328
+ # Separate from layout params; the renderer reads these transparently as styles.
1329
+ # The compiler unions this list with layout params to detect unknown properties.
1330
+ # ---------------------------------------------------------------------------
1331
+ style_props:
1332
+ _common: [variant, fill, stroke, strokeWidth, strokeStyle, strokeDasharray, opacity, radius, shape, foreground, fontFamily, background]
1333
+ document: [fontFamily, background]
1334
+ box: [fill, stroke, strokeWidth, strokeStyle, strokeDasharray, opacity, radius, shape]
1335
+ text: [foreground, fontFamily]
1336
+ label: [foreground, fontFamily]
1337
+ connector: [stroke, strokeWidth, strokeStyle, strokeDasharray, opacity, from, to, fromAnchor, toAnchor, route, cornerRadius, startArrow, endArrow, arrowSize]
1338
+ image: [src, fit]
1339
+ icon: [name, foreground]
1340
+ svgAsset: [src]
1341
+ table: [borderRadius, stroke, strokeWidth, background]
1342
+ row: [fill]
1343
+ cell: [fill, stroke, strokeWidth]
1344
+ vstack: [fill, stroke, strokeWidth, strokeStyle, opacity]
1345
+ hstack: [fill, stroke, strokeWidth, strokeStyle, opacity]
1346
+ group: []
1347
+ overlay: []
1348
+ spacer: []
1349
+ divider: [stroke, strokeWidth]
1350
+
1351
+ document:
1352
+ type: root
1353
+ description: "Root node. Defines viewBox"
1354
+ params:
1355
+ width: { from: width, default: 800 }
1356
+ height: { from: height, default: 600 }
1357
+ padding: { from: padding, default: 0 }
1358
+ slots:
1359
+ self: [resolvedWidth, resolvedHeight, availableWidth, availableHeight]
1360
+ operations:
1361
+ - type: set_explicit_size
1362
+
1363
+ stack:
1364
+ type: stack
1365
+ description: "Place children sequentially along the main axis"
1366
+ applies_to:
1367
+ vstack:
1368
+ main_slot: localY
1369
+ cross_slot: localX
1370
+ main_size: resolvedHeight
1371
+ cross_size: resolvedWidth
1372
+ content_main_size: contentHeight
1373
+ content_cross_size: contentWidth
1374
+ hstack:
1375
+ main_slot: localX
1376
+ cross_slot: localY
1377
+ main_size: resolvedWidth
1378
+ cross_size: resolvedHeight
1379
+ content_main_size: contentWidth
1380
+ content_cross_size: contentHeight
1381
+ row:
1382
+ main_slot: localX
1383
+ cross_slot: localY
1384
+ main_size: resolvedWidth
1385
+ cross_size: resolvedHeight
1386
+ content_main_size: contentWidth
1387
+ content_cross_size: contentHeight
1388
+ params:
1389
+ gap: { from: gap, default: 0 }
1390
+ padding: { from: padding, default: 0 }
1391
+ align: { from: align, default: start }
1392
+ width: { from: width, default: wrap }
1393
+ height: { from: height, default: wrap }
1394
+ slots:
1395
+ self: [resolvedWidth, resolvedHeight, localX, localY, contentWidth, contentHeight]
1396
+ children: [resolvedWidth, resolvedHeight, localX, localY]
1397
+ children_filter: non_constraint
1398
+ operations:
1399
+ - type: sequential_position
1400
+ description: "Place children along main axis (padding offset + gap between)"
1401
+ input: "children.main_size"
1402
+ output: "children.main_slot"
1403
+ uses: [gap, padding]
1404
+ - type: cross_axis_position
1405
+ description: "Offset cross-axis position by padding"
1406
+ output: "children.cross_slot"
1407
+ uses: [padding, align]
1408
+ - type: content_size_sum
1409
+ description: "Main-axis content size = children sizes + gaps"
1410
+ input: "children.main_size"
1411
+ output: "self.content_main_size"
1412
+ reduce: sum_with_gaps
1413
+ uses: [gap]
1414
+ - type: content_size_max
1415
+ description: "Cross-axis content size = max of children"
1416
+ input: "children.cross_size"
1417
+ output: "self.content_cross_size"
1418
+ reduce: max
1419
+ - type: wrap_main_size
1420
+ description: "wrap: contentSize + 2*padding"
1421
+ input: "self.content_main_size"
1422
+ output: "self.main_size"
1423
+ uses: [padding]
1424
+ - type: wrap_cross_size
1425
+ description: "wrap: max child + 2*padding"
1426
+ input: "self.content_cross_size"
1427
+ output: "self.cross_size"
1428
+ uses: [padding]
1429
+
1430
+ box:
1431
+ type: container
1432
+ description: "Container. Size resolution based on child content"
1433
+ params:
1434
+ padding: { from: padding, default: 0 }
1435
+ width: { from: width, default: wrap }
1436
+ height: { from: height, default: wrap }
1437
+ slots:
1438
+ self: [resolvedWidth, resolvedHeight, localX, localY, contentWidth, contentHeight]
1439
+ children: [resolvedWidth, resolvedHeight, localX, localY]
1440
+ size_modes:
1441
+ explicit:
1442
+ operation: set_explicit_size
1443
+ priority: strong
1444
+ wrap:
1445
+ operation: content_plus_padding
1446
+ priority: weak
1447
+ input: "self.contentSize"
1448
+ uses: [padding]
1449
+ fill:
1450
+ operation: copy_parent_available
1451
+ priority: normal
1452
+ input: "parent.availableSize"
1453
+ content_size:
1454
+ description: "Content size = max of child sizes"
1455
+ input: "children.resolvedSize"
1456
+ reduce: max
1457
+
1458
+ text:
1459
+ type: text_measurement
1460
+ description: "Intrinsic size from text measurement"
1461
+ params:
1462
+ text: { from: text, default: "" }
1463
+ fontSize: { from: fontSize, default: 14 }
1464
+ fontWeight: { from: fontWeight, default: normal }
1465
+ textAlign: { from: textAlign, default: center }
1466
+ padding: { from: padding, default: 0 }
1467
+ slots:
1468
+ self: [resolvedWidth, resolvedHeight, localX, localY, intrinsicWidth, intrinsicHeight, contentWidth, contentHeight, textBaseline, textX, textAnchor]
1469
+ operations:
1470
+ - type: measure_text
1471
+ output_width: intrinsicWidth
1472
+ output_height: intrinsicHeight
1473
+ uses: [text, fontSize, padding]
1474
+ - type: set_from_intrinsic
1475
+ description: "resolved = intrinsic (text always uses intrinsic size)"
1476
+ priority: normal
1477
+ - type: text_layout
1478
+ description: "Compute text placement (baseline, X, anchor)"
1479
+ uses: [fontSize, textAlign]
1480
+
1481
+ label:
1482
+ type: text_measurement
1483
+ inherits: text
1484
+
1485
+ group:
1486
+ type: container
1487
+ description: "Structural group. Same size resolution as box"
1488
+ inherits: box
1489
+
1490
+ overlay:
1491
+ type: overlay
1492
+ description: "Z-stack. Place all children at (0,0)"
1493
+ params:
1494
+ padding: { from: padding, default: 0 }
1495
+ slots:
1496
+ self: [resolvedWidth, resolvedHeight, localX, localY, contentWidth, contentHeight]
1497
+ children: [resolvedWidth, resolvedHeight, localX, localY]
1498
+ operations:
1499
+ - type: set_children_origin
1500
+ description: "Place all children at (padding, padding)"
1501
+ output: "children.[localX, localY]"
1502
+ uses: [padding]
1503
+ - type: content_size_max
1504
+ description: "Content size = max of children"
1505
+ input: "children.[resolvedWidth, resolvedHeight]"
1506
+ output: "self.[contentWidth, contentHeight]"
1507
+ reduce: max
1508
+
1509
+ connector:
1510
+ type: connector
1511
+ description: "Geometry consumer. Compute path from from/to node worldRect"
1512
+ params:
1513
+ from: { from: from }
1514
+ to: { from: to }
1515
+ route: { from: route, default: straight }
1516
+ slots:
1517
+ self: [resolvedWidth, resolvedHeight, localX, localY, connectorPath]
1518
+ depends_on: ["from.worldRect", "to.worldRect"]
1519
+ operations:
1520
+ - type: compute_connector_path
1521
+ input: ["from.worldRect", "to.worldRect"]
1522
+ output: connectorPath
1523
+ uses: [route]
1524
+
1525
+ spacer:
1526
+ type: fixed_size
1527
+ description: "Fixed-size blank space"
1528
+ params:
1529
+ width: { from: width, default: 0 }
1530
+ height: { from: height, default: 0 }
1531
+
1532
+ divider:
1533
+ type: fixed_size
1534
+ description: "Separator line"
1535
+ params:
1536
+ axis: { from: axis, default: horizontal }
1537
+ size_from_axis:
1538
+ horizontal: { width: fill, height: 1 }
1539
+ vertical: { width: 1, height: fill }
1540
+
1541
+ image:
1542
+ type: fixed_size
1543
+ params:
1544
+ width: { from: width, default: 100 }
1545
+ height: { from: height, default: 100 }
1546
+
1547
+ icon:
1548
+ type: fixed_size
1549
+ params:
1550
+ width: { default: 24 }
1551
+ height: { default: 24 }
1552
+
1553
+ svgAsset:
1554
+ type: fixed_size
1555
+ params:
1556
+ width: { from: width, default: 100 }
1557
+ height: { from: height, default: 100 }
1558
+
1559
+ # ---------------------------------------------------------------------------
1560
+ # Table \u2014 layout composed from grid rules
1561
+ #
1562
+ # No dedicated table algorithm. Everything is expressed via generic primitives:
1563
+ # 1. Row vertical stacking \u2192 grid handler (sequential_position)
1564
+ # 2. Cell horizontal stacking \u2192 stack rule (row = hstack)
1565
+ # 3. Column width equalization \u2192 grid handler (column equalization)
1566
+ # 4. Row height equalization \u2192 grid handler (row equalization)
1567
+ # 5. In-cell placement \u2192 container rule (colAlign \xD7 rowAlign)
1568
+ # ---------------------------------------------------------------------------
1569
+
1570
+ table:
1571
+ type: grid
1572
+ description: "Grid layout. Column/row equalization via implicit constraints"
1573
+ params:
1574
+ padding: { from: padding, default: 0 }
1575
+ gap: { from: gap, default: 0 }
1576
+ grid:
1577
+ row_kind: row
1578
+ cell_kind: cell
1579
+ row_axis: vertical
1580
+ equalize_columns: width
1581
+ equalize_rows: height
1582
+
1583
+ cell:
1584
+ type: container
1585
+ description: "Table cell. Content placement via colAlign/rowAlign"
1586
+ params:
1587
+ padding: { from: padding, default: 4 }
1588
+ colAlign: { from: colAlign, default: left }
1589
+ rowAlign: { from: rowAlign, default: top }
1590
+ `;
1591
+ var constraintRules = load(CONSTRAINT_RULES_YAML);
1592
+ var layoutRules = load(LAYOUT_RULES_YAML);
1593
+ var UNIVERSAL_PROPS = /* @__PURE__ */ new Set(["id", "geometry"]);
1594
+ function buildAllowedPropsMap() {
1595
+ const map = /* @__PURE__ */ new Map();
1596
+ const stylePropsSection = layoutRules.style_props;
1597
+ const commonStyles = stylePropsSection?._common ?? [];
1598
+ for (const [ruleKey, rule] of Object.entries(layoutRules)) {
1599
+ if (ruleKey === "style_props") continue;
1600
+ const paramNames = rule.params ? Object.entries(rule.params).map(([key, p]) => p.from ?? key) : [];
1601
+ const kindStyleProps = stylePropsSection?.[ruleKey] ?? [];
1602
+ if (rule.applies_to) {
1603
+ for (const subKind of Object.keys(rule.applies_to)) {
1604
+ const set = /* @__PURE__ */ new Set([...UNIVERSAL_PROPS, ...paramNames, ...kindStyleProps, ...commonStyles]);
1605
+ const subStyleProps = stylePropsSection?.[subKind] ?? [];
1606
+ for (const p of subStyleProps) set.add(p);
1607
+ map.set(subKind, set);
1608
+ }
1609
+ } else {
1610
+ const inheritsFrom = rule.inherits ? map.get(rule.inherits) : void 0;
1611
+ const set = /* @__PURE__ */ new Set([
1612
+ ...UNIVERSAL_PROPS,
1613
+ ...paramNames,
1614
+ ...kindStyleProps,
1615
+ ...commonStyles,
1616
+ ...inheritsFrom ?? []
1617
+ ]);
1618
+ map.set(ruleKey, set);
1619
+ }
1620
+ }
1621
+ for (const [constraintKind, rule] of Object.entries(constraintRules)) {
1622
+ const props = new Set(UNIVERSAL_PROPS);
1623
+ props.add("targets");
1624
+ if (rule.axis_mapping || rule.axis_to_slot || rule.axis_to_position_slot) props.add("axis");
1625
+ if (rule.priority_from) props.add(rule.priority_from);
1626
+ if (rule.strategy_from) props.add(rule.strategy_from);
1627
+ if (rule.min_gap_from) props.add(rule.min_gap_from);
1628
+ if (rule.reference || rule.movers) props.add("anchor");
1629
+ if (rule.operation === "world_align") props.add("align");
1630
+ map.set(constraintKind, props);
1631
+ }
1632
+ return map;
1633
+ }
1634
+ var _allowedPropsMap;
1635
+ function getAllowedProps(kind) {
1636
+ if (!_allowedPropsMap) _allowedPropsMap = buildAllowedPropsMap();
1637
+ return _allowedPropsMap.get(kind);
1638
+ }
1639
+ var MAIN_AXIS_KINDS = {
1640
+ vstack: "localY",
1641
+ hstack: "localX",
1642
+ row: "localX"
1643
+ };
1644
+ function baseMainSlot(mainSlot) {
1645
+ return mainSlot === "localY" ? "baseLocalY" : "baseLocalX";
1646
+ }
1647
+ function suffixShiftSlotId(stackId, index) {
1648
+ return slotId(stackId, `suffixShift.${index}`);
1649
+ }
1650
+ function stackMainAxisContext(targetId, localSlot, graph) {
1651
+ const parentId = graph.parentMap.get(targetId);
1652
+ if (!parentId) return null;
1653
+ const parentKind = graph.nodeKindMap.get(parentId);
1654
+ if (!parentKind) return null;
1655
+ const mainAxis = MAIN_AXIS_KINDS[parentKind];
1656
+ if (!mainAxis || mainAxis !== localSlot) return null;
1657
+ const order = graph.childrenOrderMap.get(parentId);
1658
+ if (!order) return null;
1659
+ const index = order.indexOf(targetId);
1660
+ if (index < 0) return null;
1661
+ return { stackId: parentId, index };
1662
+ }
1663
+ function pickReferenceTarget(targets, localSlot, graph, anchorProp, moverProp) {
1664
+ if (anchorProp) {
1665
+ return {
1666
+ reference: anchorProp,
1667
+ movers: targets.filter((t) => t !== anchorProp)
1668
+ };
1669
+ }
1670
+ if (moverProp) {
1671
+ const reference2 = targets.find((t) => t !== moverProp) ?? targets[0];
1672
+ return { reference: reference2, movers: [moverProp] };
1673
+ }
1674
+ const scored = targets.map((t) => ({ id: t, score: computeFreedomScore(t, localSlot, graph) }));
1675
+ const allMainAxis = scored.every((s) => s.score === 0);
1676
+ if (allMainAxis) {
1677
+ const stackParents = new Set(
1678
+ targets.map((t) => graph.parentMap.get(t)).filter((p) => p !== void 0)
1679
+ );
1680
+ if (stackParents.size === 1) {
1681
+ const stackId = [...stackParents][0];
1682
+ const order = graph.childrenOrderMap.get(stackId);
1683
+ if (order) {
1684
+ let maxIdx = -1;
1685
+ let reference2 = targets[0];
1686
+ for (const t of targets) {
1687
+ const idx = order.indexOf(t);
1688
+ if (idx > maxIdx) {
1689
+ maxIdx = idx;
1690
+ reference2 = t;
1691
+ }
1692
+ }
1693
+ return { reference: reference2, movers: targets.filter((t) => t !== reference2) };
1694
+ }
1695
+ }
1696
+ }
1697
+ scored.sort((a, b) => a.score - b.score);
1698
+ const reference = scored[0].id;
1699
+ return { reference, movers: scored.slice(1).map((s) => s.id) };
1700
+ }
1701
+ function computeFreedomScore(targetId, localSlot, graph) {
1702
+ const parentId = graph.parentMap.get(targetId);
1703
+ if (!parentId) return 4;
1704
+ const parentKind = graph.nodeKindMap.get(parentId);
1705
+ if (!parentKind) return 4;
1706
+ if (parentKind === "document") return 4;
1707
+ if (parentKind === "absolute") return 3;
1708
+ const mainAxis = MAIN_AXIS_KINDS[parentKind];
1709
+ if (mainAxis) {
1710
+ return mainAxis === localSlot ? 0 : 2;
1711
+ }
1712
+ if (parentKind === "table" || parentKind === "cell" || parentKind === "grid") return 0;
1713
+ if (parentKind === "overlay") return 1;
1714
+ return 2;
1715
+ }
1716
+ function describeFreedomContext(targetId, localSlot, graph) {
1717
+ const parentId = graph.parentMap.get(targetId);
1718
+ if (!parentId) return "root child";
1719
+ const parentKind = graph.nodeKindMap.get(parentId) ?? "unknown";
1720
+ const mainAxis = MAIN_AXIS_KINDS[parentKind];
1721
+ if (mainAxis && mainAxis === localSlot) {
1722
+ return `main-axis child of ${parentKind}(${parentId})`;
1723
+ }
1724
+ if (mainAxis && mainAxis !== localSlot) {
1725
+ return `cross-axis child of ${parentKind}(${parentId})`;
1726
+ }
1727
+ return `child of ${parentKind}(${parentId})`;
1728
+ }
1729
+ function collectPathLocalSlots(fromId, toAncestorId, localSlotKind, graph) {
1730
+ const slots = [];
1731
+ let current = fromId;
1732
+ while (current !== toAncestorId) {
1733
+ ensureSlot(graph, current, localSlotKind);
1734
+ slots.push(slotId(current, localSlotKind));
1735
+ const parent = graph.parentMap.get(current);
1736
+ if (!parent) break;
1737
+ current = parent;
1738
+ }
1739
+ return slots;
1740
+ }
1741
+ function applyWorldAlign(rule, node, graph) {
1742
+ const targets = node.props.targets;
1743
+ if (!targets || targets.length < 2) return;
1744
+ const axis = node.props.axis ?? "horizontal";
1745
+ const alignMode = node.props.align ?? "start";
1746
+ const strength = node.props.strength ?? rule.default_priority;
1747
+ const mapping = rule.axis_mapping;
1748
+ if (!mapping) return;
1749
+ const axisMap = mapping[axis];
1750
+ if (!axisMap) return;
1751
+ const { world_slot: worldSlot, local_slot: localSlot, size_slot: sizeSlot } = axisMap;
1752
+ let reference;
1753
+ let movers;
1754
+ const anchorProp = node.props.anchor;
1755
+ const moverProp = node.props.mover;
1756
+ if (anchorProp) {
1757
+ if (!targets.includes(anchorProp)) {
1758
+ graph.diagnostics.push({
1759
+ level: "error",
1760
+ message: `AlignConstraint(${node.id}): anchor="${anchorProp}" is not in targets [${targets.join(", ")}].`,
1761
+ path: node.id ?? ""
1762
+ });
1763
+ return;
1764
+ }
1765
+ }
1766
+ ({ reference, movers } = pickReferenceTarget(targets, localSlot, graph, anchorProp, moverProp));
1767
+ if (moverProp) {
1768
+ const score = computeFreedomScore(moverProp, localSlot, graph);
1769
+ if (score <= 0) {
1770
+ graph.diagnostics.push({
1771
+ level: "warning",
1772
+ message: `AlignConstraint(${node.id}): mover="${moverProp}" is ${describeFreedomContext(moverProp, localSlot, graph)}. Aligning may break sibling layout.`,
1773
+ path: node.id ?? ""
1774
+ });
1775
+ }
1776
+ } else if (!anchorProp) {
1777
+ const scored = targets.map((t) => ({ id: t, score: computeFreedomScore(t, localSlot, graph) }));
1778
+ const allLow = scored.every((s) => s.score <= 0);
1779
+ const allMainAxis = scored.every((s) => s.score === 0);
1780
+ const sameStackAllMain = allMainAxis && new Set(targets.map((t) => graph.parentMap.get(t))).size === 1;
1781
+ if (allLow && !sameStackAllMain) {
1782
+ graph.diagnostics.push({
1783
+ level: "warning",
1784
+ message: `AlignConstraint(${node.id}): all targets have low freedom on ${localSlot}. Alignment may break sibling layout. ` + targets.map((t) => `${t} is ${describeFreedomContext(t, localSlot, graph)}`).join("; ") + ".",
1785
+ path: node.id ?? ""
1786
+ });
1787
+ }
1788
+ }
1789
+ const alignRefs = node.props.alignRefs ?? {};
1790
+ const refMeasure = alignRefs[reference] ?? reference;
1791
+ ensureSlot(graph, refMeasure, worldSlot);
1792
+ if (alignMode !== "start") {
1793
+ ensureSlot(graph, refMeasure, sizeSlot);
1794
+ }
1795
+ for (const moverId of movers) {
1796
+ const moverParentId = graph.parentMap.get(moverId);
1797
+ if (!moverParentId) continue;
1798
+ const moverMeasure = alignRefs[moverId] ?? moverId;
1799
+ const stackCtx = stackMainAxisContext(moverId, localSlot, graph);
1800
+ const refStackCtx = stackMainAxisContext(reference, localSlot, graph);
1801
+ const hasMoverPath = moverMeasure !== moverId;
1802
+ const useSuffixShift = stackCtx !== null && !hasMoverPath && (refStackCtx === null || refStackCtx.stackId === stackCtx.stackId || anchorProp !== void 0);
1803
+ if (useSuffixShift && stackCtx) {
1804
+ applySuffixShiftAlign(
1805
+ graph,
1806
+ node,
1807
+ moverId,
1808
+ stackCtx.stackId,
1809
+ stackCtx.index,
1810
+ refMeasure,
1811
+ moverMeasure,
1812
+ alignMode,
1813
+ strength,
1814
+ worldSlot,
1815
+ baseMainSlot(localSlot),
1816
+ sizeSlot,
1817
+ localSlot
1818
+ );
1819
+ continue;
1820
+ }
1821
+ ensureSlot(graph, moverParentId, worldSlot);
1822
+ ensureSlot(graph, moverId, localSlot);
1823
+ const opId = `op.${node.kind}.${node.id}.${moverId}.${localSlot}`;
1824
+ const outputSlotId = slotId(moverId, localSlot);
1825
+ const inputSlots = [
1826
+ slotId(refMeasure, worldSlot),
1827
+ slotId(moverParentId, worldSlot)
1828
+ ];
1829
+ const localSlotKind = localSlot;
1830
+ if (alignMode === "center" || alignMode === "end") {
1831
+ ensureSlot(graph, refMeasure, sizeSlot);
1832
+ inputSlots.push(slotId(refMeasure, sizeSlot));
1833
+ if (moverMeasure === moverId) {
1834
+ ensureSlot(graph, moverId, sizeSlot);
1835
+ inputSlots.push(slotId(moverId, sizeSlot));
1836
+ } else {
1837
+ const pathLocalYSlots = collectPathLocalSlots(
1838
+ moverMeasure,
1839
+ moverId,
1840
+ localSlotKind,
1841
+ graph
1842
+ );
1843
+ ensureSlot(graph, moverMeasure, sizeSlot);
1844
+ for (const s of pathLocalYSlots) inputSlots.push(s);
1845
+ inputSlots.push(slotId(moverMeasure, sizeSlot));
1846
+ }
1847
+ }
1848
+ addCandidate(graph, outputSlotId, void 0, `${node.kind}(${node.id})`, strength, opId);
1849
+ addOperation(graph, opId, "alignDelta", inputSlots, [outputSlotId], (inputs) => {
1850
+ const refWorld = inputs[0] ?? 0;
1851
+ const moverParentWorld = inputs[1] ?? 0;
1852
+ if (alignMode === "center") {
1853
+ const refSize = inputs[2] ?? 0;
1854
+ const refCenter = refWorld + refSize / 2;
1855
+ if (!hasMoverPath) {
1856
+ const moverSize = inputs[3] ?? 0;
1857
+ return [refCenter - moverParentWorld - moverSize / 2];
1858
+ }
1859
+ const pathSlots = inputs.slice(3, -1);
1860
+ const moverMeasureSize = inputs[inputs.length - 1] ?? 0;
1861
+ const internalOffset = pathSlots.reduce((sum, v) => sum + (v ?? 0), 0) + moverMeasureSize / 2;
1862
+ return [refCenter - moverParentWorld - internalOffset];
1863
+ }
1864
+ if (alignMode === "end") {
1865
+ const refSize = inputs[2] ?? 0;
1866
+ const refEnd = refWorld + refSize;
1867
+ if (!hasMoverPath) {
1868
+ const moverSize = inputs[3] ?? 0;
1869
+ return [refEnd - moverParentWorld - moverSize];
1870
+ }
1871
+ const pathSlots = inputs.slice(3, -1);
1872
+ const moverMeasureSize = inputs[inputs.length - 1] ?? 0;
1873
+ const internalOffset = pathSlots.reduce((sum, v) => sum + (v ?? 0), 0) + moverMeasureSize;
1874
+ return [refEnd - moverParentWorld - internalOffset];
1875
+ }
1876
+ return [refWorld - moverParentWorld];
1877
+ });
1878
+ }
1879
+ }
1880
+ function applySuffixShiftAlign(graph, node, moverId, stackId, index, refMeasure, moverMeasure, alignMode, strength, worldSlot, baseMainSlotKind, sizeSlot, _localSlot) {
1881
+ const ssSlotId = suffixShiftSlotId(stackId, index);
1882
+ ensureSlot(graph, stackId, `suffixShift.${index}`);
1883
+ addResolver(graph, ssSlotId, "max");
1884
+ ensureSlot(graph, refMeasure, worldSlot);
1885
+ ensureSlot(graph, stackId, worldSlot);
1886
+ ensureSlot(graph, moverId, baseMainSlotKind);
1887
+ const opId = `op.${node.kind}.${node.id}.${moverId}.suffixShift`;
1888
+ addCandidate(graph, ssSlotId, void 0, `${node.kind}(${node.id}).suffix`, strength, opId);
1889
+ const inputSlots = [
1890
+ slotId(refMeasure, worldSlot),
1891
+ slotId(stackId, worldSlot),
1892
+ slotId(moverId, baseMainSlotKind)
1893
+ ];
1894
+ if (alignMode === "center" || alignMode === "end") {
1895
+ ensureSlot(graph, refMeasure, sizeSlot);
1896
+ inputSlots.push(slotId(refMeasure, sizeSlot));
1897
+ ensureSlot(graph, moverMeasure, sizeSlot);
1898
+ inputSlots.push(slotId(moverMeasure, sizeSlot));
1899
+ }
1900
+ addOperation(graph, opId, "suffixShiftDelta", inputSlots, [ssSlotId], (inputs) => {
1901
+ const refWorld = inputs[0] ?? 0;
1902
+ const stackWorld = inputs[1] ?? 0;
1903
+ const baseLocal = inputs[2] ?? 0;
1904
+ let delta;
1905
+ if (alignMode === "center") {
1906
+ const refSize = inputs[3] ?? 0;
1907
+ const moverSize = inputs[4] ?? 0;
1908
+ delta = refWorld + refSize / 2 - stackWorld - baseLocal - moverSize / 2;
1909
+ } else if (alignMode === "end") {
1910
+ const refSize = inputs[3] ?? 0;
1911
+ const moverSize = inputs[4] ?? 0;
1912
+ delta = refWorld + refSize - stackWorld - baseLocal - moverSize;
1913
+ } else {
1914
+ delta = refWorld - stackWorld - baseLocal;
1915
+ }
1916
+ return [Math.max(0, delta)];
1917
+ });
1918
+ }
1919
+ function applyEqualize(rule, node, graph) {
1920
+ const targets = node.props.targets;
1921
+ if (!targets || targets.length < 2) return;
1922
+ const strength = node.props.strength ?? rule.default_priority;
1923
+ const strategy = node.props.strategy ?? rule.default_strategy ?? "max";
1924
+ const outputSlotName = rule.slot;
1925
+ const inputSlotName = rule.content_slot;
1926
+ if (!outputSlotName || !inputSlotName) return;
1927
+ const constraintId = node.id;
1928
+ const aggSlotId = `${constraintId}._agg`;
1929
+ ensureSlot(graph, constraintId, "_agg");
1930
+ const gatherOpId = `op.${node.kind}.${constraintId}.gather`;
1931
+ const gatherInputs = [];
1932
+ for (const targetId of targets) {
1933
+ ensureSlot(graph, targetId, inputSlotName);
1934
+ gatherInputs.push(slotId(targetId, inputSlotName));
1935
+ }
1936
+ addCandidate(graph, aggSlotId, void 0, `${node.kind}(${constraintId}).agg`, "required", gatherOpId);
1937
+ addOperation(graph, gatherOpId, strategy, gatherInputs, [aggSlotId], (inputs) => {
1938
+ const vals = inputs.map((v) => v ?? 0);
1939
+ switch (strategy) {
1940
+ case "min":
1941
+ return [Math.min(...vals)];
1942
+ case "first":
1943
+ return [vals[0] ?? 0];
1944
+ default:
1945
+ return [Math.max(...vals)];
1946
+ }
1947
+ });
1948
+ const distributeOpId = `op.${node.kind}.${constraintId}.dist`;
1949
+ const distributeOutputs = [];
1950
+ for (const targetId of targets) {
1951
+ ensureSlot(graph, targetId, outputSlotName);
1952
+ const sid = slotId(targetId, outputSlotName);
1953
+ distributeOutputs.push(sid);
1954
+ addCandidate(graph, sid, void 0, `${node.kind}(${constraintId})`, strength, distributeOpId);
1955
+ }
1956
+ addOperation(graph, distributeOpId, "copy", [aggSlotId], distributeOutputs, (inputs) => {
1957
+ return targets.map(() => inputs[0] ?? 0);
1958
+ });
1959
+ }
1960
+ function applyEnforceOrder(rule, node, graph) {
1961
+ const targets = node.props.targets;
1962
+ if (!targets || targets.length < 2) return;
1963
+ const axis = node.props.axis ?? "vertical";
1964
+ const strength = node.props.strength ?? rule.default_priority;
1965
+ const posSlot = rule.axis_to_slot[axis];
1966
+ const sizeSlot = rule.axis_to_size_slot[axis];
1967
+ for (let i = 1; i < targets.length; i++) {
1968
+ const prev = targets[i - 1];
1969
+ const curr = targets[i];
1970
+ ensureSlot(graph, prev, posSlot);
1971
+ ensureSlot(graph, prev, sizeSlot);
1972
+ ensureSlot(graph, curr, posSlot);
1973
+ const opId = `op.order.${node.id}.${i}`;
1974
+ const inputSlots = [`${prev}.${posSlot}`, `${prev}.${sizeSlot}`];
1975
+ const outputSlotId = `${curr}.${posSlot}`;
1976
+ addCandidate(graph, outputSlotId, void 0, `order(${node.id})`, strength, opId);
1977
+ addOperation(graph, opId, "identity", inputSlots, [outputSlotId], (inputs) => {
1978
+ const prevPos = inputs[0] ?? 0;
1979
+ const prevSize = inputs[1] ?? 0;
1980
+ return [prevPos + prevSize];
1981
+ });
1982
+ }
1983
+ }
1984
+ function applyEnforceMinGap(rule, node, graph) {
1985
+ const targets = node.props.targets;
1986
+ if (!targets || targets.length < 2) return;
1987
+ const axis = node.props.axis ?? "vertical";
1988
+ const strength = node.props.strength ?? rule.default_priority;
1989
+ const posSlot = rule.axis_to_position_slot[axis];
1990
+ const sizeSlot = rule.axis_to_size_slot[axis];
1991
+ const minGap = getNumericProp(node, rule.min_gap_from, 0);
1992
+ for (let i = 1; i < targets.length; i++) {
1993
+ const prev = targets[i - 1];
1994
+ const curr = targets[i];
1995
+ ensureSlot(graph, prev, posSlot);
1996
+ ensureSlot(graph, prev, sizeSlot);
1997
+ ensureSlot(graph, curr, posSlot);
1998
+ const opId = `op.gap.${node.id}.${i}`;
1999
+ const inputSlots = [`${prev}.${posSlot}`, `${prev}.${sizeSlot}`];
2000
+ const outputSlotId = `${curr}.${posSlot}`;
2001
+ addCandidate(graph, outputSlotId, void 0, `gap(${node.id})`, strength, opId);
2002
+ addOperation(graph, opId, "identity", inputSlots, [outputSlotId], (inputs) => {
2003
+ const prevPos = inputs[0] ?? 0;
2004
+ const prevSize = inputs[1] ?? 0;
2005
+ return [prevPos + prevSize + minGap];
2006
+ });
2007
+ }
2008
+ }
2009
+ function applyClampInside(rule, node, graph) {
2010
+ const targets = node.props.targets;
2011
+ if (!targets || targets.length < 2) return;
2012
+ const strength = node.props.strength ?? rule.default_priority;
2013
+ const containerId = targets[0];
2014
+ const contentIds = targets.slice(1);
2015
+ for (const contentId of contentIds) {
2016
+ for (let pi = 0; pi < rule.position_slots.length; pi++) {
2017
+ const posSlot = rule.position_slots[pi];
2018
+ const sizeSlot = rule.size_slots[pi];
2019
+ const containerSizeSlot = sizeSlot;
2020
+ ensureSlot(graph, containerId, containerSizeSlot);
2021
+ ensureSlot(graph, contentId, posSlot);
2022
+ ensureSlot(graph, contentId, sizeSlot);
2023
+ const opId = `op.keepInside.${node.id}.${contentId}.${posSlot}`;
2024
+ const inputSlots = [
2025
+ `${containerId}.${containerSizeSlot}`,
2026
+ `${contentId}.${posSlot}`,
2027
+ `${contentId}.${sizeSlot}`
2028
+ ];
2029
+ const outputSlotId = `${contentId}.${posSlot}`;
2030
+ addCandidate(graph, outputSlotId, void 0, `keepInside(${node.id})`, strength, opId);
2031
+ addOperation(graph, opId, "clamp", inputSlots, [outputSlotId], (inputs) => {
2032
+ const containerSize = inputs[0] ?? 0;
2033
+ const currentPos = inputs[1] ?? 0;
2034
+ const contentSize = inputs[2] ?? 0;
2035
+ const maxPos = containerSize - contentSize;
2036
+ return [Math.max(0, Math.min(currentPos, maxPos))];
2037
+ });
2038
+ }
2039
+ }
2040
+ }
2041
+ var CONSTRAINT_HANDLERS = {
2042
+ world_align: applyWorldAlign,
2043
+ equalize: applyEqualize,
2044
+ enforce_order: applyEnforceOrder,
2045
+ enforce_min_gap: applyEnforceMinGap,
2046
+ clamp_inside: applyClampInside
2047
+ };
2048
+ function applyConstraintFromRule(kind, node, graph) {
2049
+ const rule = constraintRules[kind];
2050
+ if (!rule) return false;
2051
+ const handler = CONSTRAINT_HANDLERS[rule.operation];
2052
+ if (!handler) return false;
2053
+ handler(rule, node, graph);
2054
+ return true;
2055
+ }
2056
+ var textMeasurer = new SimpleTextMeasurer();
2057
+ function applyRootRule(rule, node, graph) {
2058
+ const id = node.id;
2059
+ const width = getNumericProp(node, "width", rule.params?.width?.default ?? 800);
2060
+ const height = getNumericProp(node, "height", rule.params?.height?.default ?? 600);
2061
+ const padding = getNumericProp(node, "padding", rule.params?.padding?.default ?? 0);
2062
+ ensureSlot(graph, id, "resolvedWidth", width);
2063
+ ensureSlot(graph, id, "resolvedHeight", height);
2064
+ ensureSlot(graph, id, "availableWidth", width - padding * 2);
2065
+ ensureSlot(graph, id, "availableHeight", height - padding * 2);
2066
+ addCandidate(graph, slotId(id, "resolvedWidth"), width, "document(explicit)", "required");
2067
+ addCandidate(graph, slotId(id, "resolvedHeight"), height, "document(explicit)", "required");
2068
+ if (padding > 0 && node.children) {
2069
+ for (const child of node.children) {
2070
+ if (child.id && !isConstraintKind(child.kind)) {
2071
+ ensureSlot(graph, child.id, "localX");
2072
+ ensureSlot(graph, child.id, "localY");
2073
+ addCandidate(graph, slotId(child.id, "localX"), padding, `document(${id}).padding`, "weak");
2074
+ addCandidate(graph, slotId(child.id, "localY"), padding, `document(${id}).padding`, "weak");
2075
+ }
2076
+ }
2077
+ }
2078
+ }
2079
+ function applyStackRule(_rule, axisConfig, kindName, node, graph, parentId) {
2080
+ const id = node.id;
2081
+ const gap = getNumericProp(node, "gap", 0);
2082
+ const padding = getNumericProp(node, "padding", 0);
2083
+ const children = node.children ?? [];
2084
+ registerSizeSlots(node, graph, parentId);
2085
+ ensureSlot(graph, id, "contentWidth");
2086
+ ensureSlot(graph, id, "contentHeight");
2087
+ const childIds = children.filter((c) => !isConstraintKind(c.kind)).map((c) => c.id);
2088
+ if (childIds.length === 0) return;
2089
+ graph.childrenOrderMap.set(id, childIds);
2090
+ const mainSlot = axisConfig.main_slot;
2091
+ const crossSlot = axisConfig.cross_slot;
2092
+ const mainSize = axisConfig.main_size;
2093
+ const crossSize = axisConfig.cross_size;
2094
+ const contentMainSize = axisConfig.content_main_size;
2095
+ const contentCrossSize = axisConfig.content_cross_size;
2096
+ const baseMain = baseMainSlot(mainSlot);
2097
+ const opId = `op.${kindName}.${id}`;
2098
+ const inputSlots = [];
2099
+ for (const childId of childIds) {
2100
+ ensureSlot(graph, childId, mainSize);
2101
+ ensureSlot(graph, childId, crossSize);
2102
+ ensureSlot(graph, childId, baseMain);
2103
+ ensureSlot(graph, childId, crossSlot);
2104
+ inputSlots.push(slotId(childId, mainSize));
2105
+ }
2106
+ const baseOutputs = [
2107
+ ...childIds.map((cid) => slotId(cid, baseMain)),
2108
+ slotId(id, contentMainSize),
2109
+ slotId(id, mainSize)
2110
+ ];
2111
+ for (const cid of childIds) {
2112
+ addCandidate(graph, slotId(cid, baseMain), void 0, `${kindName}(${id})`, "strong", opId);
2113
+ }
2114
+ addCandidate(graph, slotId(id, contentMainSize), void 0, `${kindName}(${id}).content`, "strong", opId);
2115
+ addCandidate(graph, slotId(id, mainSize), void 0, `${kindName}(${id}).wrap`, "normal", opId);
2116
+ addOperation(graph, opId, "stackPosition", inputSlots, baseOutputs, (inputs) => {
2117
+ let pos = padding;
2118
+ const positions = [];
2119
+ for (let i = 0; i < inputs.length; i++) {
2120
+ positions.push(pos);
2121
+ pos += inputs[i] + (i < inputs.length - 1 ? gap : 0);
2122
+ }
2123
+ const contentMain = pos - padding;
2124
+ const totalMain = contentMain + padding * 2;
2125
+ return [...positions, contentMain, totalMain];
2126
+ });
2127
+ const suffixShiftSlotIds = [];
2128
+ for (let i = 0; i < childIds.length; i++) {
2129
+ const ssId = suffixShiftSlotId(id, i);
2130
+ ensureSlot(graph, id, `suffixShift.${i}`);
2131
+ addCandidate(graph, ssId, 0, `${kindName}(${id}).suffixShift.${i}`, "weak");
2132
+ addResolver(graph, ssId, "max");
2133
+ suffixShiftSlotIds.push(ssId);
2134
+ }
2135
+ const composeOpId = `op.${kindName}.compose.${id}`;
2136
+ const composeInputs = [
2137
+ ...childIds.map((cid) => slotId(cid, baseMain)),
2138
+ ...suffixShiftSlotIds
2139
+ ];
2140
+ const composeOutputs = childIds.map((cid) => slotId(cid, mainSlot));
2141
+ for (const cid of childIds) {
2142
+ addCandidate(graph, slotId(cid, mainSlot), void 0, `${kindName}(${id}).compose`, "strong", composeOpId);
2143
+ }
2144
+ addOperation(graph, composeOpId, "stackCompose", composeInputs, composeOutputs, (inputs) => {
2145
+ const n = childIds.length;
2146
+ const bases = inputs.slice(0, n);
2147
+ const shifts = inputs.slice(n, n + n);
2148
+ let runningMax = 0;
2149
+ const result = [];
2150
+ for (let j = 0; j < n; j++) {
2151
+ runningMax = Math.max(runningMax, shifts[j] ?? 0);
2152
+ result.push((bases[j] ?? 0) + runningMax);
2153
+ }
2154
+ return result;
2155
+ });
2156
+ const align = node.props.align ?? "start";
2157
+ const crossPropName = crossSize === "resolvedWidth" ? "width" : "height";
2158
+ const availableCrossSlot = crossSize === "resolvedWidth" ? "availableWidth" : "availableHeight";
2159
+ const selfCrossIntent = getSizeProp(node, crossPropName);
2160
+ const contentChildren = children.filter((c) => !isConstraintKind(c.kind));
2161
+ const fillChildIndices = /* @__PURE__ */ new Set();
2162
+ for (let i = 0; i < childIds.length; i++) {
2163
+ if (getSizeProp(contentChildren[i], crossPropName) === "fill") {
2164
+ fillChildIndices.add(i);
2165
+ }
2166
+ }
2167
+ const crossInputSlots = [];
2168
+ for (let i = 0; i < childIds.length; i++) {
2169
+ if (!fillChildIndices.has(i)) {
2170
+ crossInputSlots.push(slotId(childIds[i], crossSize));
2171
+ }
2172
+ }
2173
+ const maxCrossOpId = `op.${kindName}.maxCross.${id}`;
2174
+ addCandidate(graph, slotId(id, contentCrossSize), void 0, `${kindName}(${id}).contentCross`, "strong", maxCrossOpId);
2175
+ addOperation(graph, maxCrossOpId, "max", crossInputSlots, [slotId(id, contentCrossSize)], (inputs) => {
2176
+ return [inputs.length > 0 ? Math.max(...inputs) : 0];
2177
+ });
2178
+ ensureSlot(graph, id, availableCrossSlot);
2179
+ const availCrossOpId = `op.${kindName}.availCross.${id}`;
2180
+ const availCrossInputs = [slotId(id, contentCrossSize)];
2181
+ if (selfCrossIntent === "fill" && parentId) {
2182
+ const parentAvailSlot = crossSize === "resolvedWidth" ? "availableWidth" : "availableHeight";
2183
+ ensureSlot(graph, parentId, parentAvailSlot);
2184
+ availCrossInputs.push(slotId(parentId, parentAvailSlot));
2185
+ }
2186
+ addCandidate(graph, slotId(id, availableCrossSlot), void 0, `${kindName}(${id}).availCross`, "strong", availCrossOpId);
2187
+ addOperation(graph, availCrossOpId, "max", availCrossInputs, [slotId(id, availableCrossSlot)], (inputs) => {
2188
+ const contentCross = inputs[0] ?? 0;
2189
+ const parentAvail = inputs.length > 1 ? (inputs[1] ?? 0) - padding * 2 : 0;
2190
+ return [Math.max(contentCross, parentAvail)];
2191
+ });
2192
+ const wrapCrossOpId = `op.${kindName}.wrapCross.${id}`;
2193
+ addCandidate(graph, slotId(id, crossSize), void 0, `${kindName}(${id}).wrapCross`, "normal", wrapCrossOpId);
2194
+ addOperation(graph, wrapCrossOpId, "resolveSize", [slotId(id, availableCrossSlot)], [slotId(id, crossSize)], (inputs) => {
2195
+ return [(inputs[0] ?? 0) + padding * 2];
2196
+ });
2197
+ const crossAlignOpId = `op.${kindName}.crossAlign.${id}`;
2198
+ const allChildCrossSlots = childIds.map((cid) => slotId(cid, crossSize));
2199
+ const crossAlignInputs = [slotId(id, availableCrossSlot), ...allChildCrossSlots];
2200
+ const crossAlignOutputs = childIds.map((cid) => slotId(cid, crossSlot));
2201
+ for (const cid of childIds) {
2202
+ addCandidate(graph, slotId(cid, crossSlot), void 0, `${kindName}(${id}).crossAlign`, "strong", crossAlignOpId);
2203
+ }
2204
+ addOperation(graph, crossAlignOpId, "crossAlign", crossAlignInputs, crossAlignOutputs, (inputs) => {
2205
+ const availCross = inputs[0] ?? 0;
2206
+ const positions = [];
2207
+ for (let i = 0; i < childIds.length; i++) {
2208
+ const childCross = inputs[1 + i] ?? 0;
2209
+ let crossPos = padding;
2210
+ if (align === "center") {
2211
+ crossPos = padding + (availCross - childCross) / 2;
2212
+ } else if (align === "end") {
2213
+ crossPos = padding + availCross - childCross;
2214
+ }
2215
+ positions.push(crossPos);
2216
+ }
2217
+ return positions;
2218
+ });
2219
+ }
2220
+ function applyContainerRule(rule, node, graph, parentId) {
2221
+ const id = node.id;
2222
+ const padding = getNumericProp(node, "padding", rule.params?.padding?.default ?? 0);
2223
+ registerSizeSlots(node, graph, parentId);
2224
+ ensureSlot(graph, id, "contentWidth");
2225
+ ensureSlot(graph, id, "contentHeight");
2226
+ const widthIntent = getSizeProp(node, "width");
2227
+ const heightIntent = getSizeProp(node, "height");
2228
+ if (widthIntent === "fill" && parentId) {
2229
+ const opId = `op.box.fillWidth.${id}`;
2230
+ const outSid = slotId(id, "resolvedWidth");
2231
+ ensureSlot(graph, parentId, "availableWidth");
2232
+ addCandidate(graph, outSid, void 0, `box(${id}).fill`, "normal", opId);
2233
+ addOperation(graph, opId, "resolveSize", [slotId(parentId, "availableWidth")], [outSid], (inputs) => {
2234
+ return [inputs[0] ?? 0];
2235
+ });
2236
+ }
2237
+ if (heightIntent === "fill" && parentId) {
2238
+ const opId = `op.box.fillHeight.${id}`;
2239
+ const outSid = slotId(id, "resolvedHeight");
2240
+ ensureSlot(graph, parentId, "availableHeight");
2241
+ addCandidate(graph, outSid, void 0, `box(${id}).fill`, "normal", opId);
2242
+ addOperation(graph, opId, "resolveSize", [slotId(parentId, "availableHeight")], [outSid], (inputs) => {
2243
+ return [inputs[0] ?? 0];
2244
+ });
2245
+ }
2246
+ const wrapWidthOpId = `op.box.wrapWidth.${id}`;
2247
+ const wrapHeightOpId = `op.box.wrapHeight.${id}`;
2248
+ if (widthIntent === "wrap") {
2249
+ const outSid = slotId(id, "resolvedWidth");
2250
+ addCandidate(graph, outSid, void 0, `box(${id}).wrap`, "weak", wrapWidthOpId);
2251
+ addOperation(graph, wrapWidthOpId, "resolveSize", [slotId(id, "contentWidth")], [outSid], (inputs) => {
2252
+ return [(inputs[0] ?? 0) + padding * 2];
2253
+ });
2254
+ }
2255
+ if (heightIntent === "wrap") {
2256
+ const outSid = slotId(id, "resolvedHeight");
2257
+ addCandidate(graph, outSid, void 0, `box(${id}).wrap`, "weak", wrapHeightOpId);
2258
+ addOperation(graph, wrapHeightOpId, "resolveSize", [slotId(id, "contentHeight")], [outSid], (inputs) => {
2259
+ return [(inputs[0] ?? 0) + padding * 2];
2260
+ });
2261
+ }
2262
+ const availWidthOpId = `op.box.availW.${id}`;
2263
+ ensureSlot(graph, id, "availableWidth");
2264
+ addCandidate(graph, slotId(id, "availableWidth"), void 0, `box(${id}).availW`, "normal", availWidthOpId);
2265
+ addOperation(graph, availWidthOpId, "resolveSize", [slotId(id, "resolvedWidth")], [slotId(id, "availableWidth")], (inputs) => {
2266
+ return [Math.max((inputs[0] ?? 0) - padding * 2, 0)];
2267
+ });
2268
+ const availHeightOpId = `op.box.availH.${id}`;
2269
+ ensureSlot(graph, id, "availableHeight");
2270
+ addCandidate(graph, slotId(id, "availableHeight"), void 0, `box(${id}).availH`, "normal", availHeightOpId);
2271
+ addOperation(graph, availHeightOpId, "resolveSize", [slotId(id, "resolvedHeight")], [slotId(id, "availableHeight")], (inputs) => {
2272
+ return [Math.max((inputs[0] ?? 0) - padding * 2, 0)];
2273
+ });
2274
+ const children = node.children ?? [];
2275
+ const contentChildIds = children.filter((c) => !isConstraintKind(c.kind)).map((c) => c.id);
2276
+ if (contentChildIds.length > 0) {
2277
+ for (const childId of contentChildIds) {
2278
+ ensureSlot(graph, childId, "resolvedWidth");
2279
+ ensureSlot(graph, childId, "resolvedHeight");
2280
+ ensureSlot(graph, childId, "localX");
2281
+ ensureSlot(graph, childId, "localY");
2282
+ }
2283
+ const contentOpId = `op.box.content.${id}`;
2284
+ const childWidthSlots = contentChildIds.map((cid) => slotId(cid, "resolvedWidth"));
2285
+ const childHeightSlots = contentChildIds.map((cid) => slotId(cid, "resolvedHeight"));
2286
+ const allInputs = [...childWidthSlots, ...childHeightSlots];
2287
+ const contentOutputs = [slotId(id, "contentWidth"), slotId(id, "contentHeight")];
2288
+ addCandidate(graph, slotId(id, "contentWidth"), void 0, `box(${id}).childContent`, "normal", contentOpId);
2289
+ addCandidate(graph, slotId(id, "contentHeight"), void 0, `box(${id}).childContent`, "normal", contentOpId);
2290
+ addOperation(graph, contentOpId, "resolveSize", allInputs, contentOutputs, (inputs) => {
2291
+ const nChildren = contentChildIds.length;
2292
+ const widths = inputs.slice(0, nChildren);
2293
+ const heights = inputs.slice(nChildren);
2294
+ return [Math.max(...widths, 0), Math.max(...heights, 0)];
2295
+ });
2296
+ const hasAlignParams = rule.params?.colAlign !== void 0;
2297
+ const colAlign = hasAlignParams ? node.props.colAlign ?? rule.params.colAlign.default : void 0;
2298
+ const rowAlign = hasAlignParams ? node.props.rowAlign ?? rule.params.rowAlign.default : void 0;
2299
+ const alignOpId = `op.box.center.${id}`;
2300
+ const alignInputs = [slotId(id, "resolvedWidth"), slotId(id, "resolvedHeight"), ...childWidthSlots, ...childHeightSlots];
2301
+ const alignOutputs = contentChildIds.flatMap((cid) => [slotId(cid, "localX"), slotId(cid, "localY")]);
2302
+ for (const cid of contentChildIds) {
2303
+ addCandidate(graph, slotId(cid, "localX"), void 0, `box(${id}).center`, "normal", alignOpId);
2304
+ addCandidate(graph, slotId(cid, "localY"), void 0, `box(${id}).center`, "normal", alignOpId);
2305
+ }
2306
+ addOperation(graph, alignOpId, "resolveSize", alignInputs, alignOutputs, (inputs) => {
2307
+ const boxW = inputs[0] ?? 0;
2308
+ const boxH = inputs[1] ?? 0;
2309
+ const nChildren = contentChildIds.length;
2310
+ const result = [];
2311
+ for (let i = 0; i < nChildren; i++) {
2312
+ const cw = inputs[2 + i] ?? 0;
2313
+ const ch = inputs[2 + nChildren + i] ?? 0;
2314
+ let cx;
2315
+ if (colAlign !== void 0) {
2316
+ switch (colAlign) {
2317
+ case "center":
2318
+ cx = (boxW - cw) / 2;
2319
+ break;
2320
+ case "right":
2321
+ cx = boxW - cw - padding;
2322
+ break;
2323
+ default:
2324
+ cx = padding;
2325
+ break;
2326
+ }
2327
+ } else {
2328
+ cx = (boxW - cw) / 2;
2329
+ }
2330
+ let cy;
2331
+ if (rowAlign !== void 0) {
2332
+ switch (rowAlign) {
2333
+ case "center":
2334
+ cy = (boxH - ch) / 2;
2335
+ break;
2336
+ case "bottom":
2337
+ cy = boxH - ch - padding;
2338
+ break;
2339
+ default:
2340
+ cy = padding;
2341
+ break;
2342
+ }
2343
+ } else {
2344
+ cy = (boxH - ch) / 2;
2345
+ }
2346
+ result.push(cx, cy);
2347
+ }
2348
+ return result;
2349
+ });
2350
+ }
2351
+ }
2352
+ function applyTextMeasurementRule(_rule, node, graph, parentId) {
2353
+ const id = node.id;
2354
+ const text = node.props.text ?? "";
2355
+ const fontSize = getNumericProp(node, "fontSize", 14);
2356
+ const padding = getNumericProp(node, "padding", 0);
2357
+ const textAlign = node.props.textAlign ?? "center";
2358
+ ensureSlot(graph, id, "resolvedWidth");
2359
+ ensureSlot(graph, id, "resolvedHeight");
2360
+ ensureSlot(graph, id, "localX");
2361
+ ensureSlot(graph, id, "localY");
2362
+ const fontWeight = node.props.fontWeight;
2363
+ const measurement = textMeasurer.measure(text, fontSize, void 0, fontWeight);
2364
+ const intrinsicW = measurement.width + padding * 2;
2365
+ const intrinsicH = measurement.height + padding * 2;
2366
+ ensureSlot(graph, id, "intrinsicWidth", intrinsicW);
2367
+ ensureSlot(graph, id, "intrinsicHeight", intrinsicH);
2368
+ addCandidate(graph, slotId(id, "resolvedWidth"), intrinsicW, `text(${id}).intrinsic`, "normal");
2369
+ addCandidate(graph, slotId(id, "resolvedHeight"), intrinsicH, `text(${id}).intrinsic`, "normal");
2370
+ if (parentId) {
2371
+ ensureSlot(graph, id, "contentWidth", intrinsicW);
2372
+ ensureSlot(graph, id, "contentHeight", intrinsicH);
2373
+ }
2374
+ const lineCount = measurement.lines.length;
2375
+ const lineHeight = fontSize * 1.3;
2376
+ const totalTextHeight = lineHeight * lineCount;
2377
+ let textAnchorVal;
2378
+ switch (textAlign) {
2379
+ case "left":
2380
+ textAnchorVal = 0;
2381
+ break;
2382
+ case "right":
2383
+ textAnchorVal = 2;
2384
+ break;
2385
+ default:
2386
+ textAnchorVal = 1;
2387
+ break;
2388
+ }
2389
+ ensureSlot(graph, id, "textBaseline");
2390
+ ensureSlot(graph, id, "textX");
2391
+ ensureSlot(graph, id, "textAnchor");
2392
+ const textLayoutOpId = `op.textLayout.${id}`;
2393
+ const textLayoutInputs = [slotId(id, "resolvedWidth"), slotId(id, "resolvedHeight")];
2394
+ const textLayoutOutputs = [slotId(id, "textBaseline"), slotId(id, "textX"), slotId(id, "textAnchor")];
2395
+ addCandidate(graph, slotId(id, "textBaseline"), void 0, `text(${id}).layout`, "normal", textLayoutOpId);
2396
+ addCandidate(graph, slotId(id, "textX"), void 0, `text(${id}).layout`, "normal", textLayoutOpId);
2397
+ addCandidate(graph, slotId(id, "textAnchor"), void 0, `text(${id}).layout`, "normal", textLayoutOpId);
2398
+ addOperation(graph, textLayoutOpId, "identity", textLayoutInputs, textLayoutOutputs, (inputs) => {
2399
+ const w = inputs[0] ?? 0;
2400
+ const h = inputs[1] ?? 0;
2401
+ const baseline = (h - totalTextHeight) / 2 + fontSize;
2402
+ let tx;
2403
+ switch (textAnchorVal) {
2404
+ case 0:
2405
+ tx = 0;
2406
+ break;
2407
+ case 2:
2408
+ tx = w;
2409
+ break;
2410
+ default:
2411
+ tx = w / 2;
2412
+ break;
2413
+ }
2414
+ return [baseline, tx, textAnchorVal];
2415
+ });
2416
+ }
2417
+ function applyOverlayRule(_rule, node, graph, parentId) {
2418
+ const id = node.id;
2419
+ registerSizeSlots(node, graph, parentId);
2420
+ ensureSlot(graph, id, "contentWidth");
2421
+ ensureSlot(graph, id, "contentHeight");
2422
+ const children = node.children ?? [];
2423
+ const childIds = children.filter((c) => !isConstraintKind(c.kind)).map((c) => c.id);
2424
+ for (const childId of childIds) {
2425
+ ensureSlot(graph, childId, "localX", 0);
2426
+ ensureSlot(graph, childId, "localY", 0);
2427
+ addCandidate(graph, slotId(childId, "localX"), 0, `overlay(${id})`, "normal");
2428
+ addCandidate(graph, slotId(childId, "localY"), 0, `overlay(${id})`, "normal");
2429
+ }
2430
+ }
2431
+ function applyConnectorRule(_rule, node, graph) {
2432
+ const id = node.id;
2433
+ const fromId = node.props.from;
2434
+ const toId = node.props.to;
2435
+ ensureSlot(graph, id, "resolvedWidth", 0);
2436
+ ensureSlot(graph, id, "resolvedHeight", 0);
2437
+ ensureSlot(graph, id, "localX", 0);
2438
+ ensureSlot(graph, id, "localY", 0);
2439
+ ensureSlot(graph, id, "connectorPath");
2440
+ if (fromId && toId) {
2441
+ ensureSlot(graph, fromId, "localX");
2442
+ ensureSlot(graph, fromId, "localY");
2443
+ ensureSlot(graph, fromId, "resolvedWidth");
2444
+ ensureSlot(graph, fromId, "resolvedHeight");
2445
+ ensureSlot(graph, toId, "localX");
2446
+ ensureSlot(graph, toId, "localY");
2447
+ ensureSlot(graph, toId, "resolvedWidth");
2448
+ ensureSlot(graph, toId, "resolvedHeight");
2449
+ addCandidate(graph, slotId(id, "connectorPath"), 0, `connector(${id})`, "normal");
2450
+ }
2451
+ }
2452
+ function applyFixedSizeRule(rule, node, graph) {
2453
+ const id = node.id;
2454
+ const kind = node.kind;
2455
+ let width;
2456
+ let height;
2457
+ if (rule.size_from_axis) {
2458
+ const axis = node.props.axis ?? "horizontal";
2459
+ const sizeConfig = rule.size_from_axis[axis];
2460
+ width = sizeConfig?.width ?? 0;
2461
+ height = sizeConfig?.height ?? 0;
2462
+ } else {
2463
+ width = getNumericProp(node, "width", rule.params?.width?.default ?? 0);
2464
+ height = getNumericProp(node, "height", rule.params?.height?.default ?? 0);
2465
+ }
2466
+ ensureSlot(graph, id, "resolvedWidth", width);
2467
+ ensureSlot(graph, id, "resolvedHeight", height);
2468
+ ensureSlot(graph, id, "localX", 0);
2469
+ ensureSlot(graph, id, "localY", 0);
2470
+ const priority = kind === "divider" ? "weak" : "normal";
2471
+ addCandidate(graph, slotId(id, "resolvedWidth"), width, `${kind}(${id})`, priority);
2472
+ addCandidate(graph, slotId(id, "resolvedHeight"), height, `${kind}(${id})`, priority);
2473
+ }
2474
+ function applyGridRule(rule, node, graph, parentId) {
2475
+ const id = node.id;
2476
+ const gap = getNumericProp(node, "gap", 0);
2477
+ const padding = getNumericProp(node, "padding", 0);
2478
+ const gridConfig = rule.grid;
2479
+ const rowKind = gridConfig.row_kind;
2480
+ const cellKind = gridConfig.cell_kind;
2481
+ registerSizeSlots(node, graph, parentId);
2482
+ ensureSlot(graph, id, "contentWidth");
2483
+ ensureSlot(graph, id, "contentHeight");
2484
+ const rows = (node.children ?? []).filter((c) => c.kind === rowKind);
2485
+ if (rows.length === 0) return;
2486
+ const rowIds = rows.map((r) => r.id);
2487
+ for (const rowId of rowIds) {
2488
+ ensureSlot(graph, rowId, "resolvedWidth");
2489
+ ensureSlot(graph, rowId, "resolvedHeight");
2490
+ ensureSlot(graph, rowId, "localX");
2491
+ ensureSlot(graph, rowId, "localY");
2492
+ }
2493
+ const rowPosOpId = `op.grid.rowPos.${id}`;
2494
+ const rowHeightSlots = rowIds.map((rid) => slotId(rid, "resolvedHeight"));
2495
+ const rowPosOutputs = [
2496
+ ...rowIds.map((rid) => slotId(rid, "localY")),
2497
+ ...rowIds.map((rid) => slotId(rid, "localX")),
2498
+ slotId(id, "contentHeight"),
2499
+ slotId(id, "resolvedHeight")
2500
+ ];
2501
+ for (const rid of rowIds) {
2502
+ addCandidate(graph, slotId(rid, "localY"), void 0, `grid(${id}).rowPos`, "strong", rowPosOpId);
2503
+ addCandidate(graph, slotId(rid, "localX"), void 0, `grid(${id}).rowX`, "normal", rowPosOpId);
2504
+ }
2505
+ addCandidate(graph, slotId(id, "contentHeight"), void 0, `grid(${id}).contentH`, "strong", rowPosOpId);
2506
+ addCandidate(graph, slotId(id, "resolvedHeight"), void 0, `grid(${id}).wrapH`, "normal", rowPosOpId);
2507
+ addOperation(graph, rowPosOpId, "stackPosition", rowHeightSlots, rowPosOutputs, (inputs) => {
2508
+ let y = padding;
2509
+ const yPositions = [];
2510
+ for (let i = 0; i < rowIds.length; i++) {
2511
+ yPositions.push(y);
2512
+ y += (inputs[i] ?? 0) + (i < rowIds.length - 1 ? gap : 0);
2513
+ }
2514
+ const contentH = y - padding;
2515
+ const totalH = contentH + padding * 2;
2516
+ const xPositions = rowIds.map(() => padding);
2517
+ return [...yPositions, ...xPositions, contentH, totalH];
2518
+ });
2519
+ const maxCols = Math.max(...rows.map((r) => (r.children ?? []).filter((c) => c.kind === cellKind).length));
2520
+ const columnCellIds = [];
2521
+ for (let col = 0; col < maxCols; col++) {
2522
+ columnCellIds.push([]);
2523
+ }
2524
+ for (const row of rows) {
2525
+ const cells = (row.children ?? []).filter((c) => c.kind === cellKind);
2526
+ for (let col = 0; col < cells.length && col < maxCols; col++) {
2527
+ columnCellIds[col].push(cells[col].id);
2528
+ }
2529
+ }
2530
+ const cellPaddingMap = /* @__PURE__ */ new Map();
2531
+ for (const row of rows) {
2532
+ for (const cell of (row.children ?? []).filter((c) => c.kind === cellKind)) {
2533
+ cellPaddingMap.set(cell.id, getNumericProp(cell, "padding", 0));
2534
+ }
2535
+ }
2536
+ const colMinWidthSlots = [];
2537
+ for (let col = 0; col < maxCols; col++) {
2538
+ const cellIds = columnCellIds[col];
2539
+ const colMinSlot = `${id}.colMinWidth.${col}`;
2540
+ colMinWidthSlots.push(colMinSlot);
2541
+ ensureSlot(graph, id, `colMinWidth.${col}`);
2542
+ if (cellIds.length < 2) {
2543
+ if (cellIds.length === 1) {
2544
+ const cid = cellIds[0];
2545
+ const p = cellPaddingMap.get(cid) ?? 0;
2546
+ const singleOpId = `op.grid.colMin.${id}.${col}`;
2547
+ ensureSlot(graph, cid, "contentWidth");
2548
+ addCandidate(graph, colMinSlot, void 0, `grid(${id}).colMin[${col}]`, "strong", singleOpId);
2549
+ addOperation(graph, singleOpId, "columnWidthResolve", [slotId(cid, "contentWidth")], [colMinSlot], (inputs) => {
2550
+ return [(inputs[0] ?? 0) + p * 2];
2551
+ });
2552
+ }
2553
+ continue;
2554
+ }
2555
+ const colEqOpId = `op.grid.colMin.${id}.${col}`;
2556
+ const widthInputs = cellIds.map((cid) => {
2557
+ ensureSlot(graph, cid, "contentWidth");
2558
+ return slotId(cid, "contentWidth");
2559
+ });
2560
+ const colPaddings = cellIds.map((cid) => cellPaddingMap.get(cid) ?? 0);
2561
+ addCandidate(graph, colMinSlot, void 0, `grid(${id}).colMin[${col}]`, "strong", colEqOpId);
2562
+ addOperation(graph, colEqOpId, "columnWidthResolve", widthInputs, [colMinSlot], (inputs) => {
2563
+ const desiredWidths = inputs.map((w, i) => (w ?? 0) + colPaddings[i] * 2);
2564
+ return [Math.max(...desiredWidths, 0)];
2565
+ });
2566
+ }
2567
+ if (maxCols > 0) {
2568
+ const wrapWidthOpId = `op.grid.wrapWidth.${id}`;
2569
+ const wrapOutputs = [slotId(id, "contentWidth"), slotId(id, "resolvedWidth")];
2570
+ addCandidate(graph, slotId(id, "contentWidth"), void 0, `grid(${id}).wrapContentW`, "normal", wrapWidthOpId);
2571
+ addCandidate(graph, slotId(id, "resolvedWidth"), void 0, `grid(${id}).wrapW`, "normal", wrapWidthOpId);
2572
+ addOperation(graph, wrapWidthOpId, "resolveSize", colMinWidthSlots, wrapOutputs, (inputs) => {
2573
+ const contentW = inputs.reduce((sum, w) => sum + (w ?? 0), 0) + gap * Math.max(0, inputs.length - 1);
2574
+ return [contentW, contentW + padding * 2];
2575
+ });
2576
+ }
2577
+ const colResolvedWidthSlots = [];
2578
+ if (maxCols > 0) {
2579
+ const colFillOpId = `op.grid.colFill.${id}`;
2580
+ const colFillInputs = [slotId(id, "resolvedWidth"), ...colMinWidthSlots];
2581
+ const colFillOutputs = [];
2582
+ for (let col = 0; col < maxCols; col++) {
2583
+ const colResSlot = `${id}.colResolvedWidth.${col}`;
2584
+ colResolvedWidthSlots.push(colResSlot);
2585
+ ensureSlot(graph, id, `colResolvedWidth.${col}`);
2586
+ colFillOutputs.push(colResSlot);
2587
+ addCandidate(graph, colResSlot, void 0, `grid(${id}).colFill[${col}]`, "strong", colFillOpId);
2588
+ }
2589
+ addOperation(graph, colFillOpId, "columnFill", colFillInputs, colFillOutputs, (inputs) => {
2590
+ const resolvedW = inputs[0] ?? 0;
2591
+ const availableW = resolvedW - padding * 2;
2592
+ const minWidths = inputs.slice(1).map((v) => v ?? 0);
2593
+ const totalMin = minWidths.reduce((s, w) => s + w, 0) + gap * Math.max(0, maxCols - 1);
2594
+ const surplus = Math.max(0, availableW - totalMin);
2595
+ const surplusPerCol = surplus / maxCols;
2596
+ return minWidths.map((mw) => mw + surplusPerCol);
2597
+ });
2598
+ }
2599
+ for (let col = 0; col < maxCols; col++) {
2600
+ const cellIds = columnCellIds[col];
2601
+ if (cellIds.length === 0) continue;
2602
+ const cellWOpId = `op.grid.cellW.${id}.${col}`;
2603
+ const colResSlot = colResolvedWidthSlots[col];
2604
+ const cellWOutputs = cellIds.map((cid) => slotId(cid, "resolvedWidth"));
2605
+ for (const cid of cellIds) {
2606
+ addCandidate(graph, slotId(cid, "resolvedWidth"), void 0, `grid(${id}).cellW[${col}]`, "strong", cellWOpId);
2607
+ }
2608
+ addOperation(graph, cellWOpId, "copy", [colResSlot], cellWOutputs, (inputs) => {
2609
+ return cellIds.map(() => inputs[0] ?? 0);
2610
+ });
2611
+ }
2612
+ for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {
2613
+ const cells = (rows[rowIdx].children ?? []).filter((c) => c.kind === cellKind);
2614
+ const cellIds = cells.map((c) => c.id);
2615
+ if (cellIds.length < 2) continue;
2616
+ const rowEqOpId = `op.grid.rowEq.${id}.${rowIdx}`;
2617
+ const heightInputs = cellIds.map((cid) => {
2618
+ ensureSlot(graph, cid, "contentHeight");
2619
+ return slotId(cid, "contentHeight");
2620
+ });
2621
+ const rowEqOutputs = cellIds.map((cid) => slotId(cid, "resolvedHeight"));
2622
+ const rowPaddings = cellIds.map((cid) => cellPaddingMap.get(cid) ?? 0);
2623
+ for (const cid of cellIds) {
2624
+ addCandidate(graph, slotId(cid, "resolvedHeight"), void 0, `grid(${id}).rowEq[${rowIdx}]`, "strong", rowEqOpId);
2625
+ }
2626
+ addOperation(graph, rowEqOpId, "rowHeightResolve", heightInputs, rowEqOutputs, (inputs) => {
2627
+ const desiredHeights = inputs.map((h, i) => (h ?? 0) + rowPaddings[i] * 2);
2628
+ const maxH = Math.max(...desiredHeights, 0);
2629
+ return cellIds.map(() => maxH);
2630
+ });
2631
+ }
2632
+ for (const rowId of rowIds) {
2633
+ const rowWidthOpId = `op.grid.rowWidth.${id}.${rowId}`;
2634
+ const outSid = slotId(rowId, "resolvedWidth");
2635
+ addCandidate(graph, outSid, void 0, `grid(${id}).rowWidth`, "normal", rowWidthOpId);
2636
+ addOperation(graph, rowWidthOpId, "resolveSize", [slotId(id, "resolvedWidth")], [outSid], (inputs) => {
2637
+ return [(inputs[0] ?? 0) - padding * 2];
2638
+ });
2639
+ }
2640
+ for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {
2641
+ const cells = (rows[rowIdx].children ?? []).filter((c) => c.kind === cellKind);
2642
+ const cellIds = cells.map((c) => c.id);
2643
+ const rowHeightOpId = `op.grid.rowH.${id}.${rowIdx}`;
2644
+ const cellHeightInputs = cellIds.map((cid) => slotId(cid, "resolvedHeight"));
2645
+ const outSid = slotId(rowIds[rowIdx], "resolvedHeight");
2646
+ addCandidate(graph, outSid, void 0, `grid(${id}).rowH`, "normal", rowHeightOpId);
2647
+ addOperation(graph, rowHeightOpId, "max", cellHeightInputs, [outSid], (inputs) => {
2648
+ return [Math.max(...inputs, 0)];
2649
+ });
2650
+ }
2651
+ }
2652
+ function applyStubRule(node, graph, parentId) {
2653
+ const id = node.id;
2654
+ registerSizeSlots(node, graph, parentId);
2655
+ ensureSlot(graph, id, "contentWidth");
2656
+ ensureSlot(graph, id, "contentHeight");
2657
+ }
2658
+ var STACK_RULE = layoutRules["stack"];
2659
+ function applyLayoutFromRule(kind, node, graph, parentId) {
2660
+ if (STACK_RULE.applies_to && kind in STACK_RULE.applies_to) {
2661
+ const axisConfig = STACK_RULE.applies_to[kind];
2662
+ applyStackRule(STACK_RULE, axisConfig, kind, node, graph, parentId);
2663
+ return;
2664
+ }
2665
+ let rule = layoutRules[kind];
2666
+ if (!rule) {
2667
+ applyStubRule(node, graph, parentId);
2668
+ return;
2669
+ }
2670
+ if (rule.inherits) {
2671
+ const base = layoutRules[rule.inherits];
2672
+ if (base) {
2673
+ rule = { ...base, ...rule };
2674
+ }
2675
+ }
2676
+ switch (rule.type) {
2677
+ case "root":
2678
+ applyRootRule(rule, node, graph);
2679
+ break;
2680
+ case "container":
2681
+ applyContainerRule(rule, node, graph, parentId);
2682
+ break;
2683
+ case "text_measurement":
2684
+ applyTextMeasurementRule(rule, node, graph, parentId);
2685
+ break;
2686
+ case "overlay":
2687
+ applyOverlayRule(rule, node, graph, parentId);
2688
+ break;
2689
+ case "connector":
2690
+ applyConnectorRule(rule, node, graph);
2691
+ break;
2692
+ case "fixed_size":
2693
+ applyFixedSizeRule(rule, node, graph);
2694
+ break;
2695
+ case "grid":
2696
+ applyGridRule(rule, node, graph, parentId);
2697
+ break;
2698
+ default:
2699
+ applyStubRule(node, graph, parentId);
2700
+ break;
2701
+ }
2702
+ }
2703
+
2704
+ // src/compiler/compiler.ts
2705
+ function isParamRef(v) {
2706
+ return typeof v === "object" && v !== null && v.__paramRef === true;
2707
+ }
2708
+ function isTemplateRef(v) {
2709
+ return typeof v === "object" && v !== null && v.__templateRef === true;
2710
+ }
2711
+ var TSX_TO_IR = {
2712
+ Document: "document",
2713
+ Group: "group",
2714
+ Box: "box",
2715
+ VStack: "vstack",
2716
+ HStack: "hstack",
2717
+ Overlay: "overlay",
2718
+ Grid: "grid",
2719
+ Flow: "flow",
2720
+ Absolute: "absolute",
2721
+ Spacer: "spacer",
2722
+ Divider: "divider",
2723
+ Text: "text",
2724
+ Label: "label",
2725
+ Image: "image",
2726
+ Icon: "icon",
2727
+ SvgAsset: "svgAsset",
2728
+ Connector: "connector",
2729
+ AlignConstraint: "alignConstraint",
2730
+ EqualizeWidth: "equalizeWidth",
2731
+ EqualizeHeight: "equalizeHeight",
2732
+ MinGap: "minGap",
2733
+ KeepInsideConstraint: "keepInsideConstraint",
2734
+ OrderConstraint: "orderConstraint",
2735
+ Table: "table",
2736
+ Row: "row",
2737
+ Cell: "cell"
2738
+ };
2739
+ var TEXT_KINDS = /* @__PURE__ */ new Set(["text", "label"]);
2740
+ function hasExportModifier(node) {
2741
+ if (!ts.canHaveModifiers(node)) return false;
2742
+ const mods = ts.getModifiers(node);
2743
+ return mods?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
2744
+ }
2745
+ function extractParamNames(params) {
2746
+ if (params.length === 0) return /* @__PURE__ */ new Set();
2747
+ const param = params[0];
2748
+ if (!ts.isObjectBindingPattern(param.name)) return null;
2749
+ const names = /* @__PURE__ */ new Set();
2750
+ for (const el of param.name.elements) {
2751
+ if (ts.isIdentifier(el.name)) {
2752
+ names.add(el.name.text);
2753
+ }
2754
+ }
2755
+ return names;
2756
+ }
2757
+ function findReturnJsx(body) {
2758
+ if (!body) return void 0;
2759
+ for (const stmt of body.statements) {
2760
+ if (ts.isReturnStatement(stmt) && stmt.expression) {
2761
+ let expr = stmt.expression;
2762
+ while (ts.isParenthesizedExpression(expr)) expr = expr.expression;
2763
+ if (ts.isJsxElement(expr) || ts.isJsxSelfClosingElement(expr)) return expr;
2764
+ }
2765
+ }
2766
+ return void 0;
2767
+ }
2768
+ function evaluateExpression(expr, paramNames) {
2769
+ if (ts.isNumericLiteral(expr)) return Number(expr.text);
2770
+ if (ts.isStringLiteral(expr)) return expr.text;
2771
+ if (ts.isNoSubstitutionTemplateLiteral(expr)) return expr.text;
2772
+ if (expr.kind === ts.SyntaxKind.TrueKeyword) return true;
2773
+ if (expr.kind === ts.SyntaxKind.FalseKeyword) return false;
2774
+ if (ts.isPrefixUnaryExpression(expr) && expr.operator === ts.SyntaxKind.MinusToken) {
2775
+ const operand = evaluateExpression(expr.operand, paramNames);
2776
+ if (typeof operand === "number") return -operand;
2777
+ }
2778
+ if (ts.isArrayLiteralExpression(expr)) {
2779
+ return expr.elements.map((el) => evaluateExpression(el, paramNames));
2780
+ }
2781
+ if (ts.isObjectLiteralExpression(expr)) {
2782
+ const obj = {};
2783
+ for (const prop of expr.properties) {
2784
+ if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
2785
+ obj[prop.name.text] = evaluateExpression(prop.initializer, paramNames);
2786
+ }
2787
+ }
2788
+ return obj;
2789
+ }
2790
+ if (paramNames && ts.isIdentifier(expr) && paramNames.has(expr.text)) {
2791
+ return { __paramRef: true, name: expr.text };
2792
+ }
2793
+ if (ts.isTemplateExpression(expr)) {
2794
+ const parts = [];
2795
+ if (expr.head.text) parts.push(expr.head.text);
2796
+ for (const span of expr.templateSpans) {
2797
+ const spanVal = evaluateExpression(span.expression, paramNames);
2798
+ if (isParamRef(spanVal)) {
2799
+ parts.push(spanVal);
2800
+ } else if (spanVal !== void 0) {
2801
+ parts.push(String(spanVal));
2802
+ }
2803
+ if (span.literal.text) parts.push(span.literal.text);
2804
+ }
2805
+ if (parts.every((p) => typeof p === "string")) {
2806
+ return parts.join("");
2807
+ }
2808
+ return { __templateRef: true, parts };
2809
+ }
2810
+ return void 0;
2811
+ }
2812
+ function extractAttrValue(init, paramNames) {
2813
+ if (!init) return true;
2814
+ if (ts.isStringLiteral(init)) return init.text;
2815
+ if (ts.isJsxExpression(init) && init.expression) {
2816
+ return evaluateExpression(init.expression, paramNames);
2817
+ }
2818
+ return void 0;
2819
+ }
2820
+ function extractAttributes(attrs, paramNames) {
2821
+ const props = {};
2822
+ for (const attr of attrs.properties) {
2823
+ if (ts.isJsxAttribute(attr) && ts.isIdentifier(attr.name)) {
2824
+ const val = extractAttrValue(attr.initializer, paramNames);
2825
+ if (val !== void 0) {
2826
+ props[attr.name.text] = val;
2827
+ }
2828
+ }
2829
+ }
2830
+ return props;
2831
+ }
2832
+ function collectTextContent(children) {
2833
+ const parts = [];
2834
+ for (const child of children) {
2835
+ if (ts.isJsxText(child)) {
2836
+ const trimmed = child.text.trim();
2837
+ if (trimmed) parts.push(trimmed);
2838
+ }
2839
+ }
2840
+ return parts.join(" ");
2841
+ }
2842
+ function collectTextContentForTemplate(children, paramNames) {
2843
+ const parts = [];
2844
+ for (const child of children) {
2845
+ if (ts.isJsxText(child)) {
2846
+ const trimmed = child.text.trim();
2847
+ if (trimmed) parts.push(trimmed);
2848
+ } else if (ts.isJsxExpression(child) && child.expression) {
2849
+ const val = evaluateExpression(child.expression, paramNames);
2850
+ if (isParamRef(val)) {
2851
+ parts.push(val);
2852
+ } else if (val !== void 0) {
2853
+ parts.push(String(val));
2854
+ }
2855
+ }
2856
+ }
2857
+ if (parts.length === 0) return void 0;
2858
+ if (parts.every((p) => typeof p === "string")) {
2859
+ return parts.join(" ");
2860
+ }
2861
+ if (parts.length === 1 && isParamRef(parts[0])) {
2862
+ return parts[0];
2863
+ }
2864
+ return { __templateRef: true, parts };
2865
+ }
2866
+ function extractComponentDefs(sourceFile, diagnostics, knownComponents) {
2867
+ const defs = /* @__PURE__ */ new Map();
2868
+ const registry = new Map(knownComponents ?? []);
2869
+ for (const stmt of sourceFile.statements) {
2870
+ if (!hasExportModifier(stmt)) continue;
2871
+ if (ts.isFunctionDeclaration(stmt) && stmt.name) {
2872
+ const pNames = extractParamNames(stmt.parameters);
2873
+ if (pNames === null) continue;
2874
+ const jsx = findReturnJsx(stmt.body);
2875
+ if (!jsx) continue;
2876
+ const ctx = {
2877
+ diagnostics,
2878
+ registry,
2879
+ paramNames: pNames,
2880
+ expandingStack: /* @__PURE__ */ new Set()
2881
+ };
2882
+ const template = compileElement(jsx, ctx);
2883
+ if (template) {
2884
+ const def = { name: stmt.name.text, paramNames: pNames, template };
2885
+ defs.set(stmt.name.text, def);
2886
+ registry.set(stmt.name.text, def);
2887
+ }
2888
+ }
2889
+ if (ts.isVariableStatement(stmt)) {
2890
+ for (const decl of stmt.declarationList.declarations) {
2891
+ if (!ts.isIdentifier(decl.name) || !decl.initializer) continue;
2892
+ if (!ts.isArrowFunction(decl.initializer)) continue;
2893
+ const name = decl.name.text;
2894
+ const arrow = decl.initializer;
2895
+ const pNames = extractParamNames(arrow.parameters);
2896
+ if (pNames === null) continue;
2897
+ let jsx;
2898
+ if (ts.isBlock(arrow.body)) {
2899
+ jsx = findReturnJsx(arrow.body);
2900
+ } else {
2901
+ let expr = arrow.body;
2902
+ while (ts.isParenthesizedExpression(expr)) expr = expr.expression;
2903
+ if (ts.isJsxElement(expr) || ts.isJsxSelfClosingElement(expr)) jsx = expr;
2904
+ }
2905
+ if (!jsx) continue;
2906
+ const ctx = {
2907
+ diagnostics,
2908
+ registry,
2909
+ paramNames: pNames,
2910
+ expandingStack: /* @__PURE__ */ new Set()
2911
+ };
2912
+ const template = compileElement(jsx, ctx);
2913
+ if (template) {
2914
+ const def = { name, paramNames: pNames, template };
2915
+ defs.set(name, def);
2916
+ registry.set(name, def);
2917
+ }
2918
+ }
2919
+ }
2920
+ }
2921
+ return defs;
2922
+ }
2923
+ function expandComponentTag(tagName, props, childNodes, ctx) {
2924
+ if (ctx.expandingStack.has(tagName)) {
2925
+ ctx.diagnostics.push({
2926
+ level: "error",
2927
+ message: `Circular component reference: ${tagName}`,
2928
+ path: tagName
2929
+ });
2930
+ return null;
2931
+ }
2932
+ const def = ctx.registry.get(tagName);
2933
+ if (!def) return null;
2934
+ ctx.expandingStack.add(tagName);
2935
+ const compiledChildren = [];
2936
+ if (childNodes) {
2937
+ for (const child of childNodes) {
2938
+ if (ts.isJsxElement(child) || ts.isJsxSelfClosingElement(child)) {
2939
+ const compiled = compileElement(child, ctx);
2940
+ if (compiled) compiledChildren.push(compiled);
2941
+ }
2942
+ }
2943
+ }
2944
+ const id = props["id"];
2945
+ const idIsParam = def.paramNames.has("id");
2946
+ const paramValues = {};
2947
+ const extraProps = {};
2948
+ for (const [key, value] of Object.entries(props)) {
2949
+ if (key === "id" && !idIsParam) continue;
2950
+ if (def.paramNames.has(key)) {
2951
+ paramValues[key] = value;
2952
+ } else {
2953
+ extraProps[key] = value;
2954
+ }
2955
+ }
2956
+ const expanded = deepCloneAndSubstitute(def.template, paramValues, compiledChildren);
2957
+ Object.assign(expanded.props, extraProps);
2958
+ if (id && !idIsParam) expanded.id = id;
2959
+ ctx.expandingStack.delete(tagName);
2960
+ return expanded;
2961
+ }
2962
+ function deepCloneAndSubstitute(node, paramValues, compiledChildren) {
2963
+ const cloned = { kind: node.kind, props: {} };
2964
+ if (node.id) cloned.id = node.id;
2965
+ for (const [key, value] of Object.entries(node.props)) {
2966
+ if (key === "__childrenSlot") continue;
2967
+ if (key === "__idParam") {
2968
+ const resolved = substituteValue(value, paramValues);
2969
+ if (typeof resolved === "string") {
2970
+ cloned.id = resolved;
2971
+ } else if (isParamRef(resolved) || isTemplateRef(resolved)) {
2972
+ cloned.props["__idParam"] = resolved;
2973
+ }
2974
+ continue;
2975
+ }
2976
+ cloned.props[key] = substituteValue(value, paramValues);
2977
+ }
2978
+ if (node.children) {
2979
+ const newChildren = [];
2980
+ for (const child of node.children) {
2981
+ if (child.props.__childrenSlot === true) {
2982
+ newChildren.push(...compiledChildren);
2983
+ } else {
2984
+ newChildren.push(deepCloneAndSubstitute(child, paramValues, compiledChildren));
2985
+ }
2986
+ }
2987
+ if (newChildren.length > 0) cloned.children = newChildren;
2988
+ }
2989
+ return cloned;
2990
+ }
2991
+ function substituteValue(value, paramValues) {
2992
+ if (isParamRef(value)) {
2993
+ return paramValues[value.name];
2994
+ }
2995
+ if (isTemplateRef(value)) {
2996
+ const newParts = [];
2997
+ for (const part of value.parts) {
2998
+ if (isParamRef(part)) {
2999
+ const resolved = paramValues[part.name];
3000
+ if (resolved === void 0) {
3001
+ } else if (typeof resolved === "string") {
3002
+ newParts.push(resolved);
3003
+ } else if (isParamRef(resolved)) {
3004
+ newParts.push(resolved);
3005
+ } else if (isTemplateRef(resolved)) {
3006
+ newParts.push(...resolved.parts);
3007
+ } else {
3008
+ newParts.push(String(resolved));
3009
+ }
3010
+ } else {
3011
+ newParts.push(part);
3012
+ }
3013
+ }
3014
+ const allStrings = newParts.every((p) => typeof p === "string");
3015
+ if (allStrings) {
3016
+ return newParts.join("");
3017
+ }
3018
+ return { __templateRef: true, parts: newParts };
3019
+ }
3020
+ if (Array.isArray(value)) {
3021
+ return value.map((item) => substituteValue(item, paramValues));
3022
+ }
3023
+ if (typeof value === "object" && value !== null) {
3024
+ const result = {};
3025
+ for (const [k, v] of Object.entries(value)) {
3026
+ result[k] = substituteValue(v, paramValues);
3027
+ }
3028
+ return result;
3029
+ }
3030
+ return value;
3031
+ }
3032
+ function resolveImports(sourceFile, resolveImport, diagnostics, fromFile, fileCache) {
3033
+ const allDefs = /* @__PURE__ */ new Map();
3034
+ const cache = fileCache ?? /* @__PURE__ */ new Map();
3035
+ for (const stmt of sourceFile.statements) {
3036
+ if (!ts.isImportDeclaration(stmt)) continue;
3037
+ if (!ts.isStringLiteral(stmt.moduleSpecifier)) continue;
3038
+ const specifier = stmt.moduleSpecifier.text;
3039
+ const importSource = resolveImport(specifier, fromFile);
3040
+ if (!importSource) continue;
3041
+ const cacheKey = importSource;
3042
+ let defs;
3043
+ if (cache.has(cacheKey)) {
3044
+ defs = cache.get(cacheKey);
3045
+ } else {
3046
+ cache.set(cacheKey, /* @__PURE__ */ new Map());
3047
+ const importedFile = ts.createSourceFile(
3048
+ specifier,
3049
+ importSource,
3050
+ ts.ScriptTarget.Latest,
3051
+ true,
3052
+ ts.ScriptKind.TSX
3053
+ );
3054
+ const transitiveDefs = resolveImports(importedFile, resolveImport, diagnostics, specifier, cache);
3055
+ for (const [k, v] of transitiveDefs) allDefs.set(k, v);
3056
+ defs = extractComponentDefs(importedFile, diagnostics, allDefs);
3057
+ cache.set(cacheKey, defs);
3058
+ }
3059
+ if (stmt.importClause?.namedBindings && ts.isNamedImports(stmt.importClause.namedBindings)) {
3060
+ for (const imp of stmt.importClause.namedBindings.elements) {
3061
+ const importedName = (imp.propertyName ?? imp.name).text;
3062
+ const localName = imp.name.text;
3063
+ const def = defs.get(importedName);
3064
+ if (def) {
3065
+ allDefs.set(localName, { ...def, name: localName });
3066
+ }
3067
+ }
3068
+ }
3069
+ }
3070
+ return allDefs;
3071
+ }
3072
+ function compileElement(node, ctx) {
3073
+ let tagName;
3074
+ let props;
3075
+ let childNodes;
3076
+ if (ts.isJsxSelfClosingElement(node)) {
3077
+ tagName = node.tagName.getText();
3078
+ props = extractAttributes(node.attributes, ctx.paramNames);
3079
+ } else {
3080
+ tagName = node.openingElement.tagName.getText();
3081
+ props = extractAttributes(node.openingElement.attributes, ctx.paramNames);
3082
+ childNodes = node.children;
3083
+ }
3084
+ const kind = TSX_TO_IR[tagName];
3085
+ if (!kind) {
3086
+ if (ctx.registry.has(tagName)) {
3087
+ return expandComponentTag(tagName, props, childNodes, ctx);
3088
+ }
3089
+ ctx.diagnostics.push({
3090
+ level: "warning",
3091
+ message: `Unknown TSX element <${tagName}>`,
3092
+ path: tagName
3093
+ });
3094
+ return null;
3095
+ }
3096
+ const irProps = {};
3097
+ const rawId = props["id"];
3098
+ delete props["id"];
3099
+ const allowed = getAllowedProps(kind);
3100
+ if (allowed) {
3101
+ for (const key of Object.keys(props)) {
3102
+ if (!allowed.has(key)) {
3103
+ ctx.diagnostics.push({
3104
+ level: "error",
3105
+ message: `Unknown property "${key}" on <${tagName}>. Allowed: ${[...allowed].filter((k) => k !== "id" && k !== "geometry").sort().join(", ")}`,
3106
+ path: tagName
3107
+ });
3108
+ }
3109
+ }
3110
+ }
3111
+ Object.assign(irProps, props);
3112
+ const irNode = { kind, props: irProps };
3113
+ if (isParamRef(rawId) || isTemplateRef(rawId)) {
3114
+ irProps["__idParam"] = rawId;
3115
+ } else if (typeof rawId === "string") {
3116
+ irNode.id = rawId;
3117
+ }
3118
+ if (childNodes) {
3119
+ if (TEXT_KINDS.has(kind)) {
3120
+ if (ctx.paramNames) {
3121
+ const text = collectTextContentForTemplate(childNodes, ctx.paramNames);
3122
+ if (text !== void 0) irNode.props["text"] = text;
3123
+ } else {
3124
+ const text = collectTextContent(childNodes);
3125
+ if (text) irNode.props["text"] = text;
3126
+ }
3127
+ }
3128
+ const children = [];
3129
+ for (const child of childNodes) {
3130
+ if (ts.isJsxElement(child) || ts.isJsxSelfClosingElement(child)) {
3131
+ const compiled = compileElement(child, ctx);
3132
+ if (compiled) children.push(compiled);
3133
+ } else if (ctx.paramNames && ts.isJsxExpression(child) && child.expression && ts.isIdentifier(child.expression) && child.expression.text === "children" && ctx.paramNames.has("children")) {
3134
+ children.push({ kind: "group", props: { __childrenSlot: true } });
3135
+ }
3136
+ }
3137
+ if (children.length > 0) irNode.children = children;
3138
+ }
3139
+ return irNode;
3140
+ }
3141
+ function findRootJsxElement(sourceFile) {
3142
+ let result;
3143
+ function visit(node) {
3144
+ if (result) return;
3145
+ if (ts.isFunctionDeclaration(node) && hasExportModifier(node)) return;
3146
+ if (ts.isVariableStatement(node) && hasExportModifier(node)) return;
3147
+ if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) {
3148
+ result = node;
3149
+ return;
3150
+ }
3151
+ ts.forEachChild(node, visit);
3152
+ }
3153
+ ts.forEachChild(sourceFile, visit);
3154
+ return result;
3155
+ }
3156
+ function compileDrawTsx(source, options) {
3157
+ const sourceFile = ts.createSourceFile(
3158
+ "input.tsx",
3159
+ source,
3160
+ ts.ScriptTarget.Latest,
3161
+ true,
3162
+ ts.ScriptKind.TSX
3163
+ );
3164
+ const diagnostics = [];
3165
+ const registry = new Map(options?.components);
3166
+ const inlineDefs = extractComponentDefs(sourceFile, diagnostics);
3167
+ for (const [name, def] of inlineDefs) {
3168
+ registry.set(name, def);
3169
+ }
3170
+ if (options?.resolveImport) {
3171
+ const importDefs = resolveImports(
3172
+ sourceFile,
3173
+ options.resolveImport,
3174
+ diagnostics
3175
+ );
3176
+ for (const [name, def] of importDefs) {
3177
+ registry.set(name, def);
3178
+ }
3179
+ }
3180
+ const root = findRootJsxElement(sourceFile);
3181
+ if (!root) {
3182
+ diagnostics.push({
3183
+ level: "error",
3184
+ message: "No JSX element found in source",
3185
+ path: "root"
3186
+ });
3187
+ return {
3188
+ ir: { kind: "document", props: {} },
3189
+ diagnostics
3190
+ };
3191
+ }
3192
+ const ctx = {
3193
+ diagnostics,
3194
+ registry,
3195
+ expandingStack: /* @__PURE__ */ new Set()
3196
+ };
3197
+ const ir = compileElement(root, ctx);
3198
+ const compiledIr = ir ?? { kind: "document", props: {} };
3199
+ validateUniqueIds(compiledIr, diagnostics);
3200
+ return { ir: compiledIr, diagnostics };
3201
+ }
3202
+ function validateUniqueIds(root, diagnostics) {
3203
+ const seen = /* @__PURE__ */ new Map();
3204
+ collectNodeIds(root, seen, "root");
3205
+ for (const [id, paths] of seen) {
3206
+ if (paths.length > 1) {
3207
+ diagnostics.push({
3208
+ level: "error",
3209
+ message: `Duplicate id '${id}' after component expansion (${paths.length} occurrences). Component children must use derived IDs (e.g. \`\${id}-child\`).`,
3210
+ path: paths[0]
3211
+ });
3212
+ }
3213
+ }
3214
+ }
3215
+ function collectNodeIds(node, seen, path) {
3216
+ if (node.id) {
3217
+ const paths = seen.get(node.id) ?? [];
3218
+ paths.push(path);
3219
+ seen.set(node.id, paths);
3220
+ }
3221
+ if (node.children) {
3222
+ for (let i = 0; i < node.children.length; i++) {
3223
+ collectNodeIds(node.children[i], seen, `${path}.children[${i}]`);
3224
+ }
3225
+ }
3226
+ }
3227
+
3228
+ // src/design/index.ts
3229
+ import { z as z2 } from "zod";
3230
+ import { load as load2 } from "js-yaml";
3231
+ var TokenEntrySchema = z2.object({
3232
+ $type: z2.enum(["color", "dimension", "fontFamily", "fontWeight", "number", "shadow", "string"]),
3233
+ $value: z2.string()
3234
+ });
3235
+ var TokenIRSchema = z2.lazy(
3236
+ () => z2.record(z2.string(), z2.union([TokenEntrySchema, TokenIRSchema]))
3237
+ );
3238
+ var ThemeIRSchema = z2.object({
3239
+ defaults: z2.record(z2.string(), z2.record(z2.string(), z2.unknown())),
3240
+ variants: z2.record(
3241
+ z2.string(),
3242
+ z2.record(z2.string(), z2.record(z2.string(), z2.unknown()))
3243
+ )
3244
+ });
3245
+ function parseTokens(yaml) {
3246
+ const raw = load2(yaml);
3247
+ return TokenIRSchema.parse(raw);
3248
+ }
3249
+ function parseTheme(yaml) {
3250
+ const raw = load2(yaml);
3251
+ return ThemeIRSchema.parse(raw);
3252
+ }
3253
+ function resolveTokenRef(ref, tokens) {
3254
+ const path = ref.replace(/^\{|\}$/g, "");
3255
+ const parts = path.split(".");
3256
+ let current = tokens;
3257
+ for (const part of parts) {
3258
+ if (isTokenEntry(current)) {
3259
+ throw new Error(`Cannot traverse into token entry at '${part}' in ref '${ref}'`);
3260
+ }
3261
+ const next = current[part];
3262
+ if (next === void 0) {
3263
+ throw new Error(`Token path '${path}' not found at segment '${part}'`);
3264
+ }
3265
+ current = next;
3266
+ }
3267
+ if (isTokenEntry(current)) {
3268
+ return current.$value;
3269
+ }
3270
+ throw new Error(`Token ref '${ref}' does not resolve to a token entry (found group)`);
3271
+ }
3272
+ function isTokenEntry(value) {
3273
+ return "$type" in value && "$value" in value;
3274
+ }
3275
+
3276
+ // src/design/defaults.ts
3277
+ var TOKENS_YAML = `draw:
3278
+ font-family:
3279
+ $type: fontFamily
3280
+ $value: "system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"
3281
+ primary:
3282
+ $type: color
3283
+ $value: "#2563eb"
3284
+ surface:
3285
+ $type: color
3286
+ $value: "#ffffff"
3287
+ border:
3288
+ $type: color
3289
+ $value: "#e5e7eb"
3290
+ text:
3291
+ $type: color
3292
+ $value: "#111827"
3293
+ on-primary:
3294
+ $type: color
3295
+ $value: "#ffffff"
3296
+ connector-default:
3297
+ $type: color
3298
+ $value: "#6b7280"
3299
+ space:
3300
+ node-padding:
3301
+ $type: dimension
3302
+ $value: "12px"
3303
+ node-radius:
3304
+ $type: dimension
3305
+ $value: "8px"
3306
+ connector-width:
3307
+ $type: dimension
3308
+ $value: "1.5px"
3309
+ font-label:
3310
+ $type: dimension
3311
+ $value: "12px"
3312
+ font-node:
3313
+ $type: dimension
3314
+ $value: "14px"
3315
+ `;
3316
+ var THEME_YAML = `defaults:
3317
+ Document:
3318
+ fontFamily: "{draw.font-family}"
3319
+ Box:
3320
+ fill: "{draw.surface}"
3321
+ stroke: "{draw.border}"
3322
+ strokeWidth: 1
3323
+ radius: "{draw.space.node-radius}"
3324
+ padding: "{draw.space.node-padding}"
3325
+ Text:
3326
+ foreground: "{draw.text}"
3327
+ fontSize: "{draw.space.font-node}"
3328
+ Connector:
3329
+ stroke: "{draw.connector-default}"
3330
+ strokeWidth: "{draw.space.connector-width}"
3331
+ route: orthogonal
3332
+ endArrow: arrow
3333
+ Label:
3334
+ fontSize: "{draw.space.font-label}"
3335
+ foreground: "{draw.text}"
3336
+
3337
+ variants:
3338
+ Box:
3339
+ primary:
3340
+ fill: "{draw.primary}"
3341
+ foreground: "{draw.on-primary}"
3342
+ muted:
3343
+ fill: "{draw.surface}"
3344
+ stroke: "{draw.border}"
3345
+ opacity: 0.6
3346
+ Connector:
3347
+ dashed:
3348
+ strokeStyle: dashed
3349
+ opacity: 0.7
3350
+ highlighted:
3351
+ stroke: "{draw.primary}"
3352
+ strokeWidth: 2
3353
+ `;
3354
+ var _cachedTokens;
3355
+ var _cachedTheme;
3356
+ function defaultTokens() {
3357
+ if (!_cachedTokens) _cachedTokens = parseTokens(TOKENS_YAML);
3358
+ return _cachedTokens;
3359
+ }
3360
+ function defaultTheme() {
3361
+ if (!_cachedTheme) _cachedTheme = parseTheme(THEME_YAML);
3362
+ return _cachedTheme;
3363
+ }
3364
+
3365
+ // src/normalizer/index.ts
3366
+ function kindToPascal(kind) {
3367
+ return kind.charAt(0).toUpperCase() + kind.slice(1);
3368
+ }
3369
+ function normalize(ir, options) {
3370
+ const diagnostics = [];
3371
+ const result = structuredClone(ir);
3372
+ const kindCounters = /* @__PURE__ */ new Map();
3373
+ assignAutoIds(result, kindCounters);
3374
+ checkDuplicateIds(result, diagnostics);
3375
+ applyThemeAndVariants(result, options.theme);
3376
+ resolveAllTokens(result, options.tokens, diagnostics, "root");
3377
+ const geometryRegistry = buildGeometryRegistry(result, diagnostics);
3378
+ resolveGeometryReferences(result, geometryRegistry, diagnostics);
3379
+ return { ir: result, diagnostics, geometryRegistry };
3380
+ }
3381
+ function assignAutoIds(node, counters) {
3382
+ if (!node.id) {
3383
+ const count = (counters.get(node.kind) ?? 0) + 1;
3384
+ counters.set(node.kind, count);
3385
+ node.id = `${node.kind}_${count}`;
3386
+ }
3387
+ if (node.children) {
3388
+ for (const child of node.children) {
3389
+ assignAutoIds(child, counters);
3390
+ }
3391
+ }
3392
+ }
3393
+ function checkDuplicateIds(root, diagnostics) {
3394
+ const idLocations = /* @__PURE__ */ new Map();
3395
+ collectIds(root, idLocations, "root");
3396
+ for (const [id, paths] of idLocations) {
3397
+ if (paths.length > 1) {
3398
+ diagnostics.push({
3399
+ level: "error",
3400
+ message: `Duplicate id '${id}' found at: ${paths.join(", ")}`,
3401
+ path: paths[0]
3402
+ });
3403
+ }
3404
+ }
3405
+ }
3406
+ function collectIds(node, idLocations, path) {
3407
+ if (node.id) {
3408
+ const paths = idLocations.get(node.id) ?? [];
3409
+ paths.push(path);
3410
+ idLocations.set(node.id, paths);
3411
+ }
3412
+ if (node.children) {
3413
+ for (let i = 0; i < node.children.length; i++) {
3414
+ collectIds(node.children[i], idLocations, `${path}.children[${i}]`);
3415
+ }
3416
+ }
3417
+ }
3418
+ function applyThemeAndVariants(node, theme) {
3419
+ const pascalKind = kindToPascal(node.kind);
3420
+ const explicitKeys = new Set(Object.keys(node.props));
3421
+ const defaults = theme.defaults[pascalKind];
3422
+ if (defaults) {
3423
+ for (const [key, value] of Object.entries(defaults)) {
3424
+ if (!explicitKeys.has(key)) {
3425
+ node.props[key] = value;
3426
+ }
3427
+ }
3428
+ }
3429
+ const variantName = explicitKeys.has("variant") ? node.props.variant : void 0;
3430
+ if (variantName) {
3431
+ const variantDefs = theme.variants[pascalKind]?.[variantName];
3432
+ if (variantDefs) {
3433
+ for (const [key, value] of Object.entries(variantDefs)) {
3434
+ if (!explicitKeys.has(key)) {
3435
+ node.props[key] = value;
3436
+ }
3437
+ }
3438
+ }
3439
+ }
3440
+ if (node.children) {
3441
+ for (const child of node.children) {
3442
+ applyThemeAndVariants(child, theme);
3443
+ }
3444
+ }
3445
+ }
3446
+ function isTokenRef(value) {
3447
+ return typeof value === "string" && /^\{.+\}$/.test(value);
3448
+ }
3449
+ var DIMENSION_RE = /^(-?\d+(?:\.\d+)?)\s*(?:px|rem|em|pt|dp)$/;
3450
+ function coerceDimension(value) {
3451
+ const m = DIMENSION_RE.exec(value);
3452
+ if (m) return parseFloat(m[1]);
3453
+ return value;
3454
+ }
3455
+ function resolveValue(value, tokens, diagnostics, propPath) {
3456
+ if (isTokenRef(value)) {
3457
+ try {
3458
+ const resolved = resolveTokenRef(value, tokens);
3459
+ return coerceDimension(resolved);
3460
+ } catch (err) {
3461
+ diagnostics.push({
3462
+ level: "error",
3463
+ message: `Failed to resolve token '${value}': ${err.message}`,
3464
+ path: propPath
3465
+ });
3466
+ return value;
3467
+ }
3468
+ }
3469
+ if (Array.isArray(value)) {
3470
+ return value.map(
3471
+ (item, i) => resolveValue(item, tokens, diagnostics, `${propPath}[${i}]`)
3472
+ );
3473
+ }
3474
+ return value;
3475
+ }
3476
+ function resolveAllTokens(node, tokens, diagnostics, path) {
3477
+ for (const [key, value] of Object.entries(node.props)) {
3478
+ node.props[key] = resolveValue(value, tokens, diagnostics, `${path}.props.${key}`);
3479
+ }
3480
+ if (node.children) {
3481
+ for (let i = 0; i < node.children.length; i++) {
3482
+ resolveAllTokens(node.children[i], tokens, diagnostics, `${path}.children[${i}]`);
3483
+ }
3484
+ }
3485
+ }
3486
+ var STANDARD_ROLES = ["layout", "align", "connect", "hit", "measure"];
3487
+ var DEFAULT_GEOMETRY = {
3488
+ layout: "self",
3489
+ align: "self",
3490
+ connect: "self",
3491
+ hit: "self",
3492
+ measure: "self"
3493
+ };
3494
+ function buildGeometryRegistry(root, diagnostics) {
3495
+ const registry = /* @__PURE__ */ new Map();
3496
+ collectGeometryProps(root, registry, diagnostics);
3497
+ return registry;
3498
+ }
3499
+ function collectGeometryProps(node, registry, diagnostics) {
3500
+ if (node.id && node.props.geometry) {
3501
+ const geom = node.props.geometry;
3502
+ if (typeof geom === "object" && geom !== null && !Array.isArray(geom)) {
3503
+ const roleMap = { ...DEFAULT_GEOMETRY };
3504
+ for (const [role, target] of Object.entries(geom)) {
3505
+ if (!STANDARD_ROLES.includes(role)) {
3506
+ diagnostics.push({
3507
+ level: "warning",
3508
+ message: `Unknown geometry role '${role}' on node '${node.id}'`,
3509
+ path: node.id
3510
+ });
3511
+ }
3512
+ if (typeof target === "string") {
3513
+ roleMap[role] = target;
3514
+ }
3515
+ }
3516
+ registry.set(node.id, roleMap);
3517
+ }
3518
+ }
3519
+ if (node.children) {
3520
+ for (const child of node.children) {
3521
+ collectGeometryProps(child, registry, diagnostics);
3522
+ }
3523
+ }
3524
+ }
3525
+ function parseGeometryRef(ref) {
3526
+ const colonIdx = ref.indexOf(":");
3527
+ if (colonIdx === -1) return { nodeId: ref };
3528
+ return { nodeId: ref.substring(0, colonIdx), role: ref.substring(colonIdx + 1) };
3529
+ }
3530
+ function resolveWithRole(nodeId, role, registry) {
3531
+ const visited = /* @__PURE__ */ new Set();
3532
+ let current = nodeId;
3533
+ while (!visited.has(current)) {
3534
+ visited.add(current);
3535
+ const roleMap = registry.get(current);
3536
+ if (!roleMap) return current;
3537
+ const target = roleMap[role];
3538
+ if (!target || target === "self") return current;
3539
+ current = target;
3540
+ }
3541
+ return current;
3542
+ }
3543
+ function resolveGeometryRef(ref, implicitRole, registry) {
3544
+ const { nodeId, role } = parseGeometryRef(ref);
3545
+ const effectiveRole = role ?? implicitRole;
3546
+ if (!effectiveRole) return nodeId;
3547
+ return resolveWithRole(nodeId, effectiveRole, registry);
3548
+ }
3549
+ function resolveGeometryReferences(node, registry, diagnostics) {
3550
+ const kind = node.kind;
3551
+ if (kind === "alignConstraint") {
3552
+ const targets = node.props.targets;
3553
+ if (targets) {
3554
+ const alignRefs = {};
3555
+ for (const t of targets) {
3556
+ const resolved = resolveGeometryRef(t, "align", registry);
3557
+ if (resolved !== t) alignRefs[t] = resolved;
3558
+ }
3559
+ if (Object.keys(alignRefs).length > 0) {
3560
+ node.props.alignRefs = alignRefs;
3561
+ }
3562
+ }
3563
+ const anchor = node.props.anchor;
3564
+ if (anchor) {
3565
+ const resolved = resolveGeometryRef(anchor, "align", registry);
3566
+ if (resolved !== anchor) {
3567
+ node.props.alignRefs = { ...node.props.alignRefs ?? {}, [anchor]: resolved };
3568
+ }
3569
+ }
3570
+ }
3571
+ if (kind === "connector") {
3572
+ const from = node.props.from;
3573
+ if (from) {
3574
+ node.props.from = resolveGeometryRef(from, "connect", registry);
3575
+ }
3576
+ const to = node.props.to;
3577
+ if (to) {
3578
+ node.props.to = resolveGeometryRef(to, "connect", registry);
3579
+ }
3580
+ }
3581
+ if (kind === "equalizeWidth" || kind === "equalizeHeight" || kind === "minGap" || kind === "keepInsideConstraint" || kind === "orderConstraint") {
3582
+ const targets = node.props.targets;
3583
+ if (targets) {
3584
+ node.props.targets = targets.map(
3585
+ (t) => resolveGeometryRef(t, void 0, registry)
3586
+ );
3587
+ }
3588
+ }
3589
+ if (node.children) {
3590
+ for (const child of node.children) {
3591
+ resolveGeometryReferences(child, registry, diagnostics);
3592
+ }
3593
+ }
3594
+ }
3595
+
3596
+ // src/renderer/shapes.ts
3597
+ var STROKE_DASHARRAY = {
3598
+ dashed: "6 4",
3599
+ dotted: "2 3"
3600
+ };
3601
+ function styleAttrs(styles) {
3602
+ const parts = [];
3603
+ if (styles.fill != null) parts.push(`fill="${esc(styles.fill)}"`);
3604
+ else parts.push('fill="none"');
3605
+ if (styles.stroke != null) parts.push(`stroke="${esc(styles.stroke)}"`);
3606
+ if (styles.strokeWidth != null) parts.push(`stroke-width="${styles.strokeWidth}"`);
3607
+ if (styles.strokeStyle != null && styles.strokeStyle !== "solid") {
3608
+ const da = STROKE_DASHARRAY[String(styles.strokeStyle)];
3609
+ if (da) parts.push(`stroke-dasharray="${da}"`);
3610
+ }
3611
+ if (styles.opacity != null) parts.push(`opacity="${styles.opacity}"`);
3612
+ return parts.join(" ");
3613
+ }
3614
+ function esc(v) {
3615
+ return String(v).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3616
+ }
3617
+ function pts(coords) {
3618
+ return coords.map(([px, py]) => `${px},${py}`).join(" ");
3619
+ }
3620
+ function renderRect(r, sa) {
3621
+ return `<rect x="${r.x}" y="${r.y}" width="${r.width}" height="${r.height}" ${sa}/>`;
3622
+ }
3623
+ function renderRoundedRect(r, styles, sa) {
3624
+ const rx = styles.radius != null ? Number(styles.radius) : 8;
3625
+ return `<rect x="${r.x}" y="${r.y}" width="${r.width}" height="${r.height}" rx="${rx}" ${sa}/>`;
3626
+ }
3627
+ function renderCircle(r, sa) {
3628
+ const radius = r.width / 2;
3629
+ return `<circle cx="${r.x + radius}" cy="${r.y + radius}" r="${radius}" ${sa}/>`;
3630
+ }
3631
+ function renderEllipse(r, sa) {
3632
+ const rx = r.width / 2;
3633
+ const ry = r.height / 2;
3634
+ return `<ellipse cx="${r.x + rx}" cy="${r.y + ry}" rx="${rx}" ry="${ry}" ${sa}/>`;
3635
+ }
3636
+ function renderDiamond(r, sa) {
3637
+ const cx = r.x + r.width / 2;
3638
+ const cy = r.y + r.height / 2;
3639
+ const points = [
3640
+ [cx, r.y],
3641
+ [r.x + r.width, cy],
3642
+ [cx, r.y + r.height],
3643
+ [r.x, cy]
3644
+ ];
3645
+ return `<polygon points="${pts(points)}" ${sa}/>`;
3646
+ }
3647
+ function renderParallelogram(r, sa) {
3648
+ const skew = r.width * 0.2;
3649
+ const points = [
3650
+ [r.x + skew, r.y],
3651
+ [r.x + r.width, r.y],
3652
+ [r.x + r.width - skew, r.y + r.height],
3653
+ [r.x, r.y + r.height]
3654
+ ];
3655
+ return `<polygon points="${pts(points)}" ${sa}/>`;
3656
+ }
3657
+ function renderCapsule(r, sa) {
3658
+ const rx = Math.min(r.width, r.height) / 2;
3659
+ return `<rect x="${r.x}" y="${r.y}" width="${r.width}" height="${r.height}" rx="${rx}" ${sa}/>`;
3660
+ }
3661
+ function renderCylinder(r, sa) {
3662
+ const ellipseH = Math.min(r.height * 0.2, 20);
3663
+ const ry = ellipseH / 2;
3664
+ const x = r.x;
3665
+ const y = r.y;
3666
+ const w = r.width;
3667
+ const h = r.height;
3668
+ const rx = w / 2;
3669
+ const topArc = `M ${x},${y + ry} A ${rx},${ry} 0 0,1 ${x + w},${y + ry}`;
3670
+ const rightSide = `L ${x + w},${y + h - ry}`;
3671
+ const bottomArc = `A ${rx},${ry} 0 0,1 ${x},${y + h - ry}`;
3672
+ const leftSide = `L ${x},${y + ry}`;
3673
+ const topEllipseBack = `M ${x},${y + ry} A ${rx},${ry} 0 0,0 ${x + w},${y + ry}`;
3674
+ return `<path d="${topArc} ${rightSide} ${bottomArc} ${leftSide}" ${sa}/><path d="${topEllipseBack}" ${styles_stroke_only(sa)}/>`;
3675
+ }
3676
+ function styles_stroke_only(sa) {
3677
+ return sa.replace(/fill="[^"]*"/, 'fill="none"');
3678
+ }
3679
+ function renderHexagon(r, sa) {
3680
+ const cx = r.x + r.width / 2;
3681
+ const cy = r.y + r.height / 2;
3682
+ const inset = r.width * 0.25;
3683
+ const points = [
3684
+ [r.x + inset, r.y],
3685
+ [r.x + r.width - inset, r.y],
3686
+ [r.x + r.width, cy],
3687
+ [r.x + r.width - inset, r.y + r.height],
3688
+ [r.x + inset, r.y + r.height],
3689
+ [r.x, cy]
3690
+ ];
3691
+ return `<polygon points="${pts(points)}" ${sa}/>`;
3692
+ }
3693
+ function renderTriangle(r, sa) {
3694
+ const points = [
3695
+ [r.x + r.width / 2, r.y],
3696
+ [r.x + r.width, r.y + r.height],
3697
+ [r.x, r.y + r.height]
3698
+ ];
3699
+ return `<polygon points="${pts(points)}" ${sa}/>`;
3700
+ }
3701
+ var SHAPE_RENDERERS = {
3702
+ rect: (r, _s, sa) => renderRect(r, sa),
3703
+ roundedRect: (r, s, sa) => renderRoundedRect(r, s, sa),
3704
+ circle: (r, _s, sa) => renderCircle(r, sa),
3705
+ ellipse: (r, _s, sa) => renderEllipse(r, sa),
3706
+ diamond: (r, _s, sa) => renderDiamond(r, sa),
3707
+ parallelogram: (r, _s, sa) => renderParallelogram(r, sa),
3708
+ capsule: (r, _s, sa) => renderCapsule(r, sa),
3709
+ cylinder: (r, _s, sa) => renderCylinder(r, sa),
3710
+ hexagon: (r, _s, sa) => renderHexagon(r, sa),
3711
+ triangle: (r, _s, sa) => renderTriangle(r, sa)
3712
+ };
3713
+ function renderShape(shape, rect, styles) {
3714
+ const sa = styleAttrs(styles);
3715
+ const renderer = SHAPE_RENDERERS[shape] ?? SHAPE_RENDERERS.rect;
3716
+ return renderer(rect, styles, sa);
3717
+ }
3718
+
3719
+ // src/renderer/connectors.ts
3720
+ var BASE_MARKER_SIZE = 7;
3721
+ var BASE_MARKER_HALF = BASE_MARKER_SIZE / 2;
3722
+ var MARKER_TEMPLATES = [
3723
+ { type: "arrow", path: `M 0,0 L ${BASE_MARKER_SIZE},${BASE_MARKER_HALF} L 0,${BASE_MARKER_SIZE}`, fill: false, shape: "polygon" },
3724
+ { type: "filledArrow", path: `M 0,0 L ${BASE_MARKER_SIZE},${BASE_MARKER_HALF} L 0,${BASE_MARKER_SIZE} Z`, fill: true, shape: "polygon" },
3725
+ { type: "diamond", path: `M 0,${BASE_MARKER_HALF} L ${BASE_MARKER_HALF},0 L ${BASE_MARKER_SIZE},${BASE_MARKER_HALF} L ${BASE_MARKER_HALF},${BASE_MARKER_SIZE} Z`, fill: false, shape: "polygon" },
3726
+ { type: "filledDiamond", path: `M 0,${BASE_MARKER_HALF} L ${BASE_MARKER_HALF},0 L ${BASE_MARKER_SIZE},${BASE_MARKER_HALF} L ${BASE_MARKER_HALF},${BASE_MARKER_SIZE} Z`, fill: true, shape: "polygon" },
3727
+ { type: "circle", path: "", fill: false, shape: "circle" },
3728
+ { type: "filledCircle", path: "", fill: true, shape: "circle" }
3729
+ ];
3730
+ function renderMarkerDef(def, size) {
3731
+ const half = size / 2;
3732
+ const scale = size / BASE_MARKER_SIZE;
3733
+ const common = `id="${def.id}" markerWidth="${size}" markerHeight="${size}" refX="${size}" refY="${half}" orient="auto-start-reverse"`;
3734
+ if (def.shape === "circle") {
3735
+ const r = half * 0.8;
3736
+ const fillAttr2 = def.fill ? `fill="${def.color}"` : `fill="none" stroke="${def.color}" stroke-width="1"`;
3737
+ return `<marker ${common}><circle cx="${half}" cy="${half}" r="${r}" ${fillAttr2}/></marker>`;
3738
+ }
3739
+ const fillAttr = def.fill ? `fill="${def.color}"` : `fill="none" stroke="${def.color}" stroke-width="1"`;
3740
+ if (scale === 1) {
3741
+ return `<marker ${common}><path d="${def.path}" ${fillAttr}/></marker>`;
3742
+ }
3743
+ return `<marker ${common}><path d="${def.path}" ${fillAttr} transform="scale(${scale})"/></marker>`;
3744
+ }
3745
+ function renderArrowMarkers() {
3746
+ return `<defs>
3747
+ </defs>`;
3748
+ }
3749
+ var dynamicMarkerRegistry = /* @__PURE__ */ new Map();
3750
+ function resetDynamicMarkers() {
3751
+ dynamicMarkerRegistry.clear();
3752
+ }
3753
+ function getDynamicMarkerDefs() {
3754
+ if (dynamicMarkerRegistry.size === 0) return "";
3755
+ return Array.from(dynamicMarkerRegistry.values()).join("\n");
3756
+ }
3757
+ function ensureColoredMarker(arrowType, arrowSize, color) {
3758
+ const colorKey = color.replace(/[^a-zA-Z0-9]/g, "");
3759
+ const sizeKey = arrowSize === 1 ? "" : `-s${arrowSize}`;
3760
+ const id = `marker-${arrowType}-${colorKey}${sizeKey}`;
3761
+ if (!dynamicMarkerRegistry.has(id)) {
3762
+ const template = MARKER_TEMPLATES.find((t) => t.type === arrowType);
3763
+ if (template) {
3764
+ const size = BASE_MARKER_SIZE * arrowSize;
3765
+ const def = renderMarkerDef(
3766
+ { id, path: template.path, fill: template.fill, shape: template.shape, color },
3767
+ size
3768
+ );
3769
+ dynamicMarkerRegistry.set(id, def);
3770
+ }
3771
+ }
3772
+ return id;
3773
+ }
3774
+ var STROKE_DASHARRAY2 = {
3775
+ dashed: "6 4",
3776
+ dotted: "2 3"
3777
+ };
3778
+ function renderConnector(node) {
3779
+ const d = node.connectorPath ?? "";
3780
+ const styles = node.styles;
3781
+ const strokeColor = styles.stroke != null ? String(styles.stroke) : "#6b7280";
3782
+ const parts = [`d="${d}"`];
3783
+ parts.push('fill="none"');
3784
+ parts.push(`stroke="${strokeColor}"`);
3785
+ if (styles.strokeWidth != null) parts.push(`stroke-width="${styles.strokeWidth}"`);
3786
+ else parts.push('stroke-width="1.5"');
3787
+ if (styles.strokeDasharray != null) {
3788
+ parts.push(`stroke-dasharray="${styles.strokeDasharray}"`);
3789
+ } else if (styles.strokeStyle != null && styles.strokeStyle !== "solid") {
3790
+ const da = STROKE_DASHARRAY2[String(styles.strokeStyle)];
3791
+ if (da) parts.push(`stroke-dasharray="${da}"`);
3792
+ }
3793
+ if (styles.opacity != null) parts.push(`opacity="${styles.opacity}"`);
3794
+ const arrowSize = styles.arrowSize != null ? Number(styles.arrowSize) : 1;
3795
+ const startArrow = styles.startArrow != null ? String(styles.startArrow) : "none";
3796
+ const endArrow = styles.endArrow != null ? String(styles.endArrow) : "arrow";
3797
+ if (startArrow !== "none") {
3798
+ const markerId = ensureColoredMarker(startArrow, arrowSize, strokeColor);
3799
+ parts.push(`marker-start="url(#${markerId})"`);
3800
+ }
3801
+ if (endArrow !== "none") {
3802
+ const markerId = ensureColoredMarker(endArrow, arrowSize, strokeColor);
3803
+ parts.push(`marker-end="url(#${markerId})"`);
3804
+ }
3805
+ return `<path ${parts.join(" ")}/>`;
3806
+ }
3807
+
3808
+ // src/renderer/text.ts
3809
+ function escXml(s) {
3810
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
3811
+ }
3812
+ function renderText(node) {
3813
+ const lines = node.textLines ?? [];
3814
+ if (lines.length === 0) return "";
3815
+ const styles = node.styles;
3816
+ const anchor = node.textAnchor ?? "middle";
3817
+ const tx = node.textX ?? node.worldRect.x + node.worldRect.width / 2;
3818
+ const fontSize = styles.fontSize != null ? Number(styles.fontSize) : 14;
3819
+ const lineHeight = fontSize * 1.3;
3820
+ const startY = node.textBaseline ?? node.worldRect.y + (node.worldRect.height - lineHeight * lines.length) / 2 + fontSize;
3821
+ const attrs = [`text-anchor="${anchor}"`];
3822
+ if (styles.foreground != null) attrs.push(`fill="${styles.foreground}"`);
3823
+ else attrs.push('fill="#000"');
3824
+ if (styles.fontSize != null) attrs.push(`font-size="${styles.fontSize}"`);
3825
+ if (styles.fontWeight != null && styles.fontWeight !== "regular") {
3826
+ attrs.push(`font-weight="${styles.fontWeight}"`);
3827
+ }
3828
+ if (styles.fontFamily != null) attrs.push(`font-family="${styles.fontFamily}"`);
3829
+ const tspans = lines.map((line, i) => {
3830
+ const dy = i === 0 ? 0 : lineHeight;
3831
+ return `<tspan x="${tx}" dy="${dy}">${escXml(line)}</tspan>`;
3832
+ });
3833
+ return `<text x="${tx}" y="${startY}" ${attrs.join(" ")}>${tspans.join("")}</text>`;
3834
+ }
3835
+
3836
+ // src/renderer/svg.ts
3837
+ var clipIdCounter = 0;
3838
+ var LAYOUT_KINDS = /* @__PURE__ */ new Set([
3839
+ "document",
3840
+ "vstack",
3841
+ "hstack",
3842
+ "group",
3843
+ "overlay",
3844
+ "grid",
3845
+ "flow",
3846
+ "absolute",
3847
+ "row"
3848
+ ]);
3849
+ var CONSTRAINT_KINDS = /* @__PURE__ */ new Set([
3850
+ "alignConstraint",
3851
+ "equalizeWidth",
3852
+ "equalizeHeight",
3853
+ "minGap",
3854
+ "keepInsideConstraint",
3855
+ "orderConstraint"
3856
+ ]);
3857
+ var FIT_TO_PAR = {
3858
+ contain: "xMidYMid meet",
3859
+ cover: "xMidYMid slice",
3860
+ fill: "none",
3861
+ none: "xMidYMid meet"
3862
+ };
3863
+ function renderConnectorLabel(node) {
3864
+ const text = renderText(node);
3865
+ if (!text) return "";
3866
+ const r = node.worldRect;
3867
+ const bg = node.styles.background != null ? String(node.styles.background) : "#ffffff";
3868
+ const pad = 2;
3869
+ const rect = bg === "none" ? "" : `<rect x="${r.x - pad}" y="${r.y - pad}" width="${r.width + pad * 2}" height="${r.height + pad * 2}" fill="${bg}"/>`;
3870
+ return `<g id="${escId(node.id)}">${rect}${text}</g>`;
3871
+ }
3872
+ function renderImage(node) {
3873
+ const r = node.worldRect;
3874
+ const src = node.styles.src;
3875
+ if (!src) {
3876
+ return `<g id="${escId(node.id)}"><rect x="${r.x}" y="${r.y}" width="${r.width}" height="${r.height}" fill="#eee" stroke="#ccc" stroke-width="1" stroke-dasharray="4 2"/></g>`;
3877
+ }
3878
+ const fit = String(node.styles.fit ?? "contain");
3879
+ const par = FIT_TO_PAR[fit] ?? FIT_TO_PAR.contain;
3880
+ return `<g id="${escId(node.id)}"><image href="${escAttr(src)}" x="${r.x}" y="${r.y}" width="${r.width}" height="${r.height}" preserveAspectRatio="${par}"/></g>`;
3881
+ }
3882
+ function renderIcon(node, registry) {
3883
+ const r = node.worldRect;
3884
+ const name = node.styles.name;
3885
+ if (!name) {
3886
+ return `<g id="${escId(node.id)}"><rect x="${r.x}" y="${r.y}" width="${r.width}" height="${r.height}" fill="#eee" stroke="#ccc" stroke-width="1" stroke-dasharray="4 2"/></g>`;
3887
+ }
3888
+ const svgContent = registry?.resolveQualified(name);
3889
+ if (svgContent) {
3890
+ return renderInlineSvg(node.id, r, svgContent);
3891
+ }
3892
+ const fg = node.styles.foreground ?? "#666";
3893
+ const fontSize = Math.min(r.width, r.height) * 0.35;
3894
+ const shortLabel = name.includes(":") ? name.split(":")[1] : name;
3895
+ const cx = r.x + r.width / 2;
3896
+ const cy = r.y + r.height / 2;
3897
+ return [
3898
+ `<g id="${escId(node.id)}">`,
3899
+ `<rect x="${r.x}" y="${r.y}" width="${r.width}" height="${r.height}" fill="#f5f5f5" stroke="#ddd" stroke-width="1" rx="4"/>`,
3900
+ `<text x="${cx}" y="${cy}" text-anchor="middle" dominant-baseline="central" font-size="${fontSize}" fill="${fg}">${escText(shortLabel)}</text>`,
3901
+ `</g>`
3902
+ ].join("");
3903
+ }
3904
+ function renderSvgAsset(node) {
3905
+ const r = node.worldRect;
3906
+ const src = node.styles.src;
3907
+ if (!src) {
3908
+ return `<g id="${escId(node.id)}"><rect x="${r.x}" y="${r.y}" width="${r.width}" height="${r.height}" fill="#eee" stroke="#ccc" stroke-width="1" stroke-dasharray="4 2"/></g>`;
3909
+ }
3910
+ return `<g id="${escId(node.id)}"><image href="${escAttr(src)}" x="${r.x}" y="${r.y}" width="${r.width}" height="${r.height}" preserveAspectRatio="xMidYMid meet"/></g>`;
3911
+ }
3912
+ function renderInlineSvg(id, r, svgContent) {
3913
+ const viewBoxMatch = svgContent.match(/viewBox="([^"]+)"/);
3914
+ const viewBox = viewBoxMatch ? viewBoxMatch[1] : `0 0 ${r.width} ${r.height}`;
3915
+ const inner = svgContent.replace(/<svg[^>]*>/, "").replace(/<\/svg>\s*$/, "");
3916
+ return [
3917
+ `<g id="${escId(id)}">`,
3918
+ `<svg x="${r.x}" y="${r.y}" width="${r.width}" height="${r.height}" viewBox="${viewBox}">`,
3919
+ inner,
3920
+ `</svg>`,
3921
+ `</g>`
3922
+ ].join("");
3923
+ }
3924
+ function escAttr(s) {
3925
+ return String(s).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3926
+ }
3927
+ function escText(s) {
3928
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3929
+ }
3930
+ var iconRegistry;
3931
+ function renderNode(node) {
3932
+ const parts = [];
3933
+ if (CONSTRAINT_KINDS.has(node.kind)) {
3934
+ return "";
3935
+ }
3936
+ if (node.kind === "spacer") {
3937
+ return "";
3938
+ }
3939
+ if (LAYOUT_KINDS.has(node.kind)) {
3940
+ if (node.styles.background != null) {
3941
+ parts.push(renderShape("rect", node.worldRect, { fill: node.styles.background }));
3942
+ }
3943
+ if (node.children) {
3944
+ for (const child of node.children) {
3945
+ if (node.kind === "row" && child.kind === "cell" && node.styles.fill != null && child.styles.fill == null) {
3946
+ child.styles.fill = node.styles.fill;
3947
+ }
3948
+ const childSvg = renderNode(child);
3949
+ if (childSvg) parts.push(childSvg);
3950
+ }
3951
+ }
3952
+ if (parts.length === 0) return "";
3953
+ return `<g id="${escId(node.id)}">${parts.join("")}</g>`;
3954
+ }
3955
+ if (node.kind === "table") {
3956
+ const r = node.worldRect;
3957
+ const rx = Number(node.styles.borderRadius ?? 6);
3958
+ const borderStroke = String(node.styles.stroke ?? "#d1d5db");
3959
+ const borderSw = Number(node.styles.strokeWidth ?? 1);
3960
+ const tableFill = String(node.styles.background ?? "white");
3961
+ const clipId = `table-clip-${++clipIdCounter}`;
3962
+ const childParts = [];
3963
+ if (node.children) {
3964
+ for (const child of node.children) {
3965
+ const childSvg = renderNode(child);
3966
+ if (childSvg) childParts.push(childSvg);
3967
+ }
3968
+ }
3969
+ const clipDef = `<defs><clipPath id="${clipId}"><rect x="${r.x}" y="${r.y}" width="${r.width}" height="${r.height}" rx="${rx}" ry="${rx}"/></clipPath></defs>`;
3970
+ const bg = `<rect x="${r.x}" y="${r.y}" width="${r.width}" height="${r.height}" rx="${rx}" ry="${rx}" fill="${tableFill}"/>`;
3971
+ const clipped = `<g clip-path="url(#${clipId})">${childParts.join("")}</g>`;
3972
+ const border = `<rect x="${r.x}" y="${r.y}" width="${r.width}" height="${r.height}" rx="${rx}" ry="${rx}" fill="none" stroke="${borderStroke}" stroke-width="${borderSw}"/>`;
3973
+ return `<g id="${escId(node.id)}">${clipDef}${bg}${clipped}${border}</g>`;
3974
+ }
3975
+ if (node.kind === "box") {
3976
+ const shape = node.styles.shape != null ? String(node.styles.shape) : "rect";
3977
+ parts.push(renderShape(shape, node.worldRect, node.styles));
3978
+ if (node.children) {
3979
+ for (const child of node.children) {
3980
+ const childSvg = renderNode(child);
3981
+ if (childSvg) parts.push(childSvg);
3982
+ }
3983
+ }
3984
+ return `<g id="${escId(node.id)}">${parts.join("")}</g>`;
3985
+ }
3986
+ if (node.kind === "text" || node.kind === "label") {
3987
+ const textSvg = renderText(node);
3988
+ if (!textSvg) return "";
3989
+ return `<g id="${escId(node.id)}">${textSvg}</g>`;
3990
+ }
3991
+ if (node.kind === "connector") {
3992
+ const connSvg = renderConnector(node);
3993
+ const labelSvg = (node.children ?? []).filter((c) => c.kind === "label" || c.kind === "text").map(renderConnectorLabel).join("");
3994
+ return `<g id="${escId(node.id)}">${connSvg}${labelSvg}</g>`;
3995
+ }
3996
+ if (node.kind === "divider") {
3997
+ const r = node.worldRect;
3998
+ const stroke = node.styles.stroke ?? "#ccc";
3999
+ const sw = node.styles.strokeWidth ?? 1;
4000
+ const line = r.width >= r.height ? `<line x1="${r.x}" y1="${r.y}" x2="${r.x + r.width}" y2="${r.y}" stroke="${stroke}" stroke-width="${sw}"/>` : `<line x1="${r.x}" y1="${r.y}" x2="${r.x}" y2="${r.y + r.height}" stroke="${stroke}" stroke-width="${sw}"/>`;
4001
+ return `<g id="${escId(node.id)}">${line}</g>`;
4002
+ }
4003
+ if (node.kind === "cell") {
4004
+ const r = node.worldRect;
4005
+ const cellParts = [];
4006
+ const cellFill = node.styles.fill ?? "none";
4007
+ const cellStroke = node.styles.stroke ?? "#e5e7eb";
4008
+ const cellSw = node.styles.strokeWidth ?? 0.5;
4009
+ cellParts.push(`<rect x="${r.x}" y="${r.y}" width="${r.width}" height="${r.height}" fill="${cellFill}" stroke="${cellStroke}" stroke-width="${cellSw}"/>`);
4010
+ if (node.children) {
4011
+ for (const child of node.children) {
4012
+ const childSvg = renderNode(child);
4013
+ if (childSvg) cellParts.push(childSvg);
4014
+ }
4015
+ }
4016
+ return `<g id="${escId(node.id)}">${cellParts.join("")}</g>`;
4017
+ }
4018
+ if (node.kind === "image") {
4019
+ return renderImage(node);
4020
+ }
4021
+ if (node.kind === "icon") {
4022
+ return renderIcon(node, iconRegistry);
4023
+ }
4024
+ if (node.kind === "svgAsset") {
4025
+ return renderSvgAsset(node);
4026
+ }
4027
+ return "";
4028
+ }
4029
+ function escId(id) {
4030
+ return id.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
4031
+ }
4032
+ function renderSvg(artifact, options) {
4033
+ clipIdCounter = 0;
4034
+ resetDynamicMarkers();
4035
+ iconRegistry = options?.iconRegistry;
4036
+ const vb = artifact.viewBox;
4037
+ const body = [];
4038
+ if (artifact.background != null) {
4039
+ body.push(`<rect x="${vb.x}" y="${vb.y}" width="${vb.width}" height="${vb.height}" fill="${artifact.background}"/>`);
4040
+ }
4041
+ for (const node of artifact.nodes) {
4042
+ const nodeSvg = renderNode(node);
4043
+ if (nodeSvg) body.push(nodeSvg);
4044
+ }
4045
+ const dynamicDefs = getDynamicMarkerDefs();
4046
+ const defaultDefs = renderArrowMarkers();
4047
+ const defs = dynamicDefs ? defaultDefs.replace("</defs>", dynamicDefs + "\n</defs>") : defaultDefs;
4048
+ return [
4049
+ `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${vb.x} ${vb.y} ${vb.width} ${vb.height}" width="${vb.width}" height="${vb.height}"${artifact.fontFamily ? ` font-family="${artifact.fontFamily}"` : ""}>`,
4050
+ defs,
4051
+ body.join("\n"),
4052
+ "</svg>"
4053
+ ].join("\n");
4054
+ }
4055
+
4056
+ // src/icons/registry.ts
4057
+ function createIconRegistry() {
4058
+ const packs = /* @__PURE__ */ new Map();
4059
+ return {
4060
+ register(pack) {
4061
+ packs.set(pack.provider, pack);
4062
+ },
4063
+ resolve(provider, name) {
4064
+ return packs.get(provider)?.resolve(name);
4065
+ },
4066
+ resolveQualified(qualifiedName) {
4067
+ const colonIdx = qualifiedName.indexOf(":");
4068
+ if (colonIdx < 0) return void 0;
4069
+ const provider = qualifiedName.slice(0, colonIdx);
4070
+ const name = qualifiedName.slice(colonIdx + 1);
4071
+ return packs.get(provider)?.resolve(name);
4072
+ }
4073
+ };
4074
+ }
4075
+
4076
+ // src/icons/aws-icon-pack.ts
4077
+ import { readFileSync } from "fs";
4078
+ import { createRequire } from "module";
4079
+ var SHORT_NAME_MAP = {
4080
+ // Architecture service icons
4081
+ lambda: "architecture-service/AWSLambda",
4082
+ dynamodb: "architecture-service/AmazonDynamoDB",
4083
+ apigateway: "architecture-service/AmazonAPIGateway",
4084
+ s3: "architecture-service/AmazonSimpleStorageService",
4085
+ ec2: "architecture-service/AmazonEC2",
4086
+ rds: "architecture-service/AmazonRDS",
4087
+ sqs: "architecture-service/AmazonSQS",
4088
+ sns: "architecture-service/AmazonSNS",
4089
+ cloudfront: "architecture-service/AmazonCloudFront",
4090
+ ecs: "architecture-service/AmazonElasticContainerService",
4091
+ eks: "architecture-service/AmazonElasticKubernetesService",
4092
+ fargate: "architecture-service/AWSFargate",
4093
+ elasticloadbalancing: "architecture-service/ElasticLoadBalancing",
4094
+ cloudwatch: "architecture-service/AmazonCloudWatch",
4095
+ iam: "architecture-service/AWSIdentityandAccessManagement",
4096
+ route53: "architecture-service/AmazonRoute53",
4097
+ cloudformation: "architecture-service/AWSCloudFormation",
4098
+ stepfunctions: "architecture-service/AWSStepFunctions",
4099
+ kinesis: "architecture-service/AmazonKinesis",
4100
+ elasticache: "architecture-service/AmazonElastiCache",
4101
+ secretsmanager: "architecture-service/AWSSecretsManager",
4102
+ codepipeline: "architecture-service/AWSCodePipeline",
4103
+ codebuild: "architecture-service/AWSCodeBuild",
4104
+ cognito: "architecture-service/AmazonCognito",
4105
+ eventbridge: "architecture-service/AmazonEventBridge",
4106
+ waf: "architecture-service/AWSWAF",
4107
+ aurora: "architecture-service/AmazonAurora",
4108
+ redshift: "architecture-service/AmazonRedshift",
4109
+ glue: "architecture-service/AWSGlue",
4110
+ dms: "architecture-service/AWSDatabaseMigrationService",
4111
+ directconnect: "architecture-service/AWSDirectConnect",
4112
+ natgateway: "architecture-service/AmazonVPCLattice",
4113
+ // Architecture group icons
4114
+ "vpc-group": "architecture-group/VirtualprivatecloudVPC",
4115
+ "region-group": "architecture-group/Region",
4116
+ "subnet-public": "architecture-group/Publicsubnet",
4117
+ "subnet-private": "architecture-group/Privatesubnet",
4118
+ "auto-scaling-group": "architecture-group/AutoScalinggroup",
4119
+ "aws-cloud": "architecture-group/AWSCloud",
4120
+ "aws-account": "architecture-group/AWSAccount"
4121
+ };
4122
+ var svgCache = /* @__PURE__ */ new Map();
4123
+ function tryResolveAwsIconsPath() {
4124
+ try {
4125
+ const esmRequire = createRequire(import.meta.url);
4126
+ const resolved = esmRequire.resolve("aws-icons/package.json");
4127
+ return resolved.replace(/package\.json$/, "icons/");
4128
+ } catch {
4129
+ return void 0;
4130
+ }
4131
+ }
4132
+ var awsIconsBasePath = null;
4133
+ function getBasePath() {
4134
+ if (awsIconsBasePath === null) {
4135
+ awsIconsBasePath = tryResolveAwsIconsPath();
4136
+ }
4137
+ return awsIconsBasePath ?? void 0;
4138
+ }
4139
+ function loadSvg(relativePath) {
4140
+ const cached = svgCache.get(relativePath);
4141
+ if (cached) return cached;
4142
+ const base = getBasePath();
4143
+ if (!base) return void 0;
4144
+ try {
4145
+ const svg = readFileSync(base + relativePath + ".svg", "utf-8");
4146
+ svgCache.set(relativePath, svg);
4147
+ return svg;
4148
+ } catch {
4149
+ return void 0;
4150
+ }
4151
+ }
4152
+ function createAwsIconPack() {
4153
+ return {
4154
+ provider: "aws",
4155
+ resolve(name) {
4156
+ const mapped = SHORT_NAME_MAP[name];
4157
+ if (mapped) return loadSvg(mapped);
4158
+ return loadSvg("architecture-service/" + name);
4159
+ },
4160
+ list() {
4161
+ return Object.keys(SHORT_NAME_MAP);
4162
+ }
4163
+ };
4164
+ }
4165
+
4166
+ // src/graph/resolver.ts
4167
+ var PRIORITY_RANK = {
4168
+ required: 4,
4169
+ strong: 3,
4170
+ normal: 2,
4171
+ weak: 1
4172
+ };
4173
+ function resolveSlot(candidates, policy) {
4174
+ const resolved = candidates.filter((c) => c.value !== void 0);
4175
+ if (resolved.length === 0) return void 0;
4176
+ switch (policy) {
4177
+ case "requiredAllEqual":
4178
+ return resolveRequiredAllEqual(resolved);
4179
+ case "highestPriority":
4180
+ return resolveHighestPriority(resolved);
4181
+ case "max":
4182
+ return Math.max(...resolved.map((c) => c.value));
4183
+ case "sum":
4184
+ return resolved.reduce((acc, c) => acc + c.value, 0);
4185
+ }
4186
+ }
4187
+ function resolveRequiredAllEqual(candidates) {
4188
+ const requiredCandidates = candidates.filter((c) => c.priority === "required");
4189
+ if (requiredCandidates.length === 0) {
4190
+ return resolveHighestPriority(candidates);
4191
+ }
4192
+ const firstValue = requiredCandidates[0].value;
4193
+ for (let i = 1; i < requiredCandidates.length; i++) {
4194
+ if (requiredCandidates[i].value !== firstValue) {
4195
+ throw new Error(
4196
+ `Required conflict: slot has conflicting required values ${firstValue} (from ${requiredCandidates[0].source}) vs ${requiredCandidates[i].value} (from ${requiredCandidates[i].source})`
4197
+ );
4198
+ }
4199
+ }
4200
+ return firstValue;
4201
+ }
4202
+ function resolveHighestPriority(candidates) {
4203
+ if (candidates.length === 0) return void 0;
4204
+ let best = candidates[0];
4205
+ for (let i = 1; i < candidates.length; i++) {
4206
+ if (PRIORITY_RANK[candidates[i].priority] > PRIORITY_RANK[best.priority]) {
4207
+ best = candidates[i];
4208
+ }
4209
+ }
4210
+ return best.value;
4211
+ }
4212
+
4213
+ // src/graph/fragments.ts
4214
+ var ALL_KINDS = [
4215
+ "document",
4216
+ "vstack",
4217
+ "hstack",
4218
+ "box",
4219
+ "text",
4220
+ "label",
4221
+ "connector",
4222
+ "group",
4223
+ "overlay",
4224
+ "grid",
4225
+ "flow",
4226
+ "absolute",
4227
+ "spacer",
4228
+ "divider",
4229
+ "image",
4230
+ "icon",
4231
+ "svgAsset",
4232
+ "table",
4233
+ "row",
4234
+ "cell",
4235
+ "alignConstraint",
4236
+ "equalizeWidth",
4237
+ "equalizeHeight",
4238
+ "orderConstraint",
4239
+ "minGap",
4240
+ "keepInsideConstraint"
4241
+ ];
4242
+ function createFragmentFactory(kind) {
4243
+ return (node, graph, parentId) => {
4244
+ if (!applyConstraintFromRule(kind, node, graph)) {
4245
+ applyLayoutFromRule(kind, node, graph, parentId);
4246
+ }
4247
+ };
4248
+ }
4249
+ var FRAGMENT_MAP = {};
4250
+ for (const kind of ALL_KINDS) {
4251
+ FRAGMENT_MAP[kind] = createFragmentFactory(kind);
4252
+ }
4253
+
4254
+ // src/graph/evaluator.ts
4255
+ function createGraph() {
4256
+ return {
4257
+ slots: /* @__PURE__ */ new Map(),
4258
+ operations: /* @__PURE__ */ new Map(),
4259
+ candidates: [],
4260
+ candidatesBySlot: /* @__PURE__ */ new Map(),
4261
+ candidatesByOperation: /* @__PURE__ */ new Map(),
4262
+ resolvers: /* @__PURE__ */ new Map(),
4263
+ dependencies: [],
4264
+ parentMap: /* @__PURE__ */ new Map(),
4265
+ nodeKindMap: /* @__PURE__ */ new Map(),
4266
+ childrenOrderMap: /* @__PURE__ */ new Map(),
4267
+ diagnostics: []
4268
+ };
4269
+ }
4270
+ function evaluate(graph, ir) {
4271
+ const diagnostics = [];
4272
+ buildFragments(graph, ir, void 0);
4273
+ diagnostics.push(...graph.diagnostics);
4274
+ runEvaluation(graph, diagnostics);
4275
+ const artifact = collectArtifact(graph, ir);
4276
+ return { artifact, diagnostics };
4277
+ }
4278
+ function buildFragments(graph, node, parentId) {
4279
+ const factory = FRAGMENT_MAP[node.kind];
4280
+ if (factory) {
4281
+ factory(node, graph, parentId);
4282
+ }
4283
+ const nodeId = node.id;
4284
+ if (nodeId && !isConstraintKind(node.kind)) {
4285
+ graph.nodeKindMap.set(nodeId, node.kind);
4286
+ if (parentId) {
4287
+ graph.parentMap.set(nodeId, parentId);
4288
+ }
4289
+ addWorldCoordOps(graph, nodeId, parentId);
4290
+ }
4291
+ if (node.children) {
4292
+ for (const child of node.children) {
4293
+ buildFragments(graph, child, node.id);
4294
+ }
4295
+ }
4296
+ }
4297
+ function addWorldCoordOps(graph, nodeId, parentId) {
4298
+ ensureSlot(graph, nodeId, "worldX");
4299
+ ensureSlot(graph, nodeId, "worldY");
4300
+ if (!parentId) {
4301
+ addCandidate(graph, slotId(nodeId, "worldX"), 0, `root.worldX`, "weak");
4302
+ addCandidate(graph, slotId(nodeId, "worldY"), 0, `root.worldY`, "weak");
4303
+ return;
4304
+ }
4305
+ ensureSlot(graph, parentId, "worldX");
4306
+ ensureSlot(graph, parentId, "worldY");
4307
+ ensureSlot(graph, nodeId, "localX");
4308
+ ensureSlot(graph, nodeId, "localY");
4309
+ const wxOpId = `op.worldX.${nodeId}`;
4310
+ addCandidate(graph, slotId(nodeId, "worldX"), void 0, `worldCoord(${nodeId})`, "weak", wxOpId);
4311
+ addOperation(graph, wxOpId, "add", [slotId(parentId, "worldX"), slotId(nodeId, "localX")], [slotId(nodeId, "worldX")], (inputs) => {
4312
+ return [(inputs[0] ?? 0) + (inputs[1] ?? 0)];
4313
+ });
4314
+ const wyOpId = `op.worldY.${nodeId}`;
4315
+ addCandidate(graph, slotId(nodeId, "worldY"), void 0, `worldCoord(${nodeId})`, "weak", wyOpId);
4316
+ addOperation(graph, wyOpId, "add", [slotId(parentId, "worldY"), slotId(nodeId, "localY")], [slotId(nodeId, "worldY")], (inputs) => {
4317
+ return [(inputs[0] ?? 0) + (inputs[1] ?? 0)];
4318
+ });
4319
+ }
4320
+ function runEvaluation(graph, diagnostics) {
4321
+ const order = topologicalSort(graph, diagnostics);
4322
+ resolveAllSlots(graph, diagnostics);
4323
+ function executeOp(opId) {
4324
+ const op = graph.operations.get(opId);
4325
+ if (!op) return;
4326
+ const inputs = op.inputs.map((sid) => {
4327
+ const slot = graph.slots.get(sid);
4328
+ return slot?.value ?? 0;
4329
+ });
4330
+ const outputs = op.compute(inputs);
4331
+ const opCandidates = graph.candidatesByOperation.get(opId);
4332
+ if (opCandidates) {
4333
+ for (let i = 0; i < op.outputs.length && i < outputs.length; i++) {
4334
+ const targetSlotId = op.outputs[i];
4335
+ const val = outputs[i];
4336
+ for (const c of opCandidates) {
4337
+ if (c.targetSlotId === targetSlotId) {
4338
+ c.value = val;
4339
+ }
4340
+ }
4341
+ }
4342
+ }
4343
+ resolveOutputSlots(graph, op.outputs, diagnostics);
4344
+ }
4345
+ for (const opId of order) {
4346
+ executeOp(opId);
4347
+ }
4348
+ for (let pass = 0; pass < 2; pass++) {
4349
+ for (const opId of order) {
4350
+ const op = graph.operations.get(opId);
4351
+ if (!op) continue;
4352
+ if (op.op === "suffixShiftDelta" || op.op === "stackCompose" || op.op === "add") {
4353
+ executeOp(opId);
4354
+ }
4355
+ }
4356
+ }
4357
+ resolveAllSlots(graph, diagnostics);
4358
+ }
4359
+ function resolveAllSlots(graph, diagnostics) {
4360
+ for (const [sid, slot] of graph.slots) {
4361
+ const candidates = graph.candidatesBySlot.get(sid);
4362
+ if (!candidates || candidates.length === 0) continue;
4363
+ const resolver = graph.resolvers.get(sid);
4364
+ const policy = resolver?.policy ?? "highestPriority";
4365
+ try {
4366
+ const value = resolveSlot(candidates, policy);
4367
+ if (value !== void 0) {
4368
+ slot.value = value;
4369
+ }
4370
+ } catch (err) {
4371
+ diagnostics.push({
4372
+ level: "error",
4373
+ message: err.message,
4374
+ path: sid
4375
+ });
4376
+ }
4377
+ }
4378
+ }
4379
+ function resolveOutputSlots(graph, outputSlots, diagnostics) {
4380
+ const seen = /* @__PURE__ */ new Set();
4381
+ for (const sid of outputSlots) {
4382
+ if (seen.has(sid)) continue;
4383
+ seen.add(sid);
4384
+ const slot = graph.slots.get(sid);
4385
+ if (!slot) continue;
4386
+ const candidates = graph.candidatesBySlot.get(sid);
4387
+ if (!candidates || candidates.length === 0) continue;
4388
+ const resolver = graph.resolvers.get(sid);
4389
+ const policy = resolver?.policy ?? "highestPriority";
4390
+ try {
4391
+ const value = resolveSlot(candidates, policy);
4392
+ if (value !== void 0) {
4393
+ slot.value = value;
4394
+ }
4395
+ } catch (err) {
4396
+ diagnostics.push({
4397
+ level: "error",
4398
+ message: err.message,
4399
+ path: sid
4400
+ });
4401
+ }
4402
+ }
4403
+ }
4404
+ function topologicalSort(graph, diagnostics) {
4405
+ const inDegree = /* @__PURE__ */ new Map();
4406
+ const adj = /* @__PURE__ */ new Map();
4407
+ const edgeSet = /* @__PURE__ */ new Set();
4408
+ for (const opId of graph.operations.keys()) {
4409
+ inDegree.set(opId, 0);
4410
+ adj.set(opId, []);
4411
+ }
4412
+ const slotToOps = /* @__PURE__ */ new Map();
4413
+ for (const dep of graph.dependencies) {
4414
+ const ops = slotToOps.get(dep.fromSlotId) ?? [];
4415
+ ops.push(dep.toOperationId);
4416
+ slotToOps.set(dep.fromSlotId, ops);
4417
+ }
4418
+ function addEdge(fromOpId, toOpId) {
4419
+ if (fromOpId === toOpId) return;
4420
+ if (!graph.operations.has(toOpId)) return;
4421
+ const key = `${fromOpId}\0${toOpId}`;
4422
+ if (edgeSet.has(key)) return;
4423
+ edgeSet.add(key);
4424
+ adj.get(fromOpId).push(toOpId);
4425
+ inDegree.set(toOpId, (inDegree.get(toOpId) ?? 0) + 1);
4426
+ }
4427
+ for (const op of graph.operations.values()) {
4428
+ for (const outputSlotId of op.outputs) {
4429
+ const dependentOps = slotToOps.get(outputSlotId);
4430
+ if (dependentOps) {
4431
+ for (const depOpId of dependentOps) {
4432
+ addEdge(op.id, depOpId);
4433
+ }
4434
+ }
4435
+ }
4436
+ }
4437
+ for (const candidate of graph.candidates) {
4438
+ if (candidate.operationId) {
4439
+ const dependentOps = slotToOps.get(candidate.targetSlotId);
4440
+ if (dependentOps) {
4441
+ for (const depOpId of dependentOps) {
4442
+ addEdge(candidate.operationId, depOpId);
4443
+ }
4444
+ }
4445
+ }
4446
+ }
4447
+ const queue = [];
4448
+ for (const [opId, deg] of inDegree) {
4449
+ if (deg === 0) queue.push(opId);
4450
+ }
4451
+ let head = 0;
4452
+ const result = [];
4453
+ while (head < queue.length) {
4454
+ const opId = queue[head++];
4455
+ result.push(opId);
4456
+ for (const next of adj.get(opId) ?? []) {
4457
+ const newDeg = (inDegree.get(next) ?? 1) - 1;
4458
+ inDegree.set(next, newDeg);
4459
+ if (newDeg === 0) {
4460
+ queue.push(next);
4461
+ }
4462
+ }
4463
+ }
4464
+ if (result.length < graph.operations.size) {
4465
+ let dfs2 = function(node) {
4466
+ visited.add(node);
4467
+ stack.add(node);
4468
+ for (const next of adj.get(node) ?? []) {
4469
+ if (!unSet.has(next)) continue;
4470
+ if (stack.has(next)) {
4471
+ cyclePath = [next];
4472
+ let cur = node;
4473
+ while (cur !== next) {
4474
+ cyclePath.unshift(cur);
4475
+ cur = parent.get(cur);
4476
+ }
4477
+ cyclePath.unshift(next);
4478
+ return true;
4479
+ }
4480
+ if (!visited.has(next)) {
4481
+ parent.set(next, node);
4482
+ if (dfs2(next)) return true;
4483
+ }
4484
+ }
4485
+ stack.delete(node);
4486
+ return false;
4487
+ };
4488
+ var dfs = dfs2;
4489
+ const scheduled = new Set(result);
4490
+ const unscheduled = [...graph.operations.keys()].filter((id) => !scheduled.has(id));
4491
+ const unSet = new Set(unscheduled);
4492
+ let cyclePath = [];
4493
+ const visited = /* @__PURE__ */ new Set();
4494
+ const stack = /* @__PURE__ */ new Set();
4495
+ const parent = /* @__PURE__ */ new Map();
4496
+ for (const start of unscheduled) {
4497
+ if (!visited.has(start)) {
4498
+ if (dfs2(start)) break;
4499
+ }
4500
+ }
4501
+ diagnostics.push({
4502
+ level: "warning",
4503
+ message: `Cycle detected: ${unscheduled.length} ops stuck. Path: ${cyclePath.join(" \u2192 ")}`,
4504
+ path: "graph"
4505
+ });
4506
+ }
4507
+ return result;
4508
+ }
4509
+ function collectArtifact(graph, ir) {
4510
+ const explicitWidth = ir.props.width;
4511
+ const explicitHeight = ir.props.height;
4512
+ const nodes = collectNodesWithOffset(graph, ir, 0, 0);
4513
+ fixConnectorPaths(nodes, graph, ir);
4514
+ let vbWidth;
4515
+ let vbHeight;
4516
+ if (explicitWidth != null && explicitHeight != null) {
4517
+ vbWidth = explicitWidth;
4518
+ vbHeight = explicitHeight;
4519
+ } else {
4520
+ const bounds = computeContentBounds(nodes);
4521
+ const padding = typeof ir.props.padding === "number" ? ir.props.padding : 0;
4522
+ vbWidth = explicitWidth ?? bounds.maxX + padding;
4523
+ vbHeight = explicitHeight ?? bounds.maxY + padding;
4524
+ }
4525
+ const viewBox = { x: 0, y: 0, width: vbWidth, height: vbHeight };
4526
+ const background = typeof ir.props.background === "string" ? ir.props.background : void 0;
4527
+ const fontFamily = typeof ir.props.fontFamily === "string" ? ir.props.fontFamily : void 0;
4528
+ return { viewBox, nodes, background, fontFamily };
4529
+ }
4530
+ function computeContentBounds(nodes) {
4531
+ let maxX = 0;
4532
+ let maxY = 0;
4533
+ for (const node of nodes) {
4534
+ const r = node.worldRect;
4535
+ maxX = Math.max(maxX, r.x + r.width);
4536
+ maxY = Math.max(maxY, r.y + r.height);
4537
+ if (node.children) {
4538
+ const childBounds = computeContentBounds(node.children);
4539
+ maxX = Math.max(maxX, childBounds.maxX);
4540
+ maxY = Math.max(maxY, childBounds.maxY);
4541
+ }
4542
+ }
4543
+ return { maxX, maxY };
4544
+ }
4545
+ function collectNodesWithOffset(graph, ir, parentWorldX, parentWorldY) {
4546
+ const results = [];
4547
+ if (ir.children) {
4548
+ for (const child of ir.children) {
4549
+ const node = buildResolvedNode(graph, child, parentWorldX, parentWorldY);
4550
+ if (node) results.push(node);
4551
+ }
4552
+ }
4553
+ return results;
4554
+ }
4555
+ function buildResolvedNode(graph, ir, parentWorldX, parentWorldY) {
4556
+ const id = ir.id;
4557
+ const kind = ir.kind;
4558
+ if (kind === "alignConstraint" || kind === "equalizeWidth" || kind === "equalizeHeight" || kind === "minGap" || kind === "keepInsideConstraint" || kind === "orderConstraint") {
4559
+ return null;
4560
+ }
4561
+ const width = getSlotValue(graph, id, "resolvedWidth") ?? 0;
4562
+ const height = getSlotValue(graph, id, "resolvedHeight") ?? 0;
4563
+ const x = getSlotValue(graph, id, "localX") ?? 0;
4564
+ const y = getSlotValue(graph, id, "localY") ?? 0;
4565
+ const rect = { x, y, width, height };
4566
+ const worldRect = {
4567
+ x: x + parentWorldX,
4568
+ y: y + parentWorldY,
4569
+ width,
4570
+ height
4571
+ };
4572
+ const styles = {};
4573
+ for (const [key, value] of Object.entries(ir.props)) {
4574
+ if (typeof value === "string" || typeof value === "number") {
4575
+ styles[key] = value;
4576
+ }
4577
+ }
4578
+ const resolved = {
4579
+ id,
4580
+ kind,
4581
+ rect,
4582
+ worldRect,
4583
+ styles
4584
+ };
4585
+ if (kind === "text" || kind === "label") {
4586
+ const text = ir.props.text;
4587
+ if (text) {
4588
+ resolved.textLines = [text];
4589
+ }
4590
+ const textBaselineVal = getSlotValue(graph, id, "textBaseline");
4591
+ const textXVal = getSlotValue(graph, id, "textX");
4592
+ const textAnchorVal = getSlotValue(graph, id, "textAnchor");
4593
+ if (textBaselineVal !== void 0) {
4594
+ resolved.textBaseline = worldRect.y + textBaselineVal;
4595
+ }
4596
+ if (textXVal !== void 0) {
4597
+ resolved.textX = worldRect.x + textXVal;
4598
+ }
4599
+ if (textAnchorVal !== void 0) {
4600
+ switch (textAnchorVal) {
4601
+ case 0:
4602
+ resolved.textAnchor = "start";
4603
+ break;
4604
+ case 2:
4605
+ resolved.textAnchor = "end";
4606
+ break;
4607
+ default:
4608
+ resolved.textAnchor = "middle";
4609
+ break;
4610
+ }
4611
+ }
4612
+ }
4613
+ if (ir.children && ir.children.length > 0) {
4614
+ const children = [];
4615
+ for (const child of ir.children) {
4616
+ const childNode = buildResolvedNode(graph, child, worldRect.x, worldRect.y);
4617
+ if (childNode) children.push(childNode);
4618
+ }
4619
+ if (children.length > 0) {
4620
+ resolved.children = children;
4621
+ }
4622
+ }
4623
+ return resolved;
4624
+ }
4625
+ function fixConnectorPaths(nodes, _graph, ir) {
4626
+ const worldRectMap = /* @__PURE__ */ new Map();
4627
+ collectWorldRects(nodes, worldRectMap);
4628
+ const connectorInfos = [];
4629
+ for (const node of nodes) {
4630
+ if (node.kind !== "connector") continue;
4631
+ const irNode = findIrNode(ir, node.id);
4632
+ if (!irNode) continue;
4633
+ const fromId = irNode.props.from;
4634
+ const toId = irNode.props.to;
4635
+ if (!fromId || !toId) continue;
4636
+ const fromRect = worldRectMap.get(fromId);
4637
+ const toRect = worldRectMap.get(toId);
4638
+ if (!fromRect || !toRect) continue;
4639
+ const fromAnchor = irNode.props.fromAnchor ?? "auto";
4640
+ const toAnchor = irNode.props.toAnchor ?? "auto";
4641
+ const fromEdge = resolveEdge(fromAnchor, fromRect, toRect);
4642
+ const toEdge = resolveEdge(toAnchor, toRect, fromRect);
4643
+ connectorInfos.push({ node, irNode, fromId, toId, fromAnchor, toAnchor, fromEdge, toEdge });
4644
+ }
4645
+ const edgeGroups = /* @__PURE__ */ new Map();
4646
+ for (const info of connectorInfos) {
4647
+ const fromKey = `${info.fromId}:${info.fromEdge}`;
4648
+ const toKey = `${info.toId}:${info.toEdge}`;
4649
+ if (!edgeGroups.has(fromKey)) edgeGroups.set(fromKey, []);
4650
+ edgeGroups.get(fromKey).push({
4651
+ nodeId: info.fromId,
4652
+ edge: info.fromEdge,
4653
+ anchor: info.fromAnchor,
4654
+ connectorId: info.node.id,
4655
+ role: "from"
4656
+ });
4657
+ if (!edgeGroups.has(toKey)) edgeGroups.set(toKey, []);
4658
+ edgeGroups.get(toKey).push({
4659
+ nodeId: info.toId,
4660
+ edge: info.toEdge,
4661
+ anchor: info.toAnchor,
4662
+ connectorId: info.node.id,
4663
+ role: "to"
4664
+ });
4665
+ }
4666
+ const endpointPositions = /* @__PURE__ */ new Map();
4667
+ for (const [_key, endpoints] of edgeGroups) {
4668
+ endpoints.sort((a, b) => {
4669
+ const aInfo = connectorInfos.find((c) => c.node.id === a.connectorId);
4670
+ const bInfo = connectorInfos.find((c) => c.node.id === b.connectorId);
4671
+ const aOtherId = a.role === "from" ? aInfo.toId : aInfo.fromId;
4672
+ const bOtherId = b.role === "from" ? bInfo.toId : bInfo.fromId;
4673
+ const aOther = worldRectMap.get(aOtherId);
4674
+ const bOther = worldRectMap.get(bOtherId);
4675
+ if (a.edge === "top" || a.edge === "bottom") {
4676
+ return aOther.x + aOther.width / 2 - (bOther.x + bOther.width / 2);
4677
+ }
4678
+ return aOther.y + aOther.height / 2 - (bOther.y + bOther.height / 2);
4679
+ });
4680
+ const n = endpoints.length;
4681
+ for (let i = 0; i < n; i++) {
4682
+ const ep = endpoints[i];
4683
+ const rect = worldRectMap.get(ep.nodeId);
4684
+ const t = (i + 1) / (n + 1);
4685
+ const pos = edgePoint(rect, ep.edge, t);
4686
+ const posKey = `${ep.connectorId}:${ep.role}`;
4687
+ endpointPositions.set(posKey, pos);
4688
+ }
4689
+ }
4690
+ for (const info of connectorInfos) {
4691
+ const from = endpointPositions.get(`${info.node.id}:from`);
4692
+ const to = endpointPositions.get(`${info.node.id}:to`);
4693
+ const route = info.irNode.props.route ?? "straight";
4694
+ if (route === "orthogonal") {
4695
+ const cr = Number(info.irNode.props.cornerRadius ?? 0);
4696
+ info.node.connectorPath = buildOrthogonalPath(from, to, info.fromEdge, info.toEdge, cr, worldRectMap.get(info.fromId), worldRectMap.get(info.toId));
4697
+ } else if (route === "curved") {
4698
+ const curvature = Number(info.irNode.props.curvature ?? 0.33);
4699
+ info.node.connectorPath = buildCurvedPath(from, to, info.fromEdge, info.toEdge, curvature);
4700
+ } else {
4701
+ info.node.connectorPath = `M ${from.x} ${from.y} L ${to.x} ${to.y}`;
4702
+ }
4703
+ positionConnectorLabels(info.node, from, to);
4704
+ }
4705
+ }
4706
+ function positionConnectorLabels(connector, from, to) {
4707
+ const labels = (connector.children ?? []).filter((c) => c.kind === "label" || c.kind === "text");
4708
+ if (labels.length === 0) return;
4709
+ const midX = (from.x + to.x) / 2;
4710
+ const midY = (from.y + to.y) / 2;
4711
+ for (const label of labels) {
4712
+ const { width, height } = label.worldRect;
4713
+ const newX = midX - width / 2;
4714
+ const newY = midY - height / 2;
4715
+ const dx = newX - label.worldRect.x;
4716
+ const dy = newY - label.worldRect.y;
4717
+ label.worldRect.x = newX;
4718
+ label.worldRect.y = newY;
4719
+ if (label.textX != null) label.textX += dx;
4720
+ if (label.textBaseline != null) label.textBaseline += dy;
4721
+ }
4722
+ }
4723
+ var ORTHO_MARGIN = 20;
4724
+ function edgeNormal(edge) {
4725
+ switch (edge) {
4726
+ case "top":
4727
+ return { dx: 0, dy: -1 };
4728
+ case "bottom":
4729
+ return { dx: 0, dy: 1 };
4730
+ case "left":
4731
+ return { dx: -1, dy: 0 };
4732
+ case "right":
4733
+ return { dx: 1, dy: 0 };
4734
+ }
4735
+ }
4736
+ function isHorizontalEdge(edge) {
4737
+ return edge === "top" || edge === "bottom";
4738
+ }
4739
+ function buildOrthogonalPath(from, to, fromEdge, toEdge, cornerRadius, fromRect, toRect) {
4740
+ const waypoints = computeOrthogonalWaypoints(from, to, fromEdge, toEdge, fromRect, toRect);
4741
+ return waypointsToPath(waypoints, cornerRadius);
4742
+ }
4743
+ function computeOrthogonalWaypoints(from, to, fromEdge, toEdge, fromRect, toRect) {
4744
+ const pts2 = [from];
4745
+ const fromH = isHorizontalEdge(fromEdge);
4746
+ const toH = isHorizontalEdge(toEdge);
4747
+ const fn = edgeNormal(fromEdge);
4748
+ const tn = edgeNormal(toEdge);
4749
+ if (fromEdge === toEdge) {
4750
+ const offset = ORTHO_MARGIN;
4751
+ if (fromH) {
4752
+ const outY = fn.dy > 0 ? Math.max(fromRect.y + fromRect.height, toRect.y + toRect.height) + offset : Math.min(fromRect.y, toRect.y) - offset;
4753
+ pts2.push({ x: from.x, y: outY });
4754
+ pts2.push({ x: to.x, y: outY });
4755
+ } else {
4756
+ const outX = fn.dx > 0 ? Math.max(fromRect.x + fromRect.width, toRect.x + toRect.width) + offset : Math.min(fromRect.x, toRect.x) - offset;
4757
+ pts2.push({ x: outX, y: from.y });
4758
+ pts2.push({ x: outX, y: to.y });
4759
+ }
4760
+ } else if (fromH === toH) {
4761
+ if (fromH) {
4762
+ const midY = (from.y + to.y) / 2;
4763
+ pts2.push({ x: from.x, y: midY });
4764
+ pts2.push({ x: to.x, y: midY });
4765
+ } else {
4766
+ const midX = (from.x + to.x) / 2;
4767
+ pts2.push({ x: midX, y: from.y });
4768
+ pts2.push({ x: midX, y: to.y });
4769
+ }
4770
+ } else {
4771
+ if (fromH) {
4772
+ pts2.push({ x: from.x, y: to.y + tn.dy * 0 });
4773
+ if (Math.abs(from.x - to.x) > 0.1) {
4774
+ pts2.push({ x: from.x, y: to.y });
4775
+ }
4776
+ } else {
4777
+ pts2.push({ x: to.x, y: from.y });
4778
+ }
4779
+ }
4780
+ pts2.push(to);
4781
+ return pts2;
4782
+ }
4783
+ function waypointsToPath(pts2, cornerRadius) {
4784
+ if (pts2.length < 2) return "";
4785
+ if (pts2.length === 2 || cornerRadius <= 0) {
4786
+ return "M " + pts2.map((p) => `${p.x} ${p.y}`).join(" L ");
4787
+ }
4788
+ const parts = [`M ${pts2[0].x} ${pts2[0].y}`];
4789
+ for (let i = 1; i < pts2.length - 1; i++) {
4790
+ const prev = pts2[i - 1];
4791
+ const curr = pts2[i];
4792
+ const next = pts2[i + 1];
4793
+ const dxIn = curr.x - prev.x;
4794
+ const dyIn = curr.y - prev.y;
4795
+ const dxOut = next.x - curr.x;
4796
+ const dyOut = next.y - curr.y;
4797
+ const lenIn = Math.sqrt(dxIn * dxIn + dyIn * dyIn);
4798
+ const lenOut = Math.sqrt(dxOut * dxOut + dyOut * dyOut);
4799
+ const r = Math.min(cornerRadius, lenIn / 2, lenOut / 2);
4800
+ if (r < 0.5) {
4801
+ parts.push(`L ${curr.x} ${curr.y}`);
4802
+ continue;
4803
+ }
4804
+ const arcStart = {
4805
+ x: curr.x - dxIn / lenIn * r,
4806
+ y: curr.y - dyIn / lenIn * r
4807
+ };
4808
+ const arcEnd = {
4809
+ x: curr.x + dxOut / lenOut * r,
4810
+ y: curr.y + dyOut / lenOut * r
4811
+ };
4812
+ const cross = dxIn * dyOut - dyIn * dxOut;
4813
+ const sweep = cross > 0 ? 1 : 0;
4814
+ parts.push(`L ${arcStart.x} ${arcStart.y}`);
4815
+ parts.push(`A ${r} ${r} 0 0 ${sweep} ${arcEnd.x} ${arcEnd.y}`);
4816
+ }
4817
+ parts.push(`L ${pts2[pts2.length - 1].x} ${pts2[pts2.length - 1].y}`);
4818
+ return parts.join(" ");
4819
+ }
4820
+ function buildCurvedPath(from, to, fromEdge, toEdge, curvature) {
4821
+ const dx = to.x - from.x;
4822
+ const dy = to.y - from.y;
4823
+ const dist = Math.sqrt(dx * dx + dy * dy);
4824
+ const cpDist = Math.min(dist * Math.max(0.1, Math.min(1, curvature)), 200);
4825
+ const fn = edgeNormal(fromEdge);
4826
+ const tn = edgeNormal(toEdge);
4827
+ const cp1 = { x: from.x + fn.dx * cpDist, y: from.y + fn.dy * cpDist };
4828
+ const cp2 = { x: to.x + tn.dx * cpDist, y: to.y + tn.dy * cpDist };
4829
+ return `M ${from.x} ${from.y} C ${cp1.x} ${cp1.y}, ${cp2.x} ${cp2.y}, ${to.x} ${to.y}`;
4830
+ }
4831
+ function resolveEdge(anchor, selfRect, otherRect) {
4832
+ switch (anchor) {
4833
+ case "top":
4834
+ case "topLeft":
4835
+ case "topRight":
4836
+ return "top";
4837
+ case "bottom":
4838
+ case "bottomLeft":
4839
+ case "bottomRight":
4840
+ return "bottom";
4841
+ case "left":
4842
+ return "left";
4843
+ case "right":
4844
+ return "right";
4845
+ case "center":
4846
+ case "auto":
4847
+ default:
4848
+ return autoDetectEdge(selfRect, otherRect);
4849
+ }
4850
+ }
4851
+ function autoDetectEdge(selfRect, otherRect) {
4852
+ const selfCx = selfRect.x + selfRect.width / 2;
4853
+ const selfCy = selfRect.y + selfRect.height / 2;
4854
+ const otherCx = otherRect.x + otherRect.width / 2;
4855
+ const otherCy = otherRect.y + otherRect.height / 2;
4856
+ const dx = otherCx - selfCx;
4857
+ const dy = otherCy - selfCy;
4858
+ if (Math.abs(dx) > Math.abs(dy)) {
4859
+ return dx > 0 ? "right" : "left";
4860
+ } else {
4861
+ return dy > 0 ? "bottom" : "top";
4862
+ }
4863
+ }
4864
+ function edgePoint(rect, edge, t) {
4865
+ switch (edge) {
4866
+ case "top":
4867
+ return { x: rect.x + rect.width * t, y: rect.y };
4868
+ case "bottom":
4869
+ return { x: rect.x + rect.width * t, y: rect.y + rect.height };
4870
+ case "left":
4871
+ return { x: rect.x, y: rect.y + rect.height * t };
4872
+ case "right":
4873
+ return { x: rect.x + rect.width, y: rect.y + rect.height * t };
4874
+ }
4875
+ }
4876
+ function collectWorldRects(nodes, map) {
4877
+ for (const node of nodes) {
4878
+ map.set(node.id, node.worldRect);
4879
+ if (node.children) {
4880
+ collectWorldRects(node.children, map);
4881
+ }
4882
+ }
4883
+ }
4884
+ function findIrNode(ir, id) {
4885
+ if (ir.id === id) return ir;
4886
+ if (ir.children) {
4887
+ for (const child of ir.children) {
4888
+ const found = findIrNode(child, id);
4889
+ if (found) return found;
4890
+ }
4891
+ }
4892
+ return null;
4893
+ }
4894
+ function getSlotValue(graph, nodeId, slotKind) {
4895
+ const slot = graph.slots.get(`${nodeId}.${slotKind}`);
4896
+ return slot?.value;
4897
+ }
4898
+
4899
+ // src/graph/font-loader.ts
4900
+ import { readFileSync as readFileSync2 } from "fs";
4901
+ import { createRequire as createRequire2 } from "module";
4902
+
4903
+ // src/index.ts
4904
+ function layout(ir, options) {
4905
+ const { ir: normalizedIr, diagnostics: normDiag } = normalize(ir, options);
4906
+ const graph = createGraph();
4907
+ const { artifact, diagnostics: evalDiag } = evaluate(graph, normalizedIr);
4908
+ return {
4909
+ artifact,
4910
+ diagnostics: [...normDiag, ...evalDiag]
4911
+ };
4912
+ }
4913
+ function renderToSvg(ir, options) {
4914
+ const { artifact } = layout(ir, options);
4915
+ return renderSvg(artifact, { iconRegistry: options.iconRegistry });
4916
+ }
4917
+ function renderDrawTextToSvg(source, options) {
4918
+ const { ir } = parseDrawText(source);
4919
+ const opts = {
4920
+ tokens: options?.tokens ?? defaultTokens(),
4921
+ theme: options?.theme ?? defaultTheme()
4922
+ };
4923
+ return renderToSvg(ir, { ...opts, iconRegistry: options?.iconRegistry });
4924
+ }
4925
+ function renderDrawTsxToSvg(source, options) {
4926
+ const { resolveImport, components, iconRegistry: iconRegistry2, ...rest } = options ?? {};
4927
+ const compileOpts = resolveImport || components ? { resolveImport, components } : void 0;
4928
+ const { ir } = compileDrawTsx(source, compileOpts);
4929
+ const opts = {
4930
+ tokens: rest.tokens ?? defaultTokens(),
4931
+ theme: rest.theme ?? defaultTheme()
4932
+ };
4933
+ return renderToSvg(ir, { ...opts, iconRegistry: iconRegistry2 });
4934
+ }
4935
+ export {
4936
+ ELEMENT_CATALOG,
4937
+ FRAGMENT_MAP,
4938
+ IrNodeSchema,
4939
+ PROPERTY_CATALOG,
4940
+ SimpleTextMeasurer,
4941
+ TOKEN_CATALOG,
4942
+ compileDrawTsx,
4943
+ createAwsIconPack,
4944
+ createGraph,
4945
+ createIconRegistry,
4946
+ defaultTheme,
4947
+ defaultTokens,
4948
+ evaluate,
4949
+ kindToPascal,
4950
+ layout,
4951
+ normalize,
4952
+ opClamp,
4953
+ opConstant,
4954
+ opMax,
4955
+ opSum,
4956
+ parseDrawText,
4957
+ parseIr,
4958
+ parseTheme,
4959
+ parseTokens,
4960
+ renderArrowMarkers,
4961
+ renderConnector,
4962
+ renderDrawTextToSvg,
4963
+ renderDrawTsxToSvg,
4964
+ renderShape,
4965
+ renderSvg,
4966
+ renderText,
4967
+ renderToSvg,
4968
+ resolveSlot,
4969
+ resolveTokenRef
4970
+ };