sketchmark 1.1.6 → 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.
package/dist/index.cjs CHANGED
@@ -146,10 +146,16 @@ function tokenize$1(src) {
146
146
  val += "\n";
147
147
  else if (esc === "t")
148
148
  val += "\t";
149
+ else if (esc === "r")
150
+ val += "\r";
149
151
  else if (esc === "\\")
150
152
  val += "\\";
153
+ else if (esc === q)
154
+ val += q;
155
+ else if (esc)
156
+ val += `\\${esc}`;
151
157
  else
152
- val += esc;
158
+ val += "\\";
153
159
  }
154
160
  else
155
161
  val += src[i];
@@ -226,6 +232,47 @@ function tokenize$1(src) {
226
232
  return tokens;
227
233
  }
228
234
 
235
+ function pluginMessage(plugin, stage, error) {
236
+ const detail = error instanceof Error ? error.message : String(error);
237
+ return `Plugin "${plugin.name}" ${stage} failed: ${detail}`;
238
+ }
239
+ function applyPluginPreprocessors(source, plugins = []) {
240
+ let nextSource = source;
241
+ for (const plugin of plugins) {
242
+ if (!plugin.preprocess)
243
+ continue;
244
+ try {
245
+ const transformed = plugin.preprocess(nextSource);
246
+ if (typeof transformed !== "string") {
247
+ throw new Error("preprocess must return a string");
248
+ }
249
+ nextSource = transformed;
250
+ }
251
+ catch (error) {
252
+ throw new Error(pluginMessage(plugin, "preprocess", error));
253
+ }
254
+ }
255
+ return nextSource;
256
+ }
257
+ function applyPluginAstTransforms(ast, plugins = []) {
258
+ let nextAst = ast;
259
+ for (const plugin of plugins) {
260
+ if (!plugin.transformAst)
261
+ continue;
262
+ try {
263
+ const transformed = plugin.transformAst(nextAst);
264
+ if (!transformed || transformed.kind !== "diagram") {
265
+ throw new Error('transformAst must return a DiagramAST with kind="diagram"');
266
+ }
267
+ nextAst = transformed;
268
+ }
269
+ catch (error) {
270
+ throw new Error(pluginMessage(plugin, "transformAst", error));
271
+ }
272
+ }
273
+ return nextAst;
274
+ }
275
+
229
276
  // ============================================================
230
277
  // sketchmark - Parser (Tokens -> DiagramAST)
231
278
  // ============================================================
@@ -312,9 +359,10 @@ function isValueToken(t) {
312
359
  function isPropKeyToken(t) {
313
360
  return !!t && (t.type === "IDENT" || t.type === "KEYWORD");
314
361
  }
315
- function parse(src) {
362
+ function parse(src, options = {}) {
316
363
  resetUid();
317
- const tokens = tokenize$1(src).filter((t) => t.type !== "NEWLINE" || t.value === "\n");
364
+ const preparedSource = applyPluginPreprocessors(src, options.plugins);
365
+ const tokens = tokenize$1(preparedSource).filter((t) => t.type !== "NEWLINE" || t.value === "\n");
318
366
  const flat = [];
319
367
  let lastNL = false;
320
368
  for (const t of tokens) {
@@ -497,6 +545,7 @@ function parse(src) {
497
545
  const toks = lineTokens();
498
546
  const id = requireExplicitId(keywordTok, toks);
499
547
  const props = parseSimpleProps(toks, 1);
548
+ const meta = extractNodeMeta(props);
500
549
  const node = {
501
550
  kind: "node",
502
551
  id,
@@ -511,6 +560,7 @@ function parse(src) {
511
560
  ...(props.dy ? { dy: parseFloat(props.dy) } : {}),
512
561
  ...(props.factor ? { factor: parseFloat(props.factor) } : {}),
513
562
  ...(props.theme ? { theme: props.theme } : {}),
563
+ ...(meta ? { meta } : {}),
514
564
  style: propsToStyle(props),
515
565
  };
516
566
  if (props.url)
@@ -535,12 +585,14 @@ function parse(src) {
535
585
  j = 2;
536
586
  }
537
587
  Object.assign(props, parseSimpleProps(toks, j));
588
+ const meta = extractNodeMeta(props);
538
589
  return {
539
590
  kind: "node",
540
591
  id,
541
592
  shape: "note",
542
593
  label: (props.label ?? "").replace(/\\n/g, "\n"),
543
594
  theme: props.theme,
595
+ ...(meta ? { meta } : {}),
544
596
  style: propsToStyle(props),
545
597
  ...(props.width ? { width: parseFloat(props.width) } : {}),
546
598
  ...(props.height ? { height: parseFloat(props.height) } : {}),
@@ -552,6 +604,13 @@ function parse(src) {
552
604
  ...(props.factor ? { factor: parseFloat(props.factor) } : {}),
553
605
  };
554
606
  }
607
+ function extractNodeMeta(props) {
608
+ const meta = {};
609
+ if (props["animation-parent"]) {
610
+ meta.animationParent = props["animation-parent"];
611
+ }
612
+ return Object.keys(meta).length ? meta : undefined;
613
+ }
555
614
  function parseGroup() {
556
615
  const keywordTok = cur();
557
616
  skip();
@@ -624,6 +683,8 @@ function parse(src) {
624
683
  to: toTok.value,
625
684
  connector: connector,
626
685
  label: props.label,
686
+ fromAnchor: props["anchor-from"],
687
+ toAnchor: props["anchor-to"],
627
688
  dashed,
628
689
  bidirectional,
629
690
  style: propsToStyle(props),
@@ -937,6 +998,7 @@ function parse(src) {
937
998
  registerAuthoredId(grp.id, "group", t);
938
999
  if (isBare) {
939
1000
  grp.label = "";
1001
+ grp.padding = grp.padding ?? 0;
940
1002
  grp.style = {
941
1003
  ...grp.style,
942
1004
  fill: grp.style?.fill ?? "none",
@@ -1107,7 +1169,7 @@ function parse(src) {
1107
1169
  node.style = { ...ast.styles[node.id], ...node.style };
1108
1170
  }
1109
1171
  }
1110
- return ast;
1172
+ return applyPluginAstTransforms(ast, options.plugins);
1111
1173
  }
1112
1174
 
1113
1175
  // ============================================================
@@ -3554,6 +3616,8 @@ function buildSceneGraph(ast) {
3554
3616
  to: e.to,
3555
3617
  connector: e.connector,
3556
3618
  label: e.label,
3619
+ fromAnchor: e.fromAnchor,
3620
+ toAnchor: e.toAnchor,
3557
3621
  dashed: e.dashed ?? false,
3558
3622
  bidirectional: e.bidirectional ?? false,
3559
3623
  style: e.style ?? {},
@@ -4131,28 +4195,13 @@ function connMeta(connector) {
4131
4195
  return { arrowAt: "start", dashed };
4132
4196
  return { arrowAt: "end", dashed };
4133
4197
  }
4134
- // ── Generic rect connection point ────────────────────────────────────────
4135
- function rectConnPoint$1(rx, ry, rw, rh, ox, oy) {
4136
- const cx = rx + rw / 2, cy = ry + rh / 2;
4137
- const dx = ox - cx, dy = oy - cy;
4138
- if (Math.abs(dx) < 0.01 && Math.abs(dy) < 0.01)
4139
- return [cx, cy];
4140
- const hw = rw / 2 - 2, hh = rh / 2 - 2;
4141
- const tx = Math.abs(dx) > 0.01 ? hw / Math.abs(dx) : 1e9;
4142
- const ty = Math.abs(dy) > 0.01 ? hh / Math.abs(dy) : 1e9;
4143
- const t = Math.min(tx, ty);
4144
- return [cx + t * dx, cy + t * dy];
4145
- }
4146
4198
  // ── Resolve an endpoint entity by ID across all maps ─────────────────────
4147
4199
  function resolveEndpoint(id, nm, tm, gm, cm) {
4148
4200
  return nm.get(id) ?? tm.get(id) ?? gm.get(id) ?? cm.get(id) ?? null;
4149
4201
  }
4150
4202
  // ── Get connection point for any entity ──────────────────────────────────
4151
- function getConnPoint(src, dstCX, dstCY) {
4152
- if ("shape" in src && src.shape) {
4153
- return connPoint(src, { x: dstCX - 1, y: dstCY - 1, w: 2, h: 2});
4154
- }
4155
- return rectConnPoint$1(src.x, src.y, src.w, src.h, dstCX, dstCY);
4203
+ function getConnPoint(src, dstCX, dstCY, anchor) {
4204
+ return anchoredConnPoint(src, anchor, dstCX, dstCY);
4156
4205
  }
4157
4206
  // ── Group depth (for paint order) ────────────────────────────────────────
4158
4207
  function groupDepth(g, gm) {
@@ -4587,6 +4636,50 @@ function connPoint(n, other) {
4587
4636
  const t = Math.min(tx, ty);
4588
4637
  return [cx + t * dx, cy + t * dy];
4589
4638
  }
4639
+ function clampInset(value) {
4640
+ return Math.max(2, value);
4641
+ }
4642
+ function anchoredConnPoint(entity, anchor, otherCX, otherCY) {
4643
+ if (!anchor) {
4644
+ if (entity.shape && otherCX != null && otherCY != null) {
4645
+ return connPoint(entity, { x: otherCX - 1, y: otherCY - 1, w: 2, h: 2});
4646
+ }
4647
+ if (otherCX != null && otherCY != null) {
4648
+ return rectConnPoint(entity.x, entity.y, entity.w, entity.h, otherCX, otherCY);
4649
+ }
4650
+ return [entity.x + entity.w / 2, entity.y + entity.h / 2];
4651
+ }
4652
+ const insetX = clampInset(Math.min(10, entity.w / 2));
4653
+ const insetY = clampInset(Math.min(10, entity.h / 2));
4654
+ const left = entity.x + insetX;
4655
+ const right = entity.x + entity.w - insetX;
4656
+ const top = entity.y + insetY;
4657
+ const bottom = entity.y + entity.h - insetY;
4658
+ const cx = entity.x + entity.w / 2;
4659
+ const cy = entity.y + entity.h / 2;
4660
+ switch (anchor) {
4661
+ case "top":
4662
+ return [cx, top];
4663
+ case "right":
4664
+ return [right, cy];
4665
+ case "bottom":
4666
+ return [cx, bottom];
4667
+ case "left":
4668
+ return [left, cy];
4669
+ case "center":
4670
+ return [cx, cy];
4671
+ case "top-left":
4672
+ return [left, top];
4673
+ case "top-right":
4674
+ return [right, top];
4675
+ case "bottom-left":
4676
+ return [left, bottom];
4677
+ case "bottom-right":
4678
+ return [right, bottom];
4679
+ default:
4680
+ return [cx, cy];
4681
+ }
4682
+ }
4590
4683
  function rectConnPoint(rx, ry, rw, rh, ox, oy) {
4591
4684
  const cx = rx + rw / 2, cy = ry + rh / 2;
4592
4685
  const dx = ox - cx, dy = oy - cy;
@@ -4618,17 +4711,6 @@ function routeEdges(sg) {
4618
4711
  return c;
4619
4712
  return null;
4620
4713
  }
4621
- function connPt(src, dstCX, dstCY) {
4622
- // SceneNode has a .shape field; use the existing connPoint for it
4623
- if ("shape" in src && src.shape) {
4624
- return connPoint(src, {
4625
- x: dstCX - 1,
4626
- y: dstCY - 1,
4627
- w: 2,
4628
- h: 2});
4629
- }
4630
- return rectConnPoint(src.x, src.y, src.w, src.h, dstCX, dstCY);
4631
- }
4632
4714
  for (const e of sg.edges) {
4633
4715
  const src = resolve(e.from);
4634
4716
  const dst = resolve(e.to);
@@ -4638,7 +4720,10 @@ function routeEdges(sg) {
4638
4720
  }
4639
4721
  const dstCX = dst.x + dst.w / 2, dstCY = dst.y + dst.h / 2;
4640
4722
  const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
4641
- e.points = [connPt(src, dstCX, dstCY), connPt(dst, srcCX, srcCY)];
4723
+ e.points = [
4724
+ anchoredConnPoint(src, e.fromAnchor, dstCX, dstCY),
4725
+ anchoredConnPoint(dst, e.toAnchor, srcCX, srcCY),
4726
+ ];
4642
4727
  }
4643
4728
  }
4644
4729
  function computeBounds(sg, margin) {
@@ -7779,8 +7864,8 @@ function renderToSVG(sg, container, options = {}) {
7779
7864
  continue;
7780
7865
  const dstCX = dst.x + dst.w / 2, dstCY = dst.y + dst.h / 2;
7781
7866
  const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
7782
- const [x1, y1] = getConnPoint(src, dstCX, dstCY);
7783
- const [x2, y2] = getConnPoint(dst, srcCX, srcCY);
7867
+ const [x1, y1] = getConnPoint(src, dstCX, dstCY, e.fromAnchor);
7868
+ const [x2, y2] = getConnPoint(dst, srcCX, srcCY, e.toAnchor);
7784
7869
  const eg = mkGroup(`edge-${e.from}-${e.to}`, "eg");
7785
7870
  if (e.style?.opacity != null)
7786
7871
  eg.setAttribute("opacity", String(e.style.opacity));
@@ -7857,6 +7942,8 @@ function renderToSVG(sg, container, options = {}) {
7857
7942
  ng.dataset.h = String(n.h);
7858
7943
  if (n.pathData)
7859
7944
  ng.dataset.pathData = n.pathData;
7945
+ if (n.meta?.animationParent)
7946
+ ng.dataset.animationParent = n.meta.animationParent;
7860
7947
  if (n.style?.opacity != null)
7861
7948
  ng.setAttribute("opacity", String(n.style.opacity));
7862
7949
  // ── Static transform (deg, dx, dy, factor) ──────────
@@ -8510,8 +8597,8 @@ function renderToCanvas(sg, canvas, options = {}) {
8510
8597
  continue;
8511
8598
  const dstCX = dst.x + dst.w / 2, dstCY = dst.y + dst.h / 2;
8512
8599
  const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
8513
- const [x1, y1] = getConnPoint(src, dstCX, dstCY);
8514
- const [x2, y2] = getConnPoint(dst, srcCX, srcCY);
8600
+ const [x1, y1] = getConnPoint(src, dstCX, dstCY, e.fromAnchor);
8601
+ const [x2, y2] = getConnPoint(dst, srcCX, srcCY, e.toAnchor);
8515
8602
  if (e.style?.opacity != null)
8516
8603
  ctx.globalAlpha = Number(e.style.opacity);
8517
8604
  const ecol = String(e.style?.stroke ?? palette.edgeStroke);
@@ -9368,6 +9455,13 @@ class AnimationController {
9368
9455
  this.drawTargetNodes.delete(`node-${s.target}`);
9369
9456
  }
9370
9457
  }
9458
+ this._relatedElementIdsByPrimaryId = this._buildRelatedElementIndex();
9459
+ for (const nodeId of Array.from(this.drawTargetNodes)) {
9460
+ const relatedIds = this._relatedElementIdsByPrimaryId.get(nodeId);
9461
+ if (!relatedIds)
9462
+ continue;
9463
+ relatedIds.forEach((id) => this.drawTargetNodes.add(id));
9464
+ }
9371
9465
  this._drawStepIndexByElementId = this._buildDrawStepIndex();
9372
9466
  const { parentGroupByElementId, groupDescendantIds } = this._buildGroupVisibilityIndex();
9373
9467
  this._parentGroupByElementId = parentGroupByElementId;
@@ -9398,10 +9492,30 @@ class AnimationController {
9398
9492
  const el = resolveNonEdgeDrawEl(this.svg, step.target);
9399
9493
  if (el && !drawStepIndexByElementId.has(el.id)) {
9400
9494
  drawStepIndexByElementId.set(el.id, stepIndex);
9495
+ this._relatedElementIdsByPrimaryId.get(el.id)?.forEach((relatedId) => {
9496
+ if (!drawStepIndexByElementId.has(relatedId)) {
9497
+ drawStepIndexByElementId.set(relatedId, stepIndex);
9498
+ }
9499
+ });
9401
9500
  }
9402
9501
  });
9403
9502
  return drawStepIndexByElementId;
9404
9503
  }
9504
+ _buildRelatedElementIndex() {
9505
+ const relatedElementIdsByPrimaryId = new Map();
9506
+ this.svg.querySelectorAll(POSITIONABLE_SELECTOR).forEach((el) => {
9507
+ const animationParent = el.dataset.animationParent;
9508
+ if (!animationParent)
9509
+ return;
9510
+ const primaryEl = resolveNonEdgeDrawEl(this.svg, animationParent);
9511
+ if (!primaryEl || primaryEl.id === el.id)
9512
+ return;
9513
+ const related = relatedElementIdsByPrimaryId.get(primaryEl.id) ?? new Set();
9514
+ related.add(el.id);
9515
+ relatedElementIdsByPrimaryId.set(primaryEl.id, related);
9516
+ });
9517
+ return relatedElementIdsByPrimaryId;
9518
+ }
9405
9519
  _buildGroupVisibilityIndex() {
9406
9520
  const parentGroupByElementId = new Map();
9407
9521
  const directChildIdsByGroup = new Map();
@@ -9480,10 +9594,18 @@ class AnimationController {
9480
9594
  const el = resolveEl(this.svg, target);
9481
9595
  if (!el)
9482
9596
  return [];
9483
- if (!el.id.startsWith("group-"))
9484
- return [el];
9597
+ if (!el.id.startsWith("group-")) {
9598
+ const ids = new Set([el.id]);
9599
+ this._relatedElementIdsByPrimaryId.get(el.id)?.forEach((id) => ids.add(id));
9600
+ return Array.from(ids)
9601
+ .map((id) => getEl(this.svg, id))
9602
+ .filter((candidate) => candidate != null);
9603
+ }
9485
9604
  const ids = new Set([el.id]);
9486
9605
  this._groupDescendantIds.get(el.id)?.forEach((id) => ids.add(id));
9606
+ Array.from(ids).forEach((id) => {
9607
+ this._relatedElementIdsByPrimaryId.get(id)?.forEach((relatedId) => ids.add(relatedId));
9608
+ });
9487
9609
  return Array.from(ids)
9488
9610
  .map((id) => getEl(this.svg, id))
9489
9611
  .filter((candidate) => candidate != null);
@@ -9892,9 +10014,11 @@ class AnimationController {
9892
10014
  // ── highlight ────────────────────────────────────────────
9893
10015
  _doHighlight(target) {
9894
10016
  this.svg
9895
- .querySelectorAll(".ng.hl, .tg.hl, .ntg.hl, .cg.hl, .eg.hl")
10017
+ .querySelectorAll(".ng.hl, .gg.hl, .tg.hl, .ntg.hl, .cg.hl, .mdg.hl, .eg.hl")
9896
10018
  .forEach((e) => e.classList.remove("hl"));
9897
- resolveEl(this.svg, target)?.classList.add("hl");
10019
+ for (const el of this._resolveCascadeTargets(target)) {
10020
+ el.classList.add("hl");
10021
+ }
9898
10022
  }
9899
10023
  // ── fade / unfade ─────────────────────────────────────────
9900
10024
  _doFade(target, doFade) {
@@ -9930,8 +10054,8 @@ class AnimationController {
9930
10054
  }
9931
10055
  // ── move ──────────────────────────────────────────────────
9932
10056
  _doMove(target, step, silent) {
9933
- const el = resolveEl(this.svg, target);
9934
- if (!el)
10057
+ const targets = this._resolveCascadeTargets(target);
10058
+ if (!targets.length)
9935
10059
  return;
9936
10060
  const cur = this._transforms.get(target) ?? {
9937
10061
  tx: 0,
@@ -9944,12 +10068,14 @@ class AnimationController {
9944
10068
  tx: cur.tx + (step.dx ?? 0),
9945
10069
  ty: cur.ty + (step.dy ?? 0),
9946
10070
  });
9947
- this._writeTransform(el, target, silent, step.duration ?? 420);
10071
+ for (const el of targets) {
10072
+ this._writeTransform(el, target, silent, step.duration ?? 420);
10073
+ }
9948
10074
  }
9949
10075
  // ── scale ─────────────────────────────────────────────────
9950
10076
  _doScale(target, step, silent) {
9951
- const el = resolveEl(this.svg, target);
9952
- if (!el)
10077
+ const targets = this._resolveCascadeTargets(target);
10078
+ if (!targets.length)
9953
10079
  return;
9954
10080
  const cur = this._transforms.get(target) ?? {
9955
10081
  tx: 0,
@@ -9958,12 +10084,14 @@ class AnimationController {
9958
10084
  rotate: 0,
9959
10085
  };
9960
10086
  this._transforms.set(target, { ...cur, scale: step.factor ?? 1 });
9961
- this._writeTransform(el, target, silent, step.duration ?? 350);
10087
+ for (const el of targets) {
10088
+ this._writeTransform(el, target, silent, step.duration ?? 350);
10089
+ }
9962
10090
  }
9963
10091
  // ── rotate ────────────────────────────────────────────────
9964
10092
  _doRotate(target, step, silent) {
9965
- const el = resolveEl(this.svg, target);
9966
- if (!el)
10093
+ const targets = this._resolveCascadeTargets(target);
10094
+ if (!targets.length)
9967
10095
  return;
9968
10096
  const cur = this._transforms.get(target) ?? {
9969
10097
  tx: 0,
@@ -9975,7 +10103,9 @@ class AnimationController {
9975
10103
  ...cur,
9976
10104
  rotate: cur.rotate + (step.deg ?? 0),
9977
10105
  });
9978
- this._writeTransform(el, target, silent, step.duration ?? 400);
10106
+ for (const el of targets) {
10107
+ this._writeTransform(el, target, silent, step.duration ?? 400);
10108
+ }
9979
10109
  }
9980
10110
  _doDraw(step, silent) {
9981
10111
  const { target } = step;
@@ -10119,18 +10249,20 @@ class AnimationController {
10119
10249
  return;
10120
10250
  }
10121
10251
  // ── Node draw ──────────────────────────────────────
10122
- const nodeEl = getNodeEl(this.svg, target);
10123
- if (!nodeEl)
10252
+ const nodeEls = this._resolveCascadeTargets(target).filter((el) => el.classList.contains("ng"));
10253
+ if (!nodeEls.length)
10124
10254
  return;
10125
- showDrawEl(nodeEl);
10126
- if (silent) {
10127
- revealNodeInstant(nodeEl);
10128
- }
10129
- else {
10130
- if (!nodeGuidePathEl(nodeEl) && !nodeEl.querySelector("path")?.style.strokeDasharray) {
10131
- prepareNodeForDraw(nodeEl);
10255
+ for (const nodeEl of nodeEls) {
10256
+ showDrawEl(nodeEl);
10257
+ if (silent) {
10258
+ revealNodeInstant(nodeEl);
10259
+ }
10260
+ else {
10261
+ if (!nodeGuidePathEl(nodeEl) && !nodeEl.querySelector("path")?.style.strokeDasharray) {
10262
+ prepareNodeForDraw(nodeEl);
10263
+ }
10264
+ animateNodeDraw(nodeEl, step.duration ?? ANIMATION.nodeStrokeDur, step.duration ?? ANIMATION.textRevealMs);
10132
10265
  }
10133
- animateNodeDraw(nodeEl, step.duration ?? ANIMATION.nodeStrokeDur, step.duration ?? ANIMATION.textRevealMs);
10134
10266
  }
10135
10267
  }
10136
10268
  // ── erase ─────────────────────────────────────────────────
@@ -10151,45 +10283,44 @@ class AnimationController {
10151
10283
  }
10152
10284
  // ── pulse ─────────────────────────────────────────────────
10153
10285
  _doPulse(target, duration = 500) {
10154
- resolveEl(this.svg, target)?.animate([
10155
- { filter: "brightness(1)" },
10156
- { filter: "brightness(1.6)" },
10157
- { filter: "brightness(1)" },
10158
- ], { duration, iterations: 3 });
10286
+ for (const el of this._resolveCascadeTargets(target)) {
10287
+ el.animate([
10288
+ { filter: "brightness(1)" },
10289
+ { filter: "brightness(1.6)" },
10290
+ { filter: "brightness(1)" },
10291
+ ], { duration, iterations: 3 });
10292
+ }
10159
10293
  }
10160
10294
  // ── color ─────────────────────────────────────────────────
10161
10295
  _doColor(target, color) {
10162
10296
  if (!color)
10163
10297
  return;
10164
- const el = resolveEl(this.svg, target);
10165
- if (!el)
10166
- return;
10167
- // edge color stroke
10168
- if (parseEdgeTarget(target)) {
10169
- el.querySelectorAll("path, line, polyline").forEach((p) => {
10170
- p.style.stroke = color;
10171
- });
10172
- el.querySelectorAll("polygon").forEach((p) => {
10173
- p.style.fill = color;
10174
- p.style.stroke = color;
10175
- });
10176
- return;
10177
- }
10178
- // everything else — color fill
10179
- let hit = false;
10180
- el.querySelectorAll("path, rect, ellipse, polygon").forEach((c) => {
10181
- const attrFill = c.getAttribute("fill");
10182
- if (attrFill === "none")
10183
- return;
10184
- if (attrFill === null && c.tagName === "path")
10185
- return;
10186
- c.style.fill = color;
10187
- hit = true;
10188
- });
10189
- if (!hit) {
10190
- el.querySelectorAll("text").forEach((t) => {
10191
- t.style.fill = color;
10298
+ for (const el of this._resolveCascadeTargets(target)) {
10299
+ if (parseEdgeTarget(target)) {
10300
+ el.querySelectorAll("path, line, polyline").forEach((p) => {
10301
+ p.style.stroke = color;
10302
+ });
10303
+ el.querySelectorAll("polygon").forEach((p) => {
10304
+ p.style.fill = color;
10305
+ p.style.stroke = color;
10306
+ });
10307
+ continue;
10308
+ }
10309
+ let hit = false;
10310
+ el.querySelectorAll("path, rect, ellipse, polygon").forEach((c) => {
10311
+ const attrFill = c.getAttribute("fill");
10312
+ if (attrFill === "none")
10313
+ return;
10314
+ if (attrFill === null && c.tagName === "path")
10315
+ return;
10316
+ c.style.fill = color;
10317
+ hit = true;
10192
10318
  });
10319
+ if (!hit) {
10320
+ el.querySelectorAll("text").forEach((t) => {
10321
+ t.style.fill = color;
10322
+ });
10323
+ }
10193
10324
  }
10194
10325
  }
10195
10326
  // ── narration ───────────────────────────────────────────
@@ -10783,7 +10914,7 @@ class EventEmitter {
10783
10914
  }
10784
10915
 
10785
10916
  function render(options) {
10786
- const { container: rawContainer, dsl, renderer = "svg", injectCSS = true, tts, svgOptions = {}, canvasOptions = {}, onNodeClick, onReady, } = options;
10917
+ const { container: rawContainer, dsl, plugins, renderer = "svg", injectCSS = true, tts, svgOptions = {}, canvasOptions = {}, onNodeClick, onReady, } = options;
10787
10918
  if (injectCSS && !document.getElementById("ai-diagram-css")) {
10788
10919
  const style = document.createElement("style");
10789
10920
  style.id = "ai-diagram-css";
@@ -10799,7 +10930,7 @@ function render(options) {
10799
10930
  else {
10800
10931
  el = rawContainer;
10801
10932
  }
10802
- const ast = parse(dsl);
10933
+ const ast = parse(dsl, { plugins });
10803
10934
  const scene = buildSceneGraph(ast);
10804
10935
  layout(scene);
10805
10936
  let svg;
@@ -11143,6 +11274,7 @@ class SketchmarkCanvas {
11143
11274
  const instance = render({
11144
11275
  container: this.diagramWrap,
11145
11276
  dsl: this.dsl,
11277
+ plugins: this.options.plugins,
11146
11278
  renderer: this.renderer,
11147
11279
  svgOptions: { interactive: true, showTitle: true, theme: this.options.svgOptions?.theme ?? this.theme, ...this.options.svgOptions },
11148
11280
  canvasOptions: this.options.canvasOptions,
@@ -12220,6 +12352,7 @@ class SketchmarkEmbed {
12220
12352
  const instance = render({
12221
12353
  container: this.diagramWrap,
12222
12354
  dsl: this.dsl,
12355
+ plugins: this.options.plugins,
12223
12356
  renderer: "svg",
12224
12357
  svgOptions: {
12225
12358
  showTitle: true,