sketchmark 1.1.5 → 1.2.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.
@@ -147,10 +147,16 @@ var AIDiagram = (function (exports) {
147
147
  val += "\n";
148
148
  else if (esc === "t")
149
149
  val += "\t";
150
+ else if (esc === "r")
151
+ val += "\r";
150
152
  else if (esc === "\\")
151
153
  val += "\\";
154
+ else if (esc === q)
155
+ val += q;
156
+ else if (esc)
157
+ val += `\\${esc}`;
152
158
  else
153
- val += esc;
159
+ val += "\\";
154
160
  }
155
161
  else
156
162
  val += src[i];
@@ -227,6 +233,47 @@ var AIDiagram = (function (exports) {
227
233
  return tokens;
228
234
  }
229
235
 
236
+ function pluginMessage(plugin, stage, error) {
237
+ const detail = error instanceof Error ? error.message : String(error);
238
+ return `Plugin "${plugin.name}" ${stage} failed: ${detail}`;
239
+ }
240
+ function applyPluginPreprocessors(source, plugins = []) {
241
+ let nextSource = source;
242
+ for (const plugin of plugins) {
243
+ if (!plugin.preprocess)
244
+ continue;
245
+ try {
246
+ const transformed = plugin.preprocess(nextSource);
247
+ if (typeof transformed !== "string") {
248
+ throw new Error("preprocess must return a string");
249
+ }
250
+ nextSource = transformed;
251
+ }
252
+ catch (error) {
253
+ throw new Error(pluginMessage(plugin, "preprocess", error));
254
+ }
255
+ }
256
+ return nextSource;
257
+ }
258
+ function applyPluginAstTransforms(ast, plugins = []) {
259
+ let nextAst = ast;
260
+ for (const plugin of plugins) {
261
+ if (!plugin.transformAst)
262
+ continue;
263
+ try {
264
+ const transformed = plugin.transformAst(nextAst);
265
+ if (!transformed || transformed.kind !== "diagram") {
266
+ throw new Error('transformAst must return a DiagramAST with kind="diagram"');
267
+ }
268
+ nextAst = transformed;
269
+ }
270
+ catch (error) {
271
+ throw new Error(pluginMessage(plugin, "transformAst", error));
272
+ }
273
+ }
274
+ return nextAst;
275
+ }
276
+
230
277
  // ============================================================
231
278
  // sketchmark - Parser (Tokens -> DiagramAST)
232
279
  // ============================================================
@@ -313,9 +360,10 @@ var AIDiagram = (function (exports) {
313
360
  function isPropKeyToken(t) {
314
361
  return !!t && (t.type === "IDENT" || t.type === "KEYWORD");
315
362
  }
316
- function parse(src) {
363
+ function parse(src, options = {}) {
317
364
  resetUid();
318
- const tokens = tokenize$1(src).filter((t) => t.type !== "NEWLINE" || t.value === "\n");
365
+ const preparedSource = applyPluginPreprocessors(src, options.plugins);
366
+ const tokens = tokenize$1(preparedSource).filter((t) => t.type !== "NEWLINE" || t.value === "\n");
319
367
  const flat = [];
320
368
  let lastNL = false;
321
369
  for (const t of tokens) {
@@ -498,6 +546,7 @@ var AIDiagram = (function (exports) {
498
546
  const toks = lineTokens();
499
547
  const id = requireExplicitId(keywordTok, toks);
500
548
  const props = parseSimpleProps(toks, 1);
549
+ const meta = extractNodeMeta(props);
501
550
  const node = {
502
551
  kind: "node",
503
552
  id,
@@ -505,11 +554,14 @@ var AIDiagram = (function (exports) {
505
554
  label: props.label || "",
506
555
  ...(props.width ? { width: parseFloat(props.width) } : {}),
507
556
  ...(props.height ? { height: parseFloat(props.height) } : {}),
557
+ ...(props.x ? { x: parseFloat(props.x) } : {}),
558
+ ...(props.y ? { y: parseFloat(props.y) } : {}),
508
559
  ...(props.deg ? { deg: parseFloat(props.deg) } : {}),
509
560
  ...(props.dx ? { dx: parseFloat(props.dx) } : {}),
510
561
  ...(props.dy ? { dy: parseFloat(props.dy) } : {}),
511
562
  ...(props.factor ? { factor: parseFloat(props.factor) } : {}),
512
563
  ...(props.theme ? { theme: props.theme } : {}),
564
+ ...(meta ? { meta } : {}),
513
565
  style: propsToStyle(props),
514
566
  };
515
567
  if (props.url)
@@ -534,17 +586,32 @@ var AIDiagram = (function (exports) {
534
586
  j = 2;
535
587
  }
536
588
  Object.assign(props, parseSimpleProps(toks, j));
589
+ const meta = extractNodeMeta(props);
537
590
  return {
538
591
  kind: "node",
539
592
  id,
540
593
  shape: "note",
541
594
  label: (props.label ?? "").replace(/\\n/g, "\n"),
542
595
  theme: props.theme,
596
+ ...(meta ? { meta } : {}),
543
597
  style: propsToStyle(props),
544
598
  ...(props.width ? { width: parseFloat(props.width) } : {}),
545
599
  ...(props.height ? { height: parseFloat(props.height) } : {}),
600
+ ...(props.x ? { x: parseFloat(props.x) } : {}),
601
+ ...(props.y ? { y: parseFloat(props.y) } : {}),
602
+ ...(props.deg ? { deg: parseFloat(props.deg) } : {}),
603
+ ...(props.dx ? { dx: parseFloat(props.dx) } : {}),
604
+ ...(props.dy ? { dy: parseFloat(props.dy) } : {}),
605
+ ...(props.factor ? { factor: parseFloat(props.factor) } : {}),
546
606
  };
547
607
  }
608
+ function extractNodeMeta(props) {
609
+ const meta = {};
610
+ if (props["animation-parent"]) {
611
+ meta.animationParent = props["animation-parent"];
612
+ }
613
+ return Object.keys(meta).length ? meta : undefined;
614
+ }
548
615
  function parseGroup() {
549
616
  const keywordTok = cur();
550
617
  skip();
@@ -582,6 +649,8 @@ var AIDiagram = (function (exports) {
582
649
  justify: props.justify,
583
650
  theme: props.theme,
584
651
  style: propsToStyle(props),
652
+ x: props.x !== undefined ? parseFloat(props.x) : undefined,
653
+ y: props.y !== undefined ? parseFloat(props.y) : undefined,
585
654
  width: props.width !== undefined ? parseFloat(props.width) : undefined,
586
655
  height: props.height !== undefined ? parseFloat(props.height) : undefined,
587
656
  };
@@ -615,6 +684,8 @@ var AIDiagram = (function (exports) {
615
684
  to: toTok.value,
616
685
  connector: connector,
617
686
  label: props.label,
687
+ fromAnchor: props["anchor-from"],
688
+ toAnchor: props["anchor-to"],
618
689
  dashed,
619
690
  bidirectional,
620
691
  style: propsToStyle(props),
@@ -753,6 +824,8 @@ var AIDiagram = (function (exports) {
753
824
  chartType: chartType.replace("-chart", ""),
754
825
  label: props.label ?? props.title,
755
826
  data: { headers, rows },
827
+ x: props.x ? parseFloat(props.x) : undefined,
828
+ y: props.y ? parseFloat(props.y) : undefined,
756
829
  width: props.width ? parseFloat(props.width) : undefined,
757
830
  height: props.height ? parseFloat(props.height) : undefined,
758
831
  theme: props.theme,
@@ -778,6 +851,8 @@ var AIDiagram = (function (exports) {
778
851
  id,
779
852
  label: props.label ?? "",
780
853
  rows: [],
854
+ x: props.x ? parseFloat(props.x) : undefined,
855
+ y: props.y ? parseFloat(props.y) : undefined,
781
856
  theme: props.theme,
782
857
  style: propsToStyle(props),
783
858
  };
@@ -833,6 +908,8 @@ var AIDiagram = (function (exports) {
833
908
  kind: "markdown",
834
909
  id,
835
910
  content: content.trim(),
911
+ x: props.x ? parseFloat(props.x) : undefined,
912
+ y: props.y ? parseFloat(props.y) : undefined,
836
913
  width: props.width ? parseFloat(props.width) : undefined,
837
914
  height: props.height ? parseFloat(props.height) : undefined,
838
915
  theme: props.theme,
@@ -922,6 +999,7 @@ var AIDiagram = (function (exports) {
922
999
  registerAuthoredId(grp.id, "group", t);
923
1000
  if (isBare) {
924
1001
  grp.label = "";
1002
+ grp.padding = grp.padding ?? 0;
925
1003
  grp.style = {
926
1004
  ...grp.style,
927
1005
  fill: grp.style?.fill ?? "none",
@@ -1092,7 +1170,7 @@ var AIDiagram = (function (exports) {
1092
1170
  node.style = { ...ast.styles[node.id], ...node.style };
1093
1171
  }
1094
1172
  }
1095
- return ast;
1173
+ return applyPluginAstTransforms(ast, options.plugins);
1096
1174
  }
1097
1175
 
1098
1176
  // ============================================================
@@ -3441,6 +3519,8 @@ var AIDiagram = (function (exports) {
3441
3519
  groupId: nodeParentById.get(n.id),
3442
3520
  width: n.width,
3443
3521
  height: n.height,
3522
+ authoredX: n.x,
3523
+ authoredY: n.y,
3444
3524
  deg: n.deg,
3445
3525
  dx: n.dx,
3446
3526
  dy: n.dy,
@@ -3469,6 +3549,8 @@ var AIDiagram = (function (exports) {
3469
3549
  align: (g.align ?? "start"),
3470
3550
  justify: (g.justify ?? "start"),
3471
3551
  style: { ...ast.styles[g.id], ...themeStyle, ...g.style },
3552
+ authoredX: g.x,
3553
+ authoredY: g.y,
3472
3554
  width: g.width,
3473
3555
  height: g.height,
3474
3556
  x: 0,
@@ -3488,6 +3570,8 @@ var AIDiagram = (function (exports) {
3488
3570
  headerH: TABLE.headerH,
3489
3571
  labelH: TABLE.labelH,
3490
3572
  style: { ...ast.styles[t.id], ...themeStyle, ...t.style },
3573
+ authoredX: t.x,
3574
+ authoredY: t.y,
3491
3575
  x: 0,
3492
3576
  y: 0,
3493
3577
  w: 0,
@@ -3502,6 +3586,8 @@ var AIDiagram = (function (exports) {
3502
3586
  label: c.label,
3503
3587
  data: c.data,
3504
3588
  style: { ...ast.styles[c.id], ...themeStyle, ...c.style },
3589
+ authoredX: c.x,
3590
+ authoredY: c.y,
3505
3591
  x: 0,
3506
3592
  y: 0,
3507
3593
  w: c.width ?? CHART.defaultW,
@@ -3517,6 +3603,8 @@ var AIDiagram = (function (exports) {
3517
3603
  style: { ...ast.styles[m.id], ...themeStyle, ...m.style },
3518
3604
  width: m.width,
3519
3605
  height: m.height,
3606
+ authoredX: m.x,
3607
+ authoredY: m.y,
3520
3608
  x: 0,
3521
3609
  y: 0,
3522
3610
  w: 0,
@@ -3529,6 +3617,8 @@ var AIDiagram = (function (exports) {
3529
3617
  to: e.to,
3530
3618
  connector: e.connector,
3531
3619
  label: e.label,
3620
+ fromAnchor: e.fromAnchor,
3621
+ toAnchor: e.toAnchor,
3532
3622
  dashed: e.dashed ?? false,
3533
3623
  bidirectional: e.bidirectional ?? false,
3534
3624
  style: e.style ?? {},
@@ -4106,28 +4196,13 @@ var AIDiagram = (function (exports) {
4106
4196
  return { arrowAt: "start", dashed };
4107
4197
  return { arrowAt: "end", dashed };
4108
4198
  }
4109
- // ── Generic rect connection point ────────────────────────────────────────
4110
- function rectConnPoint$1(rx, ry, rw, rh, ox, oy) {
4111
- const cx = rx + rw / 2, cy = ry + rh / 2;
4112
- const dx = ox - cx, dy = oy - cy;
4113
- if (Math.abs(dx) < 0.01 && Math.abs(dy) < 0.01)
4114
- return [cx, cy];
4115
- const hw = rw / 2 - 2, hh = rh / 2 - 2;
4116
- const tx = Math.abs(dx) > 0.01 ? hw / Math.abs(dx) : 1e9;
4117
- const ty = Math.abs(dy) > 0.01 ? hh / Math.abs(dy) : 1e9;
4118
- const t = Math.min(tx, ty);
4119
- return [cx + t * dx, cy + t * dy];
4120
- }
4121
4199
  // ── Resolve an endpoint entity by ID across all maps ─────────────────────
4122
4200
  function resolveEndpoint(id, nm, tm, gm, cm) {
4123
4201
  return nm.get(id) ?? tm.get(id) ?? gm.get(id) ?? cm.get(id) ?? null;
4124
4202
  }
4125
4203
  // ── Get connection point for any entity ──────────────────────────────────
4126
- function getConnPoint(src, dstCX, dstCY) {
4127
- if ("shape" in src && src.shape) {
4128
- return connPoint(src, { x: dstCX - 1, y: dstCY - 1, w: 2, h: 2});
4129
- }
4130
- return rectConnPoint$1(src.x, src.y, src.w, src.h, dstCX, dstCY);
4204
+ function getConnPoint(src, dstCX, dstCY, anchor) {
4205
+ return anchoredConnPoint(src, anchor, dstCX, dstCY);
4131
4206
  }
4132
4207
  // ── Group depth (for paint order) ────────────────────────────────────────
4133
4208
  function groupDepth(g, gm) {
@@ -4364,6 +4439,12 @@ var AIDiagram = (function (exports) {
4364
4439
  function iH(r, em) {
4365
4440
  return em.get(r.id).h;
4366
4441
  }
4442
+ function iAuthX(r, em) {
4443
+ return em.get(r.id).authoredX ?? 0;
4444
+ }
4445
+ function iAuthY(r, em) {
4446
+ return em.get(r.id).authoredY ?? 0;
4447
+ }
4367
4448
  function setPos(r, x, y, em) {
4368
4449
  const e = em.get(r.id);
4369
4450
  e.x = Math.round(x);
@@ -4410,6 +4491,12 @@ var AIDiagram = (function (exports) {
4410
4491
  g.w = cols * cellW + (cols - 1) * gap + pad * 2;
4411
4492
  g.h = rows * cellH + (rows - 1) * gap + pad * 2 + labelH;
4412
4493
  }
4494
+ else if (layout === "absolute") {
4495
+ const maxRight = Math.max(0, ...kids.map((r) => iAuthX(r, em) + iW(r, em)));
4496
+ const maxBottom = Math.max(0, ...kids.map((r) => iAuthY(r, em) + iH(r, em)));
4497
+ g.w = maxRight + pad * 2;
4498
+ g.h = maxBottom + pad * 2 + labelH;
4499
+ }
4413
4500
  else {
4414
4501
  // column (default)
4415
4502
  g.w = Math.max(...ws) + pad * 2;
@@ -4500,6 +4587,11 @@ var AIDiagram = (function (exports) {
4500
4587
  setPos(ref, contentX + (i % cols) * (cellW + gap), contentY + Math.floor(i / cols) * (cellH + gap), em);
4501
4588
  });
4502
4589
  }
4590
+ else if (layout === "absolute") {
4591
+ kids.forEach((ref) => {
4592
+ setPos(ref, contentX + iAuthX(ref, em), contentY + iAuthY(ref, em), em);
4593
+ });
4594
+ }
4503
4595
  else {
4504
4596
  // column (default)
4505
4597
  const ws = kids.map((r) => iW(r, em));
@@ -4545,6 +4637,50 @@ var AIDiagram = (function (exports) {
4545
4637
  const t = Math.min(tx, ty);
4546
4638
  return [cx + t * dx, cy + t * dy];
4547
4639
  }
4640
+ function clampInset(value) {
4641
+ return Math.max(2, value);
4642
+ }
4643
+ function anchoredConnPoint(entity, anchor, otherCX, otherCY) {
4644
+ if (!anchor) {
4645
+ if (entity.shape && otherCX != null && otherCY != null) {
4646
+ return connPoint(entity, { x: otherCX - 1, y: otherCY - 1, w: 2, h: 2});
4647
+ }
4648
+ if (otherCX != null && otherCY != null) {
4649
+ return rectConnPoint(entity.x, entity.y, entity.w, entity.h, otherCX, otherCY);
4650
+ }
4651
+ return [entity.x + entity.w / 2, entity.y + entity.h / 2];
4652
+ }
4653
+ const insetX = clampInset(Math.min(10, entity.w / 2));
4654
+ const insetY = clampInset(Math.min(10, entity.h / 2));
4655
+ const left = entity.x + insetX;
4656
+ const right = entity.x + entity.w - insetX;
4657
+ const top = entity.y + insetY;
4658
+ const bottom = entity.y + entity.h - insetY;
4659
+ const cx = entity.x + entity.w / 2;
4660
+ const cy = entity.y + entity.h / 2;
4661
+ switch (anchor) {
4662
+ case "top":
4663
+ return [cx, top];
4664
+ case "right":
4665
+ return [right, cy];
4666
+ case "bottom":
4667
+ return [cx, bottom];
4668
+ case "left":
4669
+ return [left, cy];
4670
+ case "center":
4671
+ return [cx, cy];
4672
+ case "top-left":
4673
+ return [left, top];
4674
+ case "top-right":
4675
+ return [right, top];
4676
+ case "bottom-left":
4677
+ return [left, bottom];
4678
+ case "bottom-right":
4679
+ return [right, bottom];
4680
+ default:
4681
+ return [cx, cy];
4682
+ }
4683
+ }
4548
4684
  function rectConnPoint(rx, ry, rw, rh, ox, oy) {
4549
4685
  const cx = rx + rw / 2, cy = ry + rh / 2;
4550
4686
  const dx = ox - cx, dy = oy - cy;
@@ -4576,17 +4712,6 @@ var AIDiagram = (function (exports) {
4576
4712
  return c;
4577
4713
  return null;
4578
4714
  }
4579
- function connPt(src, dstCX, dstCY) {
4580
- // SceneNode has a .shape field; use the existing connPoint for it
4581
- if ("shape" in src && src.shape) {
4582
- return connPoint(src, {
4583
- x: dstCX - 1,
4584
- y: dstCY - 1,
4585
- w: 2,
4586
- h: 2});
4587
- }
4588
- return rectConnPoint(src.x, src.y, src.w, src.h, dstCX, dstCY);
4589
- }
4590
4715
  for (const e of sg.edges) {
4591
4716
  const src = resolve(e.from);
4592
4717
  const dst = resolve(e.to);
@@ -4596,7 +4721,10 @@ var AIDiagram = (function (exports) {
4596
4721
  }
4597
4722
  const dstCX = dst.x + dst.w / 2, dstCY = dst.y + dst.h / 2;
4598
4723
  const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
4599
- e.points = [connPt(src, dstCX, dstCY), connPt(dst, srcCX, srcCY)];
4724
+ e.points = [
4725
+ anchoredConnPoint(src, e.fromAnchor, dstCX, dstCY),
4726
+ anchoredConnPoint(dst, e.toAnchor, srcCX, srcCY),
4727
+ ];
4600
4728
  }
4601
4729
  }
4602
4730
  function computeBounds(sg, margin) {
@@ -4682,6 +4810,7 @@ var AIDiagram = (function (exports) {
4682
4810
  const rootCols = Number(sg.config["columns"] ?? 1);
4683
4811
  const useGrid = rootLayout === "grid" && rootCols > 0;
4684
4812
  const useColumn = rootLayout === "column";
4813
+ const useAbsolute = rootLayout === "absolute";
4685
4814
  if (useGrid) {
4686
4815
  // ── Grid: per-row heights, per-column widths (no wasted space) ──
4687
4816
  const cols = rootCols;
@@ -4713,6 +4842,13 @@ var AIDiagram = (function (exports) {
4713
4842
  e.y = rowY[Math.floor(idx / cols)];
4714
4843
  });
4715
4844
  }
4845
+ else if (useAbsolute) {
4846
+ for (const ref of rootOrder) {
4847
+ const e = em.get(ref.id);
4848
+ e.x = MARGIN + (e.authoredX ?? 0);
4849
+ e.y = MARGIN + (e.authoredY ?? 0);
4850
+ }
4851
+ }
4716
4852
  else {
4717
4853
  // ── Row or Column linear flow ──────────────────────────
4718
4854
  let pos = MARGIN;
@@ -7729,8 +7865,8 @@ var AIDiagram = (function (exports) {
7729
7865
  continue;
7730
7866
  const dstCX = dst.x + dst.w / 2, dstCY = dst.y + dst.h / 2;
7731
7867
  const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
7732
- const [x1, y1] = getConnPoint(src, dstCX, dstCY);
7733
- const [x2, y2] = getConnPoint(dst, srcCX, srcCY);
7868
+ const [x1, y1] = getConnPoint(src, dstCX, dstCY, e.fromAnchor);
7869
+ const [x2, y2] = getConnPoint(dst, srcCX, srcCY, e.toAnchor);
7734
7870
  const eg = mkGroup(`edge-${e.from}-${e.to}`, "eg");
7735
7871
  if (e.style?.opacity != null)
7736
7872
  eg.setAttribute("opacity", String(e.style.opacity));
@@ -7807,6 +7943,8 @@ var AIDiagram = (function (exports) {
7807
7943
  ng.dataset.h = String(n.h);
7808
7944
  if (n.pathData)
7809
7945
  ng.dataset.pathData = n.pathData;
7946
+ if (n.meta?.animationParent)
7947
+ ng.dataset.animationParent = n.meta.animationParent;
7810
7948
  if (n.style?.opacity != null)
7811
7949
  ng.setAttribute("opacity", String(n.style.opacity));
7812
7950
  // ── Static transform (deg, dx, dy, factor) ──────────
@@ -8460,8 +8598,8 @@ var AIDiagram = (function (exports) {
8460
8598
  continue;
8461
8599
  const dstCX = dst.x + dst.w / 2, dstCY = dst.y + dst.h / 2;
8462
8600
  const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
8463
- const [x1, y1] = getConnPoint(src, dstCX, dstCY);
8464
- const [x2, y2] = getConnPoint(dst, srcCX, srcCY);
8601
+ const [x1, y1] = getConnPoint(src, dstCX, dstCY, e.fromAnchor);
8602
+ const [x2, y2] = getConnPoint(dst, srcCX, srcCY, e.toAnchor);
8465
8603
  if (e.style?.opacity != null)
8466
8604
  ctx.globalAlpha = Number(e.style.opacity);
8467
8605
  const ecol = String(e.style?.stroke ?? palette.edgeStroke);
@@ -9318,6 +9456,13 @@ var AIDiagram = (function (exports) {
9318
9456
  this.drawTargetNodes.delete(`node-${s.target}`);
9319
9457
  }
9320
9458
  }
9459
+ this._relatedElementIdsByPrimaryId = this._buildRelatedElementIndex();
9460
+ for (const nodeId of Array.from(this.drawTargetNodes)) {
9461
+ const relatedIds = this._relatedElementIdsByPrimaryId.get(nodeId);
9462
+ if (!relatedIds)
9463
+ continue;
9464
+ relatedIds.forEach((id) => this.drawTargetNodes.add(id));
9465
+ }
9321
9466
  this._drawStepIndexByElementId = this._buildDrawStepIndex();
9322
9467
  const { parentGroupByElementId, groupDescendantIds } = this._buildGroupVisibilityIndex();
9323
9468
  this._parentGroupByElementId = parentGroupByElementId;
@@ -9348,10 +9493,30 @@ var AIDiagram = (function (exports) {
9348
9493
  const el = resolveNonEdgeDrawEl(this.svg, step.target);
9349
9494
  if (el && !drawStepIndexByElementId.has(el.id)) {
9350
9495
  drawStepIndexByElementId.set(el.id, stepIndex);
9496
+ this._relatedElementIdsByPrimaryId.get(el.id)?.forEach((relatedId) => {
9497
+ if (!drawStepIndexByElementId.has(relatedId)) {
9498
+ drawStepIndexByElementId.set(relatedId, stepIndex);
9499
+ }
9500
+ });
9351
9501
  }
9352
9502
  });
9353
9503
  return drawStepIndexByElementId;
9354
9504
  }
9505
+ _buildRelatedElementIndex() {
9506
+ const relatedElementIdsByPrimaryId = new Map();
9507
+ this.svg.querySelectorAll(POSITIONABLE_SELECTOR).forEach((el) => {
9508
+ const animationParent = el.dataset.animationParent;
9509
+ if (!animationParent)
9510
+ return;
9511
+ const primaryEl = resolveNonEdgeDrawEl(this.svg, animationParent);
9512
+ if (!primaryEl || primaryEl.id === el.id)
9513
+ return;
9514
+ const related = relatedElementIdsByPrimaryId.get(primaryEl.id) ?? new Set();
9515
+ related.add(el.id);
9516
+ relatedElementIdsByPrimaryId.set(primaryEl.id, related);
9517
+ });
9518
+ return relatedElementIdsByPrimaryId;
9519
+ }
9355
9520
  _buildGroupVisibilityIndex() {
9356
9521
  const parentGroupByElementId = new Map();
9357
9522
  const directChildIdsByGroup = new Map();
@@ -9430,10 +9595,18 @@ var AIDiagram = (function (exports) {
9430
9595
  const el = resolveEl(this.svg, target);
9431
9596
  if (!el)
9432
9597
  return [];
9433
- if (!el.id.startsWith("group-"))
9434
- return [el];
9598
+ if (!el.id.startsWith("group-")) {
9599
+ const ids = new Set([el.id]);
9600
+ this._relatedElementIdsByPrimaryId.get(el.id)?.forEach((id) => ids.add(id));
9601
+ return Array.from(ids)
9602
+ .map((id) => getEl(this.svg, id))
9603
+ .filter((candidate) => candidate != null);
9604
+ }
9435
9605
  const ids = new Set([el.id]);
9436
9606
  this._groupDescendantIds.get(el.id)?.forEach((id) => ids.add(id));
9607
+ Array.from(ids).forEach((id) => {
9608
+ this._relatedElementIdsByPrimaryId.get(id)?.forEach((relatedId) => ids.add(relatedId));
9609
+ });
9437
9610
  return Array.from(ids)
9438
9611
  .map((id) => getEl(this.svg, id))
9439
9612
  .filter((candidate) => candidate != null);
@@ -9842,9 +10015,11 @@ var AIDiagram = (function (exports) {
9842
10015
  // ── highlight ────────────────────────────────────────────
9843
10016
  _doHighlight(target) {
9844
10017
  this.svg
9845
- .querySelectorAll(".ng.hl, .tg.hl, .ntg.hl, .cg.hl, .eg.hl")
10018
+ .querySelectorAll(".ng.hl, .gg.hl, .tg.hl, .ntg.hl, .cg.hl, .mdg.hl, .eg.hl")
9846
10019
  .forEach((e) => e.classList.remove("hl"));
9847
- resolveEl(this.svg, target)?.classList.add("hl");
10020
+ for (const el of this._resolveCascadeTargets(target)) {
10021
+ el.classList.add("hl");
10022
+ }
9848
10023
  }
9849
10024
  // ── fade / unfade ─────────────────────────────────────────
9850
10025
  _doFade(target, doFade) {
@@ -9880,8 +10055,8 @@ var AIDiagram = (function (exports) {
9880
10055
  }
9881
10056
  // ── move ──────────────────────────────────────────────────
9882
10057
  _doMove(target, step, silent) {
9883
- const el = resolveEl(this.svg, target);
9884
- if (!el)
10058
+ const targets = this._resolveCascadeTargets(target);
10059
+ if (!targets.length)
9885
10060
  return;
9886
10061
  const cur = this._transforms.get(target) ?? {
9887
10062
  tx: 0,
@@ -9894,12 +10069,14 @@ var AIDiagram = (function (exports) {
9894
10069
  tx: cur.tx + (step.dx ?? 0),
9895
10070
  ty: cur.ty + (step.dy ?? 0),
9896
10071
  });
9897
- this._writeTransform(el, target, silent, step.duration ?? 420);
10072
+ for (const el of targets) {
10073
+ this._writeTransform(el, target, silent, step.duration ?? 420);
10074
+ }
9898
10075
  }
9899
10076
  // ── scale ─────────────────────────────────────────────────
9900
10077
  _doScale(target, step, silent) {
9901
- const el = resolveEl(this.svg, target);
9902
- if (!el)
10078
+ const targets = this._resolveCascadeTargets(target);
10079
+ if (!targets.length)
9903
10080
  return;
9904
10081
  const cur = this._transforms.get(target) ?? {
9905
10082
  tx: 0,
@@ -9908,12 +10085,14 @@ var AIDiagram = (function (exports) {
9908
10085
  rotate: 0,
9909
10086
  };
9910
10087
  this._transforms.set(target, { ...cur, scale: step.factor ?? 1 });
9911
- this._writeTransform(el, target, silent, step.duration ?? 350);
10088
+ for (const el of targets) {
10089
+ this._writeTransform(el, target, silent, step.duration ?? 350);
10090
+ }
9912
10091
  }
9913
10092
  // ── rotate ────────────────────────────────────────────────
9914
10093
  _doRotate(target, step, silent) {
9915
- const el = resolveEl(this.svg, target);
9916
- if (!el)
10094
+ const targets = this._resolveCascadeTargets(target);
10095
+ if (!targets.length)
9917
10096
  return;
9918
10097
  const cur = this._transforms.get(target) ?? {
9919
10098
  tx: 0,
@@ -9925,7 +10104,9 @@ var AIDiagram = (function (exports) {
9925
10104
  ...cur,
9926
10105
  rotate: cur.rotate + (step.deg ?? 0),
9927
10106
  });
9928
- this._writeTransform(el, target, silent, step.duration ?? 400);
10107
+ for (const el of targets) {
10108
+ this._writeTransform(el, target, silent, step.duration ?? 400);
10109
+ }
9929
10110
  }
9930
10111
  _doDraw(step, silent) {
9931
10112
  const { target } = step;
@@ -10069,18 +10250,20 @@ var AIDiagram = (function (exports) {
10069
10250
  return;
10070
10251
  }
10071
10252
  // ── Node draw ──────────────────────────────────────
10072
- const nodeEl = getNodeEl(this.svg, target);
10073
- if (!nodeEl)
10253
+ const nodeEls = this._resolveCascadeTargets(target).filter((el) => el.classList.contains("ng"));
10254
+ if (!nodeEls.length)
10074
10255
  return;
10075
- showDrawEl(nodeEl);
10076
- if (silent) {
10077
- revealNodeInstant(nodeEl);
10078
- }
10079
- else {
10080
- if (!nodeGuidePathEl(nodeEl) && !nodeEl.querySelector("path")?.style.strokeDasharray) {
10081
- prepareNodeForDraw(nodeEl);
10256
+ for (const nodeEl of nodeEls) {
10257
+ showDrawEl(nodeEl);
10258
+ if (silent) {
10259
+ revealNodeInstant(nodeEl);
10260
+ }
10261
+ else {
10262
+ if (!nodeGuidePathEl(nodeEl) && !nodeEl.querySelector("path")?.style.strokeDasharray) {
10263
+ prepareNodeForDraw(nodeEl);
10264
+ }
10265
+ animateNodeDraw(nodeEl, step.duration ?? ANIMATION.nodeStrokeDur, step.duration ?? ANIMATION.textRevealMs);
10082
10266
  }
10083
- animateNodeDraw(nodeEl, step.duration ?? ANIMATION.nodeStrokeDur, step.duration ?? ANIMATION.textRevealMs);
10084
10267
  }
10085
10268
  }
10086
10269
  // ── erase ─────────────────────────────────────────────────
@@ -10101,45 +10284,44 @@ var AIDiagram = (function (exports) {
10101
10284
  }
10102
10285
  // ── pulse ─────────────────────────────────────────────────
10103
10286
  _doPulse(target, duration = 500) {
10104
- resolveEl(this.svg, target)?.animate([
10105
- { filter: "brightness(1)" },
10106
- { filter: "brightness(1.6)" },
10107
- { filter: "brightness(1)" },
10108
- ], { duration, iterations: 3 });
10287
+ for (const el of this._resolveCascadeTargets(target)) {
10288
+ el.animate([
10289
+ { filter: "brightness(1)" },
10290
+ { filter: "brightness(1.6)" },
10291
+ { filter: "brightness(1)" },
10292
+ ], { duration, iterations: 3 });
10293
+ }
10109
10294
  }
10110
10295
  // ── color ─────────────────────────────────────────────────
10111
10296
  _doColor(target, color) {
10112
10297
  if (!color)
10113
10298
  return;
10114
- const el = resolveEl(this.svg, target);
10115
- if (!el)
10116
- return;
10117
- // edge color stroke
10118
- if (parseEdgeTarget(target)) {
10119
- el.querySelectorAll("path, line, polyline").forEach((p) => {
10120
- p.style.stroke = color;
10121
- });
10122
- el.querySelectorAll("polygon").forEach((p) => {
10123
- p.style.fill = color;
10124
- p.style.stroke = color;
10125
- });
10126
- return;
10127
- }
10128
- // everything else — color fill
10129
- let hit = false;
10130
- el.querySelectorAll("path, rect, ellipse, polygon").forEach((c) => {
10131
- const attrFill = c.getAttribute("fill");
10132
- if (attrFill === "none")
10133
- return;
10134
- if (attrFill === null && c.tagName === "path")
10135
- return;
10136
- c.style.fill = color;
10137
- hit = true;
10138
- });
10139
- if (!hit) {
10140
- el.querySelectorAll("text").forEach((t) => {
10141
- t.style.fill = color;
10299
+ for (const el of this._resolveCascadeTargets(target)) {
10300
+ if (parseEdgeTarget(target)) {
10301
+ el.querySelectorAll("path, line, polyline").forEach((p) => {
10302
+ p.style.stroke = color;
10303
+ });
10304
+ el.querySelectorAll("polygon").forEach((p) => {
10305
+ p.style.fill = color;
10306
+ p.style.stroke = color;
10307
+ });
10308
+ continue;
10309
+ }
10310
+ let hit = false;
10311
+ el.querySelectorAll("path, rect, ellipse, polygon").forEach((c) => {
10312
+ const attrFill = c.getAttribute("fill");
10313
+ if (attrFill === "none")
10314
+ return;
10315
+ if (attrFill === null && c.tagName === "path")
10316
+ return;
10317
+ c.style.fill = color;
10318
+ hit = true;
10142
10319
  });
10320
+ if (!hit) {
10321
+ el.querySelectorAll("text").forEach((t) => {
10322
+ t.style.fill = color;
10323
+ });
10324
+ }
10143
10325
  }
10144
10326
  }
10145
10327
  // ── narration ───────────────────────────────────────────
@@ -10733,7 +10915,7 @@ var AIDiagram = (function (exports) {
10733
10915
  }
10734
10916
 
10735
10917
  function render(options) {
10736
- const { container: rawContainer, dsl, renderer = "svg", injectCSS = true, tts, svgOptions = {}, canvasOptions = {}, onNodeClick, onReady, } = options;
10918
+ const { container: rawContainer, dsl, plugins, renderer = "svg", injectCSS = true, tts, svgOptions = {}, canvasOptions = {}, onNodeClick, onReady, } = options;
10737
10919
  if (injectCSS && !document.getElementById("ai-diagram-css")) {
10738
10920
  const style = document.createElement("style");
10739
10921
  style.id = "ai-diagram-css";
@@ -10749,7 +10931,7 @@ var AIDiagram = (function (exports) {
10749
10931
  else {
10750
10932
  el = rawContainer;
10751
10933
  }
10752
- const ast = parse(dsl);
10934
+ const ast = parse(dsl, { plugins });
10753
10935
  const scene = buildSceneGraph(ast);
10754
10936
  layout(scene);
10755
10937
  let svg;
@@ -11093,6 +11275,7 @@ var AIDiagram = (function (exports) {
11093
11275
  const instance = render({
11094
11276
  container: this.diagramWrap,
11095
11277
  dsl: this.dsl,
11278
+ plugins: this.options.plugins,
11096
11279
  renderer: this.renderer,
11097
11280
  svgOptions: { interactive: true, showTitle: true, theme: this.options.svgOptions?.theme ?? this.theme, ...this.options.svgOptions },
11098
11281
  canvasOptions: this.options.canvasOptions,
@@ -12170,6 +12353,7 @@ var AIDiagram = (function (exports) {
12170
12353
  const instance = render({
12171
12354
  container: this.diagramWrap,
12172
12355
  dsl: this.dsl,
12356
+ plugins: this.options.plugins,
12173
12357
  renderer: "svg",
12174
12358
  svgOptions: {
12175
12359
  showTitle: true,