sketchmark 1.1.6 → 1.2.1

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,
@@ -512,6 +561,7 @@ var AIDiagram = (function (exports) {
512
561
  ...(props.dy ? { dy: parseFloat(props.dy) } : {}),
513
562
  ...(props.factor ? { factor: parseFloat(props.factor) } : {}),
514
563
  ...(props.theme ? { theme: props.theme } : {}),
564
+ ...(meta ? { meta } : {}),
515
565
  style: propsToStyle(props),
516
566
  };
517
567
  if (props.url)
@@ -536,12 +586,14 @@ var AIDiagram = (function (exports) {
536
586
  j = 2;
537
587
  }
538
588
  Object.assign(props, parseSimpleProps(toks, j));
589
+ const meta = extractNodeMeta(props);
539
590
  return {
540
591
  kind: "node",
541
592
  id,
542
593
  shape: "note",
543
594
  label: (props.label ?? "").replace(/\\n/g, "\n"),
544
595
  theme: props.theme,
596
+ ...(meta ? { meta } : {}),
545
597
  style: propsToStyle(props),
546
598
  ...(props.width ? { width: parseFloat(props.width) } : {}),
547
599
  ...(props.height ? { height: parseFloat(props.height) } : {}),
@@ -553,6 +605,13 @@ var AIDiagram = (function (exports) {
553
605
  ...(props.factor ? { factor: parseFloat(props.factor) } : {}),
554
606
  };
555
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
+ }
556
615
  function parseGroup() {
557
616
  const keywordTok = cur();
558
617
  skip();
@@ -625,6 +684,8 @@ var AIDiagram = (function (exports) {
625
684
  to: toTok.value,
626
685
  connector: connector,
627
686
  label: props.label,
687
+ fromAnchor: props["anchor-from"],
688
+ toAnchor: props["anchor-to"],
628
689
  dashed,
629
690
  bidirectional,
630
691
  style: propsToStyle(props),
@@ -938,6 +999,7 @@ var AIDiagram = (function (exports) {
938
999
  registerAuthoredId(grp.id, "group", t);
939
1000
  if (isBare) {
940
1001
  grp.label = "";
1002
+ grp.padding = grp.padding ?? 0;
941
1003
  grp.style = {
942
1004
  ...grp.style,
943
1005
  fill: grp.style?.fill ?? "none",
@@ -1108,7 +1170,7 @@ var AIDiagram = (function (exports) {
1108
1170
  node.style = { ...ast.styles[node.id], ...node.style };
1109
1171
  }
1110
1172
  }
1111
- return ast;
1173
+ return applyPluginAstTransforms(ast, options.plugins);
1112
1174
  }
1113
1175
 
1114
1176
  // ============================================================
@@ -3555,6 +3617,8 @@ var AIDiagram = (function (exports) {
3555
3617
  to: e.to,
3556
3618
  connector: e.connector,
3557
3619
  label: e.label,
3620
+ fromAnchor: e.fromAnchor,
3621
+ toAnchor: e.toAnchor,
3558
3622
  dashed: e.dashed ?? false,
3559
3623
  bidirectional: e.bidirectional ?? false,
3560
3624
  style: e.style ?? {},
@@ -4132,28 +4196,13 @@ var AIDiagram = (function (exports) {
4132
4196
  return { arrowAt: "start", dashed };
4133
4197
  return { arrowAt: "end", dashed };
4134
4198
  }
4135
- // ── Generic rect connection point ────────────────────────────────────────
4136
- function rectConnPoint$1(rx, ry, rw, rh, ox, oy) {
4137
- const cx = rx + rw / 2, cy = ry + rh / 2;
4138
- const dx = ox - cx, dy = oy - cy;
4139
- if (Math.abs(dx) < 0.01 && Math.abs(dy) < 0.01)
4140
- return [cx, cy];
4141
- const hw = rw / 2 - 2, hh = rh / 2 - 2;
4142
- const tx = Math.abs(dx) > 0.01 ? hw / Math.abs(dx) : 1e9;
4143
- const ty = Math.abs(dy) > 0.01 ? hh / Math.abs(dy) : 1e9;
4144
- const t = Math.min(tx, ty);
4145
- return [cx + t * dx, cy + t * dy];
4146
- }
4147
4199
  // ── Resolve an endpoint entity by ID across all maps ─────────────────────
4148
4200
  function resolveEndpoint(id, nm, tm, gm, cm) {
4149
4201
  return nm.get(id) ?? tm.get(id) ?? gm.get(id) ?? cm.get(id) ?? null;
4150
4202
  }
4151
4203
  // ── Get connection point for any entity ──────────────────────────────────
4152
- function getConnPoint(src, dstCX, dstCY) {
4153
- if ("shape" in src && src.shape) {
4154
- return connPoint(src, { x: dstCX - 1, y: dstCY - 1, w: 2, h: 2});
4155
- }
4156
- 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);
4157
4206
  }
4158
4207
  // ── Group depth (for paint order) ────────────────────────────────────────
4159
4208
  function groupDepth(g, gm) {
@@ -4251,37 +4300,510 @@ var AIDiagram = (function (exports) {
4251
4300
  },
4252
4301
  };
4253
4302
 
4303
+ const COMMAND_RE = /^[AaCcHhLlMmQqSsTtVvZz]$/;
4304
+ const TOKEN_RE = /[AaCcHhLlMmQqSsTtVvZz]|[-+]?(?:\d*\.\d+|\d+)(?:[eE][-+]?\d+)?/g;
4305
+ const EPSILON = 1e-6;
4306
+ const PARAM_COUNTS = {
4307
+ A: 7,
4308
+ C: 6,
4309
+ H: 1,
4310
+ L: 2,
4311
+ M: 2,
4312
+ Q: 4,
4313
+ S: 4,
4314
+ T: 2,
4315
+ V: 1,
4316
+ Z: 0,
4317
+ };
4318
+ function isCommandToken(token) {
4319
+ return COMMAND_RE.test(token);
4320
+ }
4321
+ function formatNumber(value) {
4322
+ const rounded = Math.abs(value) < EPSILON ? 0 : Number(value.toFixed(3));
4323
+ return Object.is(rounded, -0) ? "0" : String(rounded);
4324
+ }
4325
+ function parseRawSegments(pathData) {
4326
+ const tokens = pathData.match(TOKEN_RE) ?? [];
4327
+ if (!tokens.length)
4328
+ return [];
4329
+ const segments = [];
4330
+ let index = 0;
4331
+ let currentCommand = null;
4332
+ while (index < tokens.length) {
4333
+ const token = tokens[index];
4334
+ if (isCommandToken(token)) {
4335
+ currentCommand = token;
4336
+ index += 1;
4337
+ if (token === "Z" || token === "z") {
4338
+ segments.push({ command: "Z", values: [] });
4339
+ }
4340
+ continue;
4341
+ }
4342
+ if (!currentCommand)
4343
+ break;
4344
+ const upper = currentCommand.toUpperCase();
4345
+ const paramCount = PARAM_COUNTS[upper];
4346
+ if (!paramCount) {
4347
+ index += 1;
4348
+ continue;
4349
+ }
4350
+ let isFirstMove = upper === "M";
4351
+ while (index < tokens.length && !isCommandToken(tokens[index])) {
4352
+ if (index + paramCount > tokens.length)
4353
+ return segments;
4354
+ const values = tokens
4355
+ .slice(index, index + paramCount)
4356
+ .map((value) => Number(value));
4357
+ if (values.some((value) => Number.isNaN(value))) {
4358
+ return segments;
4359
+ }
4360
+ if (upper === "M") {
4361
+ const moveCommand = isFirstMove
4362
+ ? currentCommand
4363
+ : currentCommand === "m"
4364
+ ? "l"
4365
+ : "L";
4366
+ segments.push({ command: moveCommand, values });
4367
+ isFirstMove = false;
4368
+ }
4369
+ else {
4370
+ segments.push({ command: currentCommand, values });
4371
+ }
4372
+ index += paramCount;
4373
+ }
4374
+ }
4375
+ return segments;
4376
+ }
4377
+ function reflect(control, around) {
4378
+ return {
4379
+ x: around.x * 2 - control.x,
4380
+ y: around.y * 2 - control.y,
4381
+ };
4382
+ }
4383
+ function toAbsoluteSegments(rawSegments) {
4384
+ const segments = [];
4385
+ let current = { x: 0, y: 0 };
4386
+ let subpathStart = { x: 0, y: 0 };
4387
+ let previousCubicControl = null;
4388
+ let previousQuadraticControl = null;
4389
+ for (const segment of rawSegments) {
4390
+ const isRelative = segment.command === segment.command.toLowerCase();
4391
+ const command = segment.command.toUpperCase();
4392
+ const values = segment.values;
4393
+ switch (command) {
4394
+ case "M": {
4395
+ const x = isRelative ? current.x + values[0] : values[0];
4396
+ const y = isRelative ? current.y + values[1] : values[1];
4397
+ current = { x, y };
4398
+ subpathStart = { x, y };
4399
+ previousCubicControl = null;
4400
+ previousQuadraticControl = null;
4401
+ segments.push({ command: "M", values: [x, y] });
4402
+ break;
4403
+ }
4404
+ case "L": {
4405
+ const x = isRelative ? current.x + values[0] : values[0];
4406
+ const y = isRelative ? current.y + values[1] : values[1];
4407
+ current = { x, y };
4408
+ previousCubicControl = null;
4409
+ previousQuadraticControl = null;
4410
+ segments.push({ command: "L", values: [x, y] });
4411
+ break;
4412
+ }
4413
+ case "H": {
4414
+ const x = isRelative ? current.x + values[0] : values[0];
4415
+ current = { x, y: current.y };
4416
+ previousCubicControl = null;
4417
+ previousQuadraticControl = null;
4418
+ segments.push({ command: "L", values: [x, current.y] });
4419
+ break;
4420
+ }
4421
+ case "V": {
4422
+ const y = isRelative ? current.y + values[0] : values[0];
4423
+ current = { x: current.x, y };
4424
+ previousCubicControl = null;
4425
+ previousQuadraticControl = null;
4426
+ segments.push({ command: "L", values: [current.x, y] });
4427
+ break;
4428
+ }
4429
+ case "C": {
4430
+ const x1 = isRelative ? current.x + values[0] : values[0];
4431
+ const y1 = isRelative ? current.y + values[1] : values[1];
4432
+ const x2 = isRelative ? current.x + values[2] : values[2];
4433
+ const y2 = isRelative ? current.y + values[3] : values[3];
4434
+ const x = isRelative ? current.x + values[4] : values[4];
4435
+ const y = isRelative ? current.y + values[5] : values[5];
4436
+ current = { x, y };
4437
+ previousCubicControl = { x: x2, y: y2 };
4438
+ previousQuadraticControl = null;
4439
+ segments.push({ command: "C", values: [x1, y1, x2, y2, x, y] });
4440
+ break;
4441
+ }
4442
+ case "S": {
4443
+ const control1 = previousCubicControl
4444
+ ? reflect(previousCubicControl, current)
4445
+ : { ...current };
4446
+ const x2 = isRelative ? current.x + values[0] : values[0];
4447
+ const y2 = isRelative ? current.y + values[1] : values[1];
4448
+ const x = isRelative ? current.x + values[2] : values[2];
4449
+ const y = isRelative ? current.y + values[3] : values[3];
4450
+ current = { x, y };
4451
+ previousCubicControl = { x: x2, y: y2 };
4452
+ previousQuadraticControl = null;
4453
+ segments.push({
4454
+ command: "C",
4455
+ values: [control1.x, control1.y, x2, y2, x, y],
4456
+ });
4457
+ break;
4458
+ }
4459
+ case "Q": {
4460
+ const x1 = isRelative ? current.x + values[0] : values[0];
4461
+ const y1 = isRelative ? current.y + values[1] : values[1];
4462
+ const x = isRelative ? current.x + values[2] : values[2];
4463
+ const y = isRelative ? current.y + values[3] : values[3];
4464
+ current = { x, y };
4465
+ previousCubicControl = null;
4466
+ previousQuadraticControl = { x: x1, y: y1 };
4467
+ segments.push({ command: "Q", values: [x1, y1, x, y] });
4468
+ break;
4469
+ }
4470
+ case "T": {
4471
+ const control = previousQuadraticControl
4472
+ ? reflect(previousQuadraticControl, current)
4473
+ : { ...current };
4474
+ const x = isRelative ? current.x + values[0] : values[0];
4475
+ const y = isRelative ? current.y + values[1] : values[1];
4476
+ current = { x, y };
4477
+ previousCubicControl = null;
4478
+ previousQuadraticControl = control;
4479
+ segments.push({ command: "Q", values: [control.x, control.y, x, y] });
4480
+ break;
4481
+ }
4482
+ case "A": {
4483
+ const rx = Math.abs(values[0]);
4484
+ const ry = Math.abs(values[1]);
4485
+ const rotation = values[2];
4486
+ const largeArc = values[3];
4487
+ const sweep = values[4];
4488
+ const x = isRelative ? current.x + values[5] : values[5];
4489
+ const y = isRelative ? current.y + values[6] : values[6];
4490
+ current = { x, y };
4491
+ previousCubicControl = null;
4492
+ previousQuadraticControl = null;
4493
+ segments.push({
4494
+ command: "A",
4495
+ values: [rx, ry, rotation, largeArc, sweep, x, y],
4496
+ });
4497
+ break;
4498
+ }
4499
+ case "Z": {
4500
+ current = { ...subpathStart };
4501
+ previousCubicControl = null;
4502
+ previousQuadraticControl = null;
4503
+ segments.push({ command: "Z", values: [] });
4504
+ break;
4505
+ }
4506
+ }
4507
+ }
4508
+ return segments;
4509
+ }
4510
+ function cubicAt(p0, p1, p2, p3, t) {
4511
+ const mt = 1 - t;
4512
+ return (mt * mt * mt * p0 +
4513
+ 3 * mt * mt * t * p1 +
4514
+ 3 * mt * t * t * p2 +
4515
+ t * t * t * p3);
4516
+ }
4517
+ function quadraticAt(p0, p1, p2, t) {
4518
+ const mt = 1 - t;
4519
+ return mt * mt * p0 + 2 * mt * t * p1 + t * t * p2;
4520
+ }
4521
+ function cubicExtrema(p0, p1, p2, p3) {
4522
+ const a = -p0 + 3 * p1 - 3 * p2 + p3;
4523
+ const b = 3 * p0 - 6 * p1 + 3 * p2;
4524
+ const c = -3 * p0 + 3 * p1;
4525
+ if (Math.abs(a) < EPSILON) {
4526
+ if (Math.abs(b) < EPSILON)
4527
+ return [];
4528
+ return [-c / (2 * b)].filter((t) => t > 0 && t < 1);
4529
+ }
4530
+ const discriminant = 4 * b * b - 12 * a * c;
4531
+ if (discriminant < 0)
4532
+ return [];
4533
+ const sqrtDiscriminant = Math.sqrt(discriminant);
4534
+ return [
4535
+ (-2 * b + sqrtDiscriminant) / (6 * a),
4536
+ (-2 * b - sqrtDiscriminant) / (6 * a),
4537
+ ].filter((t) => t > 0 && t < 1);
4538
+ }
4539
+ function quadraticExtrema(p0, p1, p2) {
4540
+ const denominator = p0 - 2 * p1 + p2;
4541
+ if (Math.abs(denominator) < EPSILON)
4542
+ return [];
4543
+ const t = (p0 - p1) / denominator;
4544
+ return t > 0 && t < 1 ? [t] : [];
4545
+ }
4546
+ function angleBetween(u, v) {
4547
+ const magnitude = Math.hypot(u.x, u.y) * Math.hypot(v.x, v.y);
4548
+ if (magnitude < EPSILON)
4549
+ return 0;
4550
+ const sign = u.x * v.y - u.y * v.x < 0 ? -1 : 1;
4551
+ const cosine = Math.min(1, Math.max(-1, (u.x * v.x + u.y * v.y) / magnitude));
4552
+ return sign * Math.acos(cosine);
4553
+ }
4554
+ function sampleArc(start, values) {
4555
+ let [rx, ry, rotation, largeArcFlag, sweepFlag, endX, endY] = values;
4556
+ if ((Math.abs(start.x - endX) < EPSILON && Math.abs(start.y - endY) < EPSILON) || rx < EPSILON || ry < EPSILON) {
4557
+ return [start, { x: endX, y: endY }];
4558
+ }
4559
+ rx = Math.abs(rx);
4560
+ ry = Math.abs(ry);
4561
+ const phi = (rotation * Math.PI) / 180;
4562
+ const cosPhi = Math.cos(phi);
4563
+ const sinPhi = Math.sin(phi);
4564
+ const dx2 = (start.x - endX) / 2;
4565
+ const dy2 = (start.y - endY) / 2;
4566
+ const x1p = cosPhi * dx2 + sinPhi * dy2;
4567
+ const y1p = -sinPhi * dx2 + cosPhi * dy2;
4568
+ const lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry);
4569
+ if (lambda > 1) {
4570
+ const scale = Math.sqrt(lambda);
4571
+ rx *= scale;
4572
+ ry *= scale;
4573
+ }
4574
+ const rx2 = rx * rx;
4575
+ const ry2 = ry * ry;
4576
+ const x1p2 = x1p * x1p;
4577
+ const y1p2 = y1p * y1p;
4578
+ const numerator = rx2 * ry2 - rx2 * y1p2 - ry2 * x1p2;
4579
+ const denominator = rx2 * y1p2 + ry2 * x1p2;
4580
+ const factor = denominator < EPSILON ? 0 : Math.sqrt(Math.max(0, numerator / denominator));
4581
+ const sign = largeArcFlag === sweepFlag ? -1 : 1;
4582
+ const cxp = sign * factor * ((rx * y1p) / ry);
4583
+ const cyp = sign * factor * (-(ry * x1p) / rx);
4584
+ const cx = cosPhi * cxp - sinPhi * cyp + (start.x + endX) / 2;
4585
+ const cy = sinPhi * cxp + cosPhi * cyp + (start.y + endY) / 2;
4586
+ const startVector = {
4587
+ x: (x1p - cxp) / rx,
4588
+ y: (y1p - cyp) / ry,
4589
+ };
4590
+ const endVector = {
4591
+ x: (-x1p - cxp) / rx,
4592
+ y: (-y1p - cyp) / ry,
4593
+ };
4594
+ let deltaTheta = angleBetween(startVector, endVector);
4595
+ if (!sweepFlag && deltaTheta > 0)
4596
+ deltaTheta -= Math.PI * 2;
4597
+ if (sweepFlag && deltaTheta < 0)
4598
+ deltaTheta += Math.PI * 2;
4599
+ const theta1 = angleBetween({ x: 1, y: 0 }, startVector);
4600
+ const steps = Math.max(12, Math.ceil(Math.abs(deltaTheta) / (Math.PI / 8)));
4601
+ const points = [];
4602
+ for (let index = 0; index <= steps; index += 1) {
4603
+ const theta = theta1 + (deltaTheta * index) / steps;
4604
+ const cosTheta = Math.cos(theta);
4605
+ const sinTheta = Math.sin(theta);
4606
+ points.push({
4607
+ x: cx + rx * cosPhi * cosTheta - ry * sinPhi * sinTheta,
4608
+ y: cy + rx * sinPhi * cosTheta + ry * cosPhi * sinTheta,
4609
+ });
4610
+ }
4611
+ return points;
4612
+ }
4613
+ function boundsFromAbsoluteSegments(segments) {
4614
+ if (!segments.length)
4615
+ return null;
4616
+ let minX = Infinity;
4617
+ let minY = Infinity;
4618
+ let maxX = -Infinity;
4619
+ let maxY = -Infinity;
4620
+ const include = (point) => {
4621
+ minX = Math.min(minX, point.x);
4622
+ minY = Math.min(minY, point.y);
4623
+ maxX = Math.max(maxX, point.x);
4624
+ maxY = Math.max(maxY, point.y);
4625
+ };
4626
+ let current = { x: 0, y: 0 };
4627
+ let subpathStart = { x: 0, y: 0 };
4628
+ for (const segment of segments) {
4629
+ switch (segment.command) {
4630
+ case "M": {
4631
+ current = { x: segment.values[0], y: segment.values[1] };
4632
+ subpathStart = { ...current };
4633
+ include(current);
4634
+ break;
4635
+ }
4636
+ case "L": {
4637
+ include(current);
4638
+ current = { x: segment.values[0], y: segment.values[1] };
4639
+ include(current);
4640
+ break;
4641
+ }
4642
+ case "C": {
4643
+ const [x1, y1, x2, y2, x, y] = segment.values;
4644
+ const ts = new Set([0, 1]);
4645
+ cubicExtrema(current.x, x1, x2, x).forEach((value) => ts.add(value));
4646
+ cubicExtrema(current.y, y1, y2, y).forEach((value) => ts.add(value));
4647
+ for (const t of ts) {
4648
+ include({
4649
+ x: cubicAt(current.x, x1, x2, x, t),
4650
+ y: cubicAt(current.y, y1, y2, y, t),
4651
+ });
4652
+ }
4653
+ current = { x, y };
4654
+ break;
4655
+ }
4656
+ case "Q": {
4657
+ const [x1, y1, x, y] = segment.values;
4658
+ const ts = new Set([0, 1]);
4659
+ quadraticExtrema(current.x, x1, x).forEach((value) => ts.add(value));
4660
+ quadraticExtrema(current.y, y1, y).forEach((value) => ts.add(value));
4661
+ for (const t of ts) {
4662
+ include({
4663
+ x: quadraticAt(current.x, x1, x, t),
4664
+ y: quadraticAt(current.y, y1, y, t),
4665
+ });
4666
+ }
4667
+ current = { x, y };
4668
+ break;
4669
+ }
4670
+ case "A": {
4671
+ for (const point of sampleArc(current, segment.values)) {
4672
+ include(point);
4673
+ }
4674
+ current = { x: segment.values[5], y: segment.values[6] };
4675
+ break;
4676
+ }
4677
+ case "Z": {
4678
+ include(current);
4679
+ include(subpathStart);
4680
+ current = { ...subpathStart };
4681
+ break;
4682
+ }
4683
+ }
4684
+ }
4685
+ if (!Number.isFinite(minX) || !Number.isFinite(minY) || !Number.isFinite(maxX) || !Number.isFinite(maxY)) {
4686
+ return null;
4687
+ }
4688
+ return { minX, minY, maxX, maxY };
4689
+ }
4690
+ function transformX(x, bounds, scaleX) {
4691
+ return (x - bounds.minX) * scaleX;
4692
+ }
4693
+ function transformY(y, bounds, scaleY) {
4694
+ return (y - bounds.minY) * scaleY;
4695
+ }
4696
+ function buildScaledPathData(segments, bounds, width, height) {
4697
+ const sourceWidth = Math.max(bounds.maxX - bounds.minX, EPSILON);
4698
+ const sourceHeight = Math.max(bounds.maxY - bounds.minY, EPSILON);
4699
+ const scaleX = width / sourceWidth;
4700
+ const scaleY = height / sourceHeight;
4701
+ return segments
4702
+ .map((segment) => {
4703
+ switch (segment.command) {
4704
+ case "M":
4705
+ case "L":
4706
+ return [
4707
+ segment.command,
4708
+ formatNumber(transformX(segment.values[0], bounds, scaleX)),
4709
+ formatNumber(transformY(segment.values[1], bounds, scaleY)),
4710
+ ].join(" ");
4711
+ case "C":
4712
+ return [
4713
+ "C",
4714
+ formatNumber(transformX(segment.values[0], bounds, scaleX)),
4715
+ formatNumber(transformY(segment.values[1], bounds, scaleY)),
4716
+ formatNumber(transformX(segment.values[2], bounds, scaleX)),
4717
+ formatNumber(transformY(segment.values[3], bounds, scaleY)),
4718
+ formatNumber(transformX(segment.values[4], bounds, scaleX)),
4719
+ formatNumber(transformY(segment.values[5], bounds, scaleY)),
4720
+ ].join(" ");
4721
+ case "Q":
4722
+ return [
4723
+ "Q",
4724
+ formatNumber(transformX(segment.values[0], bounds, scaleX)),
4725
+ formatNumber(transformY(segment.values[1], bounds, scaleY)),
4726
+ formatNumber(transformX(segment.values[2], bounds, scaleX)),
4727
+ formatNumber(transformY(segment.values[3], bounds, scaleY)),
4728
+ ].join(" ");
4729
+ case "A":
4730
+ return [
4731
+ "A",
4732
+ formatNumber(segment.values[0] * scaleX),
4733
+ formatNumber(segment.values[1] * scaleY),
4734
+ formatNumber(segment.values[2]),
4735
+ formatNumber(segment.values[3]),
4736
+ formatNumber(segment.values[4]),
4737
+ formatNumber(transformX(segment.values[5], bounds, scaleX)),
4738
+ formatNumber(transformY(segment.values[6], bounds, scaleY)),
4739
+ ].join(" ");
4740
+ case "Z":
4741
+ return "Z";
4742
+ }
4743
+ })
4744
+ .join(" ");
4745
+ }
4746
+ function intrinsicSizeFromBounds(bounds) {
4747
+ if (!bounds)
4748
+ return { width: 100, height: 100 };
4749
+ return {
4750
+ width: Math.max(1, Math.ceil(bounds.maxX - bounds.minX)),
4751
+ height: Math.max(1, Math.ceil(bounds.maxY - bounds.minY)),
4752
+ };
4753
+ }
4754
+ function parsePathGeometry(pathData) {
4755
+ const segments = toAbsoluteSegments(parseRawSegments(pathData));
4756
+ return {
4757
+ segments,
4758
+ bounds: boundsFromAbsoluteSegments(segments),
4759
+ };
4760
+ }
4761
+ function getPathIntrinsicSize(pathData) {
4762
+ if (!pathData)
4763
+ return { width: 100, height: 100 };
4764
+ return intrinsicSizeFromBounds(parsePathGeometry(pathData).bounds);
4765
+ }
4766
+ function getRenderablePathData(pathData, width, height) {
4767
+ if (!pathData)
4768
+ return null;
4769
+ const { segments, bounds } = parsePathGeometry(pathData);
4770
+ if (!segments.length || !bounds)
4771
+ return pathData;
4772
+ return buildScaledPathData(segments, bounds, Math.max(1, width), Math.max(1, height));
4773
+ }
4774
+ function getRenderableNodePathData(node) {
4775
+ return getRenderablePathData(node.pathData, node.w, node.h);
4776
+ }
4777
+
4254
4778
  const pathShape = {
4255
4779
  size(n, labelW) {
4256
- // User should provide width/height; defaults to 100x100
4257
- const w = n.width ?? Math.max(100, Math.min(300, labelW + 20));
4780
+ const intrinsic = getPathIntrinsicSize(n.pathData);
4781
+ const w = n.width ?? Math.max(intrinsic.width, Math.min(300, labelW + 20));
4258
4782
  n.w = w;
4259
4783
  if (!n.h) {
4260
- if (!n.width && labelW + 20 > w) {
4784
+ if (!n.width && !n.height && labelW + 20 > w) {
4261
4785
  const fontSize = Number(n.style?.fontSize ?? 14);
4262
4786
  const lines = Math.ceil(labelW / (w - 20));
4263
- n.h = Math.max(100, lines * fontSize * 1.5 + 20);
4787
+ n.h = Math.max(intrinsic.height, lines * fontSize * 1.5 + 20);
4264
4788
  }
4265
4789
  else {
4266
- n.h = n.height ?? 100;
4790
+ n.h = n.height ?? intrinsic.height;
4267
4791
  }
4268
4792
  }
4269
4793
  },
4270
4794
  renderSVG(rc, n, _palette, opts) {
4271
- const d = n.pathData;
4795
+ const d = getRenderableNodePathData(n);
4272
4796
  if (!d) {
4273
- // No path data — render placeholder box
4274
4797
  return [rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, opts)];
4275
4798
  }
4276
4799
  const el = rc.path(d, opts);
4277
- // Wrap in a group to translate the user's path to the node position
4278
4800
  const g = document.createElementNS(SVG_NS, "g");
4279
4801
  g.setAttribute("transform", `translate(${n.x},${n.y})`);
4280
4802
  g.appendChild(el);
4281
4803
  return [g];
4282
4804
  },
4283
4805
  renderCanvas(rc, ctx, n, _palette, opts) {
4284
- const d = n.pathData;
4806
+ const d = getRenderableNodePathData(n);
4285
4807
  if (!d) {
4286
4808
  rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, opts);
4287
4809
  return;
@@ -4588,6 +5110,50 @@ var AIDiagram = (function (exports) {
4588
5110
  const t = Math.min(tx, ty);
4589
5111
  return [cx + t * dx, cy + t * dy];
4590
5112
  }
5113
+ function clampInset(value) {
5114
+ return Math.max(2, value);
5115
+ }
5116
+ function anchoredConnPoint(entity, anchor, otherCX, otherCY) {
5117
+ if (!anchor) {
5118
+ if (entity.shape && otherCX != null && otherCY != null) {
5119
+ return connPoint(entity, { x: otherCX - 1, y: otherCY - 1, w: 2, h: 2});
5120
+ }
5121
+ if (otherCX != null && otherCY != null) {
5122
+ return rectConnPoint(entity.x, entity.y, entity.w, entity.h, otherCX, otherCY);
5123
+ }
5124
+ return [entity.x + entity.w / 2, entity.y + entity.h / 2];
5125
+ }
5126
+ const insetX = clampInset(Math.min(10, entity.w / 2));
5127
+ const insetY = clampInset(Math.min(10, entity.h / 2));
5128
+ const left = entity.x + insetX;
5129
+ const right = entity.x + entity.w - insetX;
5130
+ const top = entity.y + insetY;
5131
+ const bottom = entity.y + entity.h - insetY;
5132
+ const cx = entity.x + entity.w / 2;
5133
+ const cy = entity.y + entity.h / 2;
5134
+ switch (anchor) {
5135
+ case "top":
5136
+ return [cx, top];
5137
+ case "right":
5138
+ return [right, cy];
5139
+ case "bottom":
5140
+ return [cx, bottom];
5141
+ case "left":
5142
+ return [left, cy];
5143
+ case "center":
5144
+ return [cx, cy];
5145
+ case "top-left":
5146
+ return [left, top];
5147
+ case "top-right":
5148
+ return [right, top];
5149
+ case "bottom-left":
5150
+ return [left, bottom];
5151
+ case "bottom-right":
5152
+ return [right, bottom];
5153
+ default:
5154
+ return [cx, cy];
5155
+ }
5156
+ }
4591
5157
  function rectConnPoint(rx, ry, rw, rh, ox, oy) {
4592
5158
  const cx = rx + rw / 2, cy = ry + rh / 2;
4593
5159
  const dx = ox - cx, dy = oy - cy;
@@ -4619,17 +5185,6 @@ var AIDiagram = (function (exports) {
4619
5185
  return c;
4620
5186
  return null;
4621
5187
  }
4622
- function connPt(src, dstCX, dstCY) {
4623
- // SceneNode has a .shape field; use the existing connPoint for it
4624
- if ("shape" in src && src.shape) {
4625
- return connPoint(src, {
4626
- x: dstCX - 1,
4627
- y: dstCY - 1,
4628
- w: 2,
4629
- h: 2});
4630
- }
4631
- return rectConnPoint(src.x, src.y, src.w, src.h, dstCX, dstCY);
4632
- }
4633
5188
  for (const e of sg.edges) {
4634
5189
  const src = resolve(e.from);
4635
5190
  const dst = resolve(e.to);
@@ -4639,7 +5194,10 @@ var AIDiagram = (function (exports) {
4639
5194
  }
4640
5195
  const dstCX = dst.x + dst.w / 2, dstCY = dst.y + dst.h / 2;
4641
5196
  const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
4642
- e.points = [connPt(src, dstCX, dstCY), connPt(dst, srcCX, srcCY)];
5197
+ e.points = [
5198
+ anchoredConnPoint(src, e.fromAnchor, dstCX, dstCY),
5199
+ anchoredConnPoint(dst, e.toAnchor, srcCX, srcCY),
5200
+ ];
4643
5201
  }
4644
5202
  }
4645
5203
  function computeBounds(sg, margin) {
@@ -7780,8 +8338,8 @@ var AIDiagram = (function (exports) {
7780
8338
  continue;
7781
8339
  const dstCX = dst.x + dst.w / 2, dstCY = dst.y + dst.h / 2;
7782
8340
  const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
7783
- const [x1, y1] = getConnPoint(src, dstCX, dstCY);
7784
- const [x2, y2] = getConnPoint(dst, srcCX, srcCY);
8341
+ const [x1, y1] = getConnPoint(src, dstCX, dstCY, e.fromAnchor);
8342
+ const [x2, y2] = getConnPoint(dst, srcCX, srcCY, e.toAnchor);
7785
8343
  const eg = mkGroup(`edge-${e.from}-${e.to}`, "eg");
7786
8344
  if (e.style?.opacity != null)
7787
8345
  eg.setAttribute("opacity", String(e.style.opacity));
@@ -7857,7 +8415,9 @@ var AIDiagram = (function (exports) {
7857
8415
  ng.dataset.w = String(n.w);
7858
8416
  ng.dataset.h = String(n.h);
7859
8417
  if (n.pathData)
7860
- ng.dataset.pathData = n.pathData;
8418
+ ng.dataset.pathData = getRenderableNodePathData(n) ?? n.pathData;
8419
+ if (n.meta?.animationParent)
8420
+ ng.dataset.animationParent = n.meta.animationParent;
7861
8421
  if (n.style?.opacity != null)
7862
8422
  ng.setAttribute("opacity", String(n.style.opacity));
7863
8423
  // ── Static transform (deg, dx, dy, factor) ──────────
@@ -8511,8 +9071,8 @@ var AIDiagram = (function (exports) {
8511
9071
  continue;
8512
9072
  const dstCX = dst.x + dst.w / 2, dstCY = dst.y + dst.h / 2;
8513
9073
  const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
8514
- const [x1, y1] = getConnPoint(src, dstCX, dstCY);
8515
- const [x2, y2] = getConnPoint(dst, srcCX, srcCY);
9074
+ const [x1, y1] = getConnPoint(src, dstCX, dstCY, e.fromAnchor);
9075
+ const [x2, y2] = getConnPoint(dst, srcCX, srcCY, e.toAnchor);
8516
9076
  if (e.style?.opacity != null)
8517
9077
  ctx.globalAlpha = Number(e.style.opacity);
8518
9078
  const ecol = String(e.style?.stroke ?? palette.edgeStroke);
@@ -9369,6 +9929,13 @@ var AIDiagram = (function (exports) {
9369
9929
  this.drawTargetNodes.delete(`node-${s.target}`);
9370
9930
  }
9371
9931
  }
9932
+ this._relatedElementIdsByPrimaryId = this._buildRelatedElementIndex();
9933
+ for (const nodeId of Array.from(this.drawTargetNodes)) {
9934
+ const relatedIds = this._relatedElementIdsByPrimaryId.get(nodeId);
9935
+ if (!relatedIds)
9936
+ continue;
9937
+ relatedIds.forEach((id) => this.drawTargetNodes.add(id));
9938
+ }
9372
9939
  this._drawStepIndexByElementId = this._buildDrawStepIndex();
9373
9940
  const { parentGroupByElementId, groupDescendantIds } = this._buildGroupVisibilityIndex();
9374
9941
  this._parentGroupByElementId = parentGroupByElementId;
@@ -9399,10 +9966,30 @@ var AIDiagram = (function (exports) {
9399
9966
  const el = resolveNonEdgeDrawEl(this.svg, step.target);
9400
9967
  if (el && !drawStepIndexByElementId.has(el.id)) {
9401
9968
  drawStepIndexByElementId.set(el.id, stepIndex);
9969
+ this._relatedElementIdsByPrimaryId.get(el.id)?.forEach((relatedId) => {
9970
+ if (!drawStepIndexByElementId.has(relatedId)) {
9971
+ drawStepIndexByElementId.set(relatedId, stepIndex);
9972
+ }
9973
+ });
9402
9974
  }
9403
9975
  });
9404
9976
  return drawStepIndexByElementId;
9405
9977
  }
9978
+ _buildRelatedElementIndex() {
9979
+ const relatedElementIdsByPrimaryId = new Map();
9980
+ this.svg.querySelectorAll(POSITIONABLE_SELECTOR).forEach((el) => {
9981
+ const animationParent = el.dataset.animationParent;
9982
+ if (!animationParent)
9983
+ return;
9984
+ const primaryEl = resolveNonEdgeDrawEl(this.svg, animationParent);
9985
+ if (!primaryEl || primaryEl.id === el.id)
9986
+ return;
9987
+ const related = relatedElementIdsByPrimaryId.get(primaryEl.id) ?? new Set();
9988
+ related.add(el.id);
9989
+ relatedElementIdsByPrimaryId.set(primaryEl.id, related);
9990
+ });
9991
+ return relatedElementIdsByPrimaryId;
9992
+ }
9406
9993
  _buildGroupVisibilityIndex() {
9407
9994
  const parentGroupByElementId = new Map();
9408
9995
  const directChildIdsByGroup = new Map();
@@ -9481,10 +10068,18 @@ var AIDiagram = (function (exports) {
9481
10068
  const el = resolveEl(this.svg, target);
9482
10069
  if (!el)
9483
10070
  return [];
9484
- if (!el.id.startsWith("group-"))
9485
- return [el];
10071
+ if (!el.id.startsWith("group-")) {
10072
+ const ids = new Set([el.id]);
10073
+ this._relatedElementIdsByPrimaryId.get(el.id)?.forEach((id) => ids.add(id));
10074
+ return Array.from(ids)
10075
+ .map((id) => getEl(this.svg, id))
10076
+ .filter((candidate) => candidate != null);
10077
+ }
9486
10078
  const ids = new Set([el.id]);
9487
10079
  this._groupDescendantIds.get(el.id)?.forEach((id) => ids.add(id));
10080
+ Array.from(ids).forEach((id) => {
10081
+ this._relatedElementIdsByPrimaryId.get(id)?.forEach((relatedId) => ids.add(relatedId));
10082
+ });
9488
10083
  return Array.from(ids)
9489
10084
  .map((id) => getEl(this.svg, id))
9490
10085
  .filter((candidate) => candidate != null);
@@ -9893,9 +10488,11 @@ var AIDiagram = (function (exports) {
9893
10488
  // ── highlight ────────────────────────────────────────────
9894
10489
  _doHighlight(target) {
9895
10490
  this.svg
9896
- .querySelectorAll(".ng.hl, .tg.hl, .ntg.hl, .cg.hl, .eg.hl")
10491
+ .querySelectorAll(".ng.hl, .gg.hl, .tg.hl, .ntg.hl, .cg.hl, .mdg.hl, .eg.hl")
9897
10492
  .forEach((e) => e.classList.remove("hl"));
9898
- resolveEl(this.svg, target)?.classList.add("hl");
10493
+ for (const el of this._resolveCascadeTargets(target)) {
10494
+ el.classList.add("hl");
10495
+ }
9899
10496
  }
9900
10497
  // ── fade / unfade ─────────────────────────────────────────
9901
10498
  _doFade(target, doFade) {
@@ -9931,8 +10528,8 @@ var AIDiagram = (function (exports) {
9931
10528
  }
9932
10529
  // ── move ──────────────────────────────────────────────────
9933
10530
  _doMove(target, step, silent) {
9934
- const el = resolveEl(this.svg, target);
9935
- if (!el)
10531
+ const targets = this._resolveCascadeTargets(target);
10532
+ if (!targets.length)
9936
10533
  return;
9937
10534
  const cur = this._transforms.get(target) ?? {
9938
10535
  tx: 0,
@@ -9945,12 +10542,14 @@ var AIDiagram = (function (exports) {
9945
10542
  tx: cur.tx + (step.dx ?? 0),
9946
10543
  ty: cur.ty + (step.dy ?? 0),
9947
10544
  });
9948
- this._writeTransform(el, target, silent, step.duration ?? 420);
10545
+ for (const el of targets) {
10546
+ this._writeTransform(el, target, silent, step.duration ?? 420);
10547
+ }
9949
10548
  }
9950
10549
  // ── scale ─────────────────────────────────────────────────
9951
10550
  _doScale(target, step, silent) {
9952
- const el = resolveEl(this.svg, target);
9953
- if (!el)
10551
+ const targets = this._resolveCascadeTargets(target);
10552
+ if (!targets.length)
9954
10553
  return;
9955
10554
  const cur = this._transforms.get(target) ?? {
9956
10555
  tx: 0,
@@ -9959,12 +10558,14 @@ var AIDiagram = (function (exports) {
9959
10558
  rotate: 0,
9960
10559
  };
9961
10560
  this._transforms.set(target, { ...cur, scale: step.factor ?? 1 });
9962
- this._writeTransform(el, target, silent, step.duration ?? 350);
10561
+ for (const el of targets) {
10562
+ this._writeTransform(el, target, silent, step.duration ?? 350);
10563
+ }
9963
10564
  }
9964
10565
  // ── rotate ────────────────────────────────────────────────
9965
10566
  _doRotate(target, step, silent) {
9966
- const el = resolveEl(this.svg, target);
9967
- if (!el)
10567
+ const targets = this._resolveCascadeTargets(target);
10568
+ if (!targets.length)
9968
10569
  return;
9969
10570
  const cur = this._transforms.get(target) ?? {
9970
10571
  tx: 0,
@@ -9976,7 +10577,9 @@ var AIDiagram = (function (exports) {
9976
10577
  ...cur,
9977
10578
  rotate: cur.rotate + (step.deg ?? 0),
9978
10579
  });
9979
- this._writeTransform(el, target, silent, step.duration ?? 400);
10580
+ for (const el of targets) {
10581
+ this._writeTransform(el, target, silent, step.duration ?? 400);
10582
+ }
9980
10583
  }
9981
10584
  _doDraw(step, silent) {
9982
10585
  const { target } = step;
@@ -10120,18 +10723,20 @@ var AIDiagram = (function (exports) {
10120
10723
  return;
10121
10724
  }
10122
10725
  // ── Node draw ──────────────────────────────────────
10123
- const nodeEl = getNodeEl(this.svg, target);
10124
- if (!nodeEl)
10726
+ const nodeEls = this._resolveCascadeTargets(target).filter((el) => el.classList.contains("ng"));
10727
+ if (!nodeEls.length)
10125
10728
  return;
10126
- showDrawEl(nodeEl);
10127
- if (silent) {
10128
- revealNodeInstant(nodeEl);
10129
- }
10130
- else {
10131
- if (!nodeGuidePathEl(nodeEl) && !nodeEl.querySelector("path")?.style.strokeDasharray) {
10132
- prepareNodeForDraw(nodeEl);
10729
+ for (const nodeEl of nodeEls) {
10730
+ showDrawEl(nodeEl);
10731
+ if (silent) {
10732
+ revealNodeInstant(nodeEl);
10733
+ }
10734
+ else {
10735
+ if (!nodeGuidePathEl(nodeEl) && !nodeEl.querySelector("path")?.style.strokeDasharray) {
10736
+ prepareNodeForDraw(nodeEl);
10737
+ }
10738
+ animateNodeDraw(nodeEl, step.duration ?? ANIMATION.nodeStrokeDur, step.duration ?? ANIMATION.textRevealMs);
10133
10739
  }
10134
- animateNodeDraw(nodeEl, step.duration ?? ANIMATION.nodeStrokeDur, step.duration ?? ANIMATION.textRevealMs);
10135
10740
  }
10136
10741
  }
10137
10742
  // ── erase ─────────────────────────────────────────────────
@@ -10152,45 +10757,44 @@ var AIDiagram = (function (exports) {
10152
10757
  }
10153
10758
  // ── pulse ─────────────────────────────────────────────────
10154
10759
  _doPulse(target, duration = 500) {
10155
- resolveEl(this.svg, target)?.animate([
10156
- { filter: "brightness(1)" },
10157
- { filter: "brightness(1.6)" },
10158
- { filter: "brightness(1)" },
10159
- ], { duration, iterations: 3 });
10760
+ for (const el of this._resolveCascadeTargets(target)) {
10761
+ el.animate([
10762
+ { filter: "brightness(1)" },
10763
+ { filter: "brightness(1.6)" },
10764
+ { filter: "brightness(1)" },
10765
+ ], { duration, iterations: 3 });
10766
+ }
10160
10767
  }
10161
10768
  // ── color ─────────────────────────────────────────────────
10162
10769
  _doColor(target, color) {
10163
10770
  if (!color)
10164
10771
  return;
10165
- const el = resolveEl(this.svg, target);
10166
- if (!el)
10167
- return;
10168
- // edge color stroke
10169
- if (parseEdgeTarget(target)) {
10170
- el.querySelectorAll("path, line, polyline").forEach((p) => {
10171
- p.style.stroke = color;
10172
- });
10173
- el.querySelectorAll("polygon").forEach((p) => {
10174
- p.style.fill = color;
10175
- p.style.stroke = color;
10176
- });
10177
- return;
10178
- }
10179
- // everything else — color fill
10180
- let hit = false;
10181
- el.querySelectorAll("path, rect, ellipse, polygon").forEach((c) => {
10182
- const attrFill = c.getAttribute("fill");
10183
- if (attrFill === "none")
10184
- return;
10185
- if (attrFill === null && c.tagName === "path")
10186
- return;
10187
- c.style.fill = color;
10188
- hit = true;
10189
- });
10190
- if (!hit) {
10191
- el.querySelectorAll("text").forEach((t) => {
10192
- t.style.fill = color;
10772
+ for (const el of this._resolveCascadeTargets(target)) {
10773
+ if (parseEdgeTarget(target)) {
10774
+ el.querySelectorAll("path, line, polyline").forEach((p) => {
10775
+ p.style.stroke = color;
10776
+ });
10777
+ el.querySelectorAll("polygon").forEach((p) => {
10778
+ p.style.fill = color;
10779
+ p.style.stroke = color;
10780
+ });
10781
+ continue;
10782
+ }
10783
+ let hit = false;
10784
+ el.querySelectorAll("path, rect, ellipse, polygon").forEach((c) => {
10785
+ const attrFill = c.getAttribute("fill");
10786
+ if (attrFill === "none")
10787
+ return;
10788
+ if (attrFill === null && c.tagName === "path")
10789
+ return;
10790
+ c.style.fill = color;
10791
+ hit = true;
10193
10792
  });
10793
+ if (!hit) {
10794
+ el.querySelectorAll("text").forEach((t) => {
10795
+ t.style.fill = color;
10796
+ });
10797
+ }
10194
10798
  }
10195
10799
  }
10196
10800
  // ── narration ───────────────────────────────────────────
@@ -10784,7 +11388,7 @@ var AIDiagram = (function (exports) {
10784
11388
  }
10785
11389
 
10786
11390
  function render(options) {
10787
- const { container: rawContainer, dsl, renderer = "svg", injectCSS = true, tts, svgOptions = {}, canvasOptions = {}, onNodeClick, onReady, } = options;
11391
+ const { container: rawContainer, dsl, plugins, renderer = "svg", injectCSS = true, tts, svgOptions = {}, canvasOptions = {}, onNodeClick, onReady, } = options;
10788
11392
  if (injectCSS && !document.getElementById("ai-diagram-css")) {
10789
11393
  const style = document.createElement("style");
10790
11394
  style.id = "ai-diagram-css";
@@ -10800,7 +11404,7 @@ var AIDiagram = (function (exports) {
10800
11404
  else {
10801
11405
  el = rawContainer;
10802
11406
  }
10803
- const ast = parse(dsl);
11407
+ const ast = parse(dsl, { plugins });
10804
11408
  const scene = buildSceneGraph(ast);
10805
11409
  layout(scene);
10806
11410
  let svg;
@@ -11144,6 +11748,7 @@ var AIDiagram = (function (exports) {
11144
11748
  const instance = render({
11145
11749
  container: this.diagramWrap,
11146
11750
  dsl: this.dsl,
11751
+ plugins: this.options.plugins,
11147
11752
  renderer: this.renderer,
11148
11753
  svgOptions: { interactive: true, showTitle: true, theme: this.options.svgOptions?.theme ?? this.theme, ...this.options.svgOptions },
11149
11754
  canvasOptions: this.options.canvasOptions,
@@ -12221,6 +12826,7 @@ var AIDiagram = (function (exports) {
12221
12826
  const instance = render({
12222
12827
  container: this.diagramWrap,
12223
12828
  dsl: this.dsl,
12829
+ plugins: this.options.plugins,
12224
12830
  renderer: "svg",
12225
12831
  svgOptions: {
12226
12832
  showTitle: true,