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.
Potentially problematic release.
This version of sketchmark might be problematic. Click here for more details.
- package/README.md +148 -77
- package/dist/animation/index.d.ts +2 -0
- package/dist/animation/index.d.ts.map +1 -1
- package/dist/ast/types.d.ts +14 -1
- package/dist/ast/types.d.ts.map +1 -1
- package/dist/index.cjs +279 -95
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +279 -95
- package/dist/index.js.map +1 -1
- package/dist/layout/entity-rect.d.ts +2 -0
- package/dist/layout/entity-rect.d.ts.map +1 -1
- package/dist/layout/index.d.ts +8 -0
- package/dist/layout/index.d.ts.map +1 -1
- package/dist/parser/index.d.ts +2 -1
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/tokenizer.d.ts.map +1 -1
- package/dist/plugins.d.ts +12 -0
- package/dist/plugins.d.ts.map +1 -0
- package/dist/render.d.ts +2 -0
- package/dist/render.d.ts.map +1 -1
- package/dist/renderer/shared.d.ts +1 -1
- package/dist/renderer/shared.d.ts.map +1 -1
- package/dist/renderer/svg/index.d.ts.map +1 -1
- package/dist/scene/index.d.ts +13 -1
- package/dist/scene/index.d.ts.map +1 -1
- package/dist/sketchmark.iife.js +279 -95
- package/dist/ui/canvas.d.ts +2 -0
- package/dist/ui/canvas.d.ts.map +1 -1
- package/dist/ui/embed.d.ts +2 -0
- package/dist/ui/embed.d.ts.map +1 -1
- package/package.json +18 -1
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 +=
|
|
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
|
|
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,
|
|
@@ -504,11 +553,14 @@ function parse(src) {
|
|
|
504
553
|
label: props.label || "",
|
|
505
554
|
...(props.width ? { width: parseFloat(props.width) } : {}),
|
|
506
555
|
...(props.height ? { height: parseFloat(props.height) } : {}),
|
|
556
|
+
...(props.x ? { x: parseFloat(props.x) } : {}),
|
|
557
|
+
...(props.y ? { y: parseFloat(props.y) } : {}),
|
|
507
558
|
...(props.deg ? { deg: parseFloat(props.deg) } : {}),
|
|
508
559
|
...(props.dx ? { dx: parseFloat(props.dx) } : {}),
|
|
509
560
|
...(props.dy ? { dy: parseFloat(props.dy) } : {}),
|
|
510
561
|
...(props.factor ? { factor: parseFloat(props.factor) } : {}),
|
|
511
562
|
...(props.theme ? { theme: props.theme } : {}),
|
|
563
|
+
...(meta ? { meta } : {}),
|
|
512
564
|
style: propsToStyle(props),
|
|
513
565
|
};
|
|
514
566
|
if (props.url)
|
|
@@ -533,17 +585,32 @@ function parse(src) {
|
|
|
533
585
|
j = 2;
|
|
534
586
|
}
|
|
535
587
|
Object.assign(props, parseSimpleProps(toks, j));
|
|
588
|
+
const meta = extractNodeMeta(props);
|
|
536
589
|
return {
|
|
537
590
|
kind: "node",
|
|
538
591
|
id,
|
|
539
592
|
shape: "note",
|
|
540
593
|
label: (props.label ?? "").replace(/\\n/g, "\n"),
|
|
541
594
|
theme: props.theme,
|
|
595
|
+
...(meta ? { meta } : {}),
|
|
542
596
|
style: propsToStyle(props),
|
|
543
597
|
...(props.width ? { width: parseFloat(props.width) } : {}),
|
|
544
598
|
...(props.height ? { height: parseFloat(props.height) } : {}),
|
|
599
|
+
...(props.x ? { x: parseFloat(props.x) } : {}),
|
|
600
|
+
...(props.y ? { y: parseFloat(props.y) } : {}),
|
|
601
|
+
...(props.deg ? { deg: parseFloat(props.deg) } : {}),
|
|
602
|
+
...(props.dx ? { dx: parseFloat(props.dx) } : {}),
|
|
603
|
+
...(props.dy ? { dy: parseFloat(props.dy) } : {}),
|
|
604
|
+
...(props.factor ? { factor: parseFloat(props.factor) } : {}),
|
|
545
605
|
};
|
|
546
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
|
+
}
|
|
547
614
|
function parseGroup() {
|
|
548
615
|
const keywordTok = cur();
|
|
549
616
|
skip();
|
|
@@ -581,6 +648,8 @@ function parse(src) {
|
|
|
581
648
|
justify: props.justify,
|
|
582
649
|
theme: props.theme,
|
|
583
650
|
style: propsToStyle(props),
|
|
651
|
+
x: props.x !== undefined ? parseFloat(props.x) : undefined,
|
|
652
|
+
y: props.y !== undefined ? parseFloat(props.y) : undefined,
|
|
584
653
|
width: props.width !== undefined ? parseFloat(props.width) : undefined,
|
|
585
654
|
height: props.height !== undefined ? parseFloat(props.height) : undefined,
|
|
586
655
|
};
|
|
@@ -614,6 +683,8 @@ function parse(src) {
|
|
|
614
683
|
to: toTok.value,
|
|
615
684
|
connector: connector,
|
|
616
685
|
label: props.label,
|
|
686
|
+
fromAnchor: props["anchor-from"],
|
|
687
|
+
toAnchor: props["anchor-to"],
|
|
617
688
|
dashed,
|
|
618
689
|
bidirectional,
|
|
619
690
|
style: propsToStyle(props),
|
|
@@ -752,6 +823,8 @@ function parse(src) {
|
|
|
752
823
|
chartType: chartType.replace("-chart", ""),
|
|
753
824
|
label: props.label ?? props.title,
|
|
754
825
|
data: { headers, rows },
|
|
826
|
+
x: props.x ? parseFloat(props.x) : undefined,
|
|
827
|
+
y: props.y ? parseFloat(props.y) : undefined,
|
|
755
828
|
width: props.width ? parseFloat(props.width) : undefined,
|
|
756
829
|
height: props.height ? parseFloat(props.height) : undefined,
|
|
757
830
|
theme: props.theme,
|
|
@@ -777,6 +850,8 @@ function parse(src) {
|
|
|
777
850
|
id,
|
|
778
851
|
label: props.label ?? "",
|
|
779
852
|
rows: [],
|
|
853
|
+
x: props.x ? parseFloat(props.x) : undefined,
|
|
854
|
+
y: props.y ? parseFloat(props.y) : undefined,
|
|
780
855
|
theme: props.theme,
|
|
781
856
|
style: propsToStyle(props),
|
|
782
857
|
};
|
|
@@ -832,6 +907,8 @@ function parse(src) {
|
|
|
832
907
|
kind: "markdown",
|
|
833
908
|
id,
|
|
834
909
|
content: content.trim(),
|
|
910
|
+
x: props.x ? parseFloat(props.x) : undefined,
|
|
911
|
+
y: props.y ? parseFloat(props.y) : undefined,
|
|
835
912
|
width: props.width ? parseFloat(props.width) : undefined,
|
|
836
913
|
height: props.height ? parseFloat(props.height) : undefined,
|
|
837
914
|
theme: props.theme,
|
|
@@ -921,6 +998,7 @@ function parse(src) {
|
|
|
921
998
|
registerAuthoredId(grp.id, "group", t);
|
|
922
999
|
if (isBare) {
|
|
923
1000
|
grp.label = "";
|
|
1001
|
+
grp.padding = grp.padding ?? 0;
|
|
924
1002
|
grp.style = {
|
|
925
1003
|
...grp.style,
|
|
926
1004
|
fill: grp.style?.fill ?? "none",
|
|
@@ -1091,7 +1169,7 @@ function parse(src) {
|
|
|
1091
1169
|
node.style = { ...ast.styles[node.id], ...node.style };
|
|
1092
1170
|
}
|
|
1093
1171
|
}
|
|
1094
|
-
return ast;
|
|
1172
|
+
return applyPluginAstTransforms(ast, options.plugins);
|
|
1095
1173
|
}
|
|
1096
1174
|
|
|
1097
1175
|
// ============================================================
|
|
@@ -3440,6 +3518,8 @@ function buildSceneGraph(ast) {
|
|
|
3440
3518
|
groupId: nodeParentById.get(n.id),
|
|
3441
3519
|
width: n.width,
|
|
3442
3520
|
height: n.height,
|
|
3521
|
+
authoredX: n.x,
|
|
3522
|
+
authoredY: n.y,
|
|
3443
3523
|
deg: n.deg,
|
|
3444
3524
|
dx: n.dx,
|
|
3445
3525
|
dy: n.dy,
|
|
@@ -3468,6 +3548,8 @@ function buildSceneGraph(ast) {
|
|
|
3468
3548
|
align: (g.align ?? "start"),
|
|
3469
3549
|
justify: (g.justify ?? "start"),
|
|
3470
3550
|
style: { ...ast.styles[g.id], ...themeStyle, ...g.style },
|
|
3551
|
+
authoredX: g.x,
|
|
3552
|
+
authoredY: g.y,
|
|
3471
3553
|
width: g.width,
|
|
3472
3554
|
height: g.height,
|
|
3473
3555
|
x: 0,
|
|
@@ -3487,6 +3569,8 @@ function buildSceneGraph(ast) {
|
|
|
3487
3569
|
headerH: TABLE.headerH,
|
|
3488
3570
|
labelH: TABLE.labelH,
|
|
3489
3571
|
style: { ...ast.styles[t.id], ...themeStyle, ...t.style },
|
|
3572
|
+
authoredX: t.x,
|
|
3573
|
+
authoredY: t.y,
|
|
3490
3574
|
x: 0,
|
|
3491
3575
|
y: 0,
|
|
3492
3576
|
w: 0,
|
|
@@ -3501,6 +3585,8 @@ function buildSceneGraph(ast) {
|
|
|
3501
3585
|
label: c.label,
|
|
3502
3586
|
data: c.data,
|
|
3503
3587
|
style: { ...ast.styles[c.id], ...themeStyle, ...c.style },
|
|
3588
|
+
authoredX: c.x,
|
|
3589
|
+
authoredY: c.y,
|
|
3504
3590
|
x: 0,
|
|
3505
3591
|
y: 0,
|
|
3506
3592
|
w: c.width ?? CHART.defaultW,
|
|
@@ -3516,6 +3602,8 @@ function buildSceneGraph(ast) {
|
|
|
3516
3602
|
style: { ...ast.styles[m.id], ...themeStyle, ...m.style },
|
|
3517
3603
|
width: m.width,
|
|
3518
3604
|
height: m.height,
|
|
3605
|
+
authoredX: m.x,
|
|
3606
|
+
authoredY: m.y,
|
|
3519
3607
|
x: 0,
|
|
3520
3608
|
y: 0,
|
|
3521
3609
|
w: 0,
|
|
@@ -3528,6 +3616,8 @@ function buildSceneGraph(ast) {
|
|
|
3528
3616
|
to: e.to,
|
|
3529
3617
|
connector: e.connector,
|
|
3530
3618
|
label: e.label,
|
|
3619
|
+
fromAnchor: e.fromAnchor,
|
|
3620
|
+
toAnchor: e.toAnchor,
|
|
3531
3621
|
dashed: e.dashed ?? false,
|
|
3532
3622
|
bidirectional: e.bidirectional ?? false,
|
|
3533
3623
|
style: e.style ?? {},
|
|
@@ -4105,28 +4195,13 @@ function connMeta(connector) {
|
|
|
4105
4195
|
return { arrowAt: "start", dashed };
|
|
4106
4196
|
return { arrowAt: "end", dashed };
|
|
4107
4197
|
}
|
|
4108
|
-
// ── Generic rect connection point ────────────────────────────────────────
|
|
4109
|
-
function rectConnPoint$1(rx, ry, rw, rh, ox, oy) {
|
|
4110
|
-
const cx = rx + rw / 2, cy = ry + rh / 2;
|
|
4111
|
-
const dx = ox - cx, dy = oy - cy;
|
|
4112
|
-
if (Math.abs(dx) < 0.01 && Math.abs(dy) < 0.01)
|
|
4113
|
-
return [cx, cy];
|
|
4114
|
-
const hw = rw / 2 - 2, hh = rh / 2 - 2;
|
|
4115
|
-
const tx = Math.abs(dx) > 0.01 ? hw / Math.abs(dx) : 1e9;
|
|
4116
|
-
const ty = Math.abs(dy) > 0.01 ? hh / Math.abs(dy) : 1e9;
|
|
4117
|
-
const t = Math.min(tx, ty);
|
|
4118
|
-
return [cx + t * dx, cy + t * dy];
|
|
4119
|
-
}
|
|
4120
4198
|
// ── Resolve an endpoint entity by ID across all maps ─────────────────────
|
|
4121
4199
|
function resolveEndpoint(id, nm, tm, gm, cm) {
|
|
4122
4200
|
return nm.get(id) ?? tm.get(id) ?? gm.get(id) ?? cm.get(id) ?? null;
|
|
4123
4201
|
}
|
|
4124
4202
|
// ── Get connection point for any entity ──────────────────────────────────
|
|
4125
|
-
function getConnPoint(src, dstCX, dstCY) {
|
|
4126
|
-
|
|
4127
|
-
return connPoint(src, { x: dstCX - 1, y: dstCY - 1, w: 2, h: 2});
|
|
4128
|
-
}
|
|
4129
|
-
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);
|
|
4130
4205
|
}
|
|
4131
4206
|
// ── Group depth (for paint order) ────────────────────────────────────────
|
|
4132
4207
|
function groupDepth(g, gm) {
|
|
@@ -4363,6 +4438,12 @@ function iW(r, em) {
|
|
|
4363
4438
|
function iH(r, em) {
|
|
4364
4439
|
return em.get(r.id).h;
|
|
4365
4440
|
}
|
|
4441
|
+
function iAuthX(r, em) {
|
|
4442
|
+
return em.get(r.id).authoredX ?? 0;
|
|
4443
|
+
}
|
|
4444
|
+
function iAuthY(r, em) {
|
|
4445
|
+
return em.get(r.id).authoredY ?? 0;
|
|
4446
|
+
}
|
|
4366
4447
|
function setPos(r, x, y, em) {
|
|
4367
4448
|
const e = em.get(r.id);
|
|
4368
4449
|
e.x = Math.round(x);
|
|
@@ -4409,6 +4490,12 @@ function measure(g, gm, tm, cm, mdm, em) {
|
|
|
4409
4490
|
g.w = cols * cellW + (cols - 1) * gap + pad * 2;
|
|
4410
4491
|
g.h = rows * cellH + (rows - 1) * gap + pad * 2 + labelH;
|
|
4411
4492
|
}
|
|
4493
|
+
else if (layout === "absolute") {
|
|
4494
|
+
const maxRight = Math.max(0, ...kids.map((r) => iAuthX(r, em) + iW(r, em)));
|
|
4495
|
+
const maxBottom = Math.max(0, ...kids.map((r) => iAuthY(r, em) + iH(r, em)));
|
|
4496
|
+
g.w = maxRight + pad * 2;
|
|
4497
|
+
g.h = maxBottom + pad * 2 + labelH;
|
|
4498
|
+
}
|
|
4412
4499
|
else {
|
|
4413
4500
|
// column (default)
|
|
4414
4501
|
g.w = Math.max(...ws) + pad * 2;
|
|
@@ -4499,6 +4586,11 @@ function place(g, gm, em) {
|
|
|
4499
4586
|
setPos(ref, contentX + (i % cols) * (cellW + gap), contentY + Math.floor(i / cols) * (cellH + gap), em);
|
|
4500
4587
|
});
|
|
4501
4588
|
}
|
|
4589
|
+
else if (layout === "absolute") {
|
|
4590
|
+
kids.forEach((ref) => {
|
|
4591
|
+
setPos(ref, contentX + iAuthX(ref, em), contentY + iAuthY(ref, em), em);
|
|
4592
|
+
});
|
|
4593
|
+
}
|
|
4502
4594
|
else {
|
|
4503
4595
|
// column (default)
|
|
4504
4596
|
const ws = kids.map((r) => iW(r, em));
|
|
@@ -4544,6 +4636,50 @@ function connPoint(n, other) {
|
|
|
4544
4636
|
const t = Math.min(tx, ty);
|
|
4545
4637
|
return [cx + t * dx, cy + t * dy];
|
|
4546
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
|
+
}
|
|
4547
4683
|
function rectConnPoint(rx, ry, rw, rh, ox, oy) {
|
|
4548
4684
|
const cx = rx + rw / 2, cy = ry + rh / 2;
|
|
4549
4685
|
const dx = ox - cx, dy = oy - cy;
|
|
@@ -4575,17 +4711,6 @@ function routeEdges(sg) {
|
|
|
4575
4711
|
return c;
|
|
4576
4712
|
return null;
|
|
4577
4713
|
}
|
|
4578
|
-
function connPt(src, dstCX, dstCY) {
|
|
4579
|
-
// SceneNode has a .shape field; use the existing connPoint for it
|
|
4580
|
-
if ("shape" in src && src.shape) {
|
|
4581
|
-
return connPoint(src, {
|
|
4582
|
-
x: dstCX - 1,
|
|
4583
|
-
y: dstCY - 1,
|
|
4584
|
-
w: 2,
|
|
4585
|
-
h: 2});
|
|
4586
|
-
}
|
|
4587
|
-
return rectConnPoint(src.x, src.y, src.w, src.h, dstCX, dstCY);
|
|
4588
|
-
}
|
|
4589
4714
|
for (const e of sg.edges) {
|
|
4590
4715
|
const src = resolve(e.from);
|
|
4591
4716
|
const dst = resolve(e.to);
|
|
@@ -4595,7 +4720,10 @@ function routeEdges(sg) {
|
|
|
4595
4720
|
}
|
|
4596
4721
|
const dstCX = dst.x + dst.w / 2, dstCY = dst.y + dst.h / 2;
|
|
4597
4722
|
const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
|
|
4598
|
-
e.points = [
|
|
4723
|
+
e.points = [
|
|
4724
|
+
anchoredConnPoint(src, e.fromAnchor, dstCX, dstCY),
|
|
4725
|
+
anchoredConnPoint(dst, e.toAnchor, srcCX, srcCY),
|
|
4726
|
+
];
|
|
4599
4727
|
}
|
|
4600
4728
|
}
|
|
4601
4729
|
function computeBounds(sg, margin) {
|
|
@@ -4681,6 +4809,7 @@ function layout(sg) {
|
|
|
4681
4809
|
const rootCols = Number(sg.config["columns"] ?? 1);
|
|
4682
4810
|
const useGrid = rootLayout === "grid" && rootCols > 0;
|
|
4683
4811
|
const useColumn = rootLayout === "column";
|
|
4812
|
+
const useAbsolute = rootLayout === "absolute";
|
|
4684
4813
|
if (useGrid) {
|
|
4685
4814
|
// ── Grid: per-row heights, per-column widths (no wasted space) ──
|
|
4686
4815
|
const cols = rootCols;
|
|
@@ -4712,6 +4841,13 @@ function layout(sg) {
|
|
|
4712
4841
|
e.y = rowY[Math.floor(idx / cols)];
|
|
4713
4842
|
});
|
|
4714
4843
|
}
|
|
4844
|
+
else if (useAbsolute) {
|
|
4845
|
+
for (const ref of rootOrder) {
|
|
4846
|
+
const e = em.get(ref.id);
|
|
4847
|
+
e.x = MARGIN + (e.authoredX ?? 0);
|
|
4848
|
+
e.y = MARGIN + (e.authoredY ?? 0);
|
|
4849
|
+
}
|
|
4850
|
+
}
|
|
4715
4851
|
else {
|
|
4716
4852
|
// ── Row or Column linear flow ──────────────────────────
|
|
4717
4853
|
let pos = MARGIN;
|
|
@@ -7728,8 +7864,8 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
7728
7864
|
continue;
|
|
7729
7865
|
const dstCX = dst.x + dst.w / 2, dstCY = dst.y + dst.h / 2;
|
|
7730
7866
|
const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
|
|
7731
|
-
const [x1, y1] = getConnPoint(src, dstCX, dstCY);
|
|
7732
|
-
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);
|
|
7733
7869
|
const eg = mkGroup(`edge-${e.from}-${e.to}`, "eg");
|
|
7734
7870
|
if (e.style?.opacity != null)
|
|
7735
7871
|
eg.setAttribute("opacity", String(e.style.opacity));
|
|
@@ -7806,6 +7942,8 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
7806
7942
|
ng.dataset.h = String(n.h);
|
|
7807
7943
|
if (n.pathData)
|
|
7808
7944
|
ng.dataset.pathData = n.pathData;
|
|
7945
|
+
if (n.meta?.animationParent)
|
|
7946
|
+
ng.dataset.animationParent = n.meta.animationParent;
|
|
7809
7947
|
if (n.style?.opacity != null)
|
|
7810
7948
|
ng.setAttribute("opacity", String(n.style.opacity));
|
|
7811
7949
|
// ── Static transform (deg, dx, dy, factor) ──────────
|
|
@@ -8459,8 +8597,8 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
8459
8597
|
continue;
|
|
8460
8598
|
const dstCX = dst.x + dst.w / 2, dstCY = dst.y + dst.h / 2;
|
|
8461
8599
|
const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
|
|
8462
|
-
const [x1, y1] = getConnPoint(src, dstCX, dstCY);
|
|
8463
|
-
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);
|
|
8464
8602
|
if (e.style?.opacity != null)
|
|
8465
8603
|
ctx.globalAlpha = Number(e.style.opacity);
|
|
8466
8604
|
const ecol = String(e.style?.stroke ?? palette.edgeStroke);
|
|
@@ -9317,6 +9455,13 @@ class AnimationController {
|
|
|
9317
9455
|
this.drawTargetNodes.delete(`node-${s.target}`);
|
|
9318
9456
|
}
|
|
9319
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
|
+
}
|
|
9320
9465
|
this._drawStepIndexByElementId = this._buildDrawStepIndex();
|
|
9321
9466
|
const { parentGroupByElementId, groupDescendantIds } = this._buildGroupVisibilityIndex();
|
|
9322
9467
|
this._parentGroupByElementId = parentGroupByElementId;
|
|
@@ -9347,10 +9492,30 @@ class AnimationController {
|
|
|
9347
9492
|
const el = resolveNonEdgeDrawEl(this.svg, step.target);
|
|
9348
9493
|
if (el && !drawStepIndexByElementId.has(el.id)) {
|
|
9349
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
|
+
});
|
|
9350
9500
|
}
|
|
9351
9501
|
});
|
|
9352
9502
|
return drawStepIndexByElementId;
|
|
9353
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
|
+
}
|
|
9354
9519
|
_buildGroupVisibilityIndex() {
|
|
9355
9520
|
const parentGroupByElementId = new Map();
|
|
9356
9521
|
const directChildIdsByGroup = new Map();
|
|
@@ -9429,10 +9594,18 @@ class AnimationController {
|
|
|
9429
9594
|
const el = resolveEl(this.svg, target);
|
|
9430
9595
|
if (!el)
|
|
9431
9596
|
return [];
|
|
9432
|
-
if (!el.id.startsWith("group-"))
|
|
9433
|
-
|
|
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
|
+
}
|
|
9434
9604
|
const ids = new Set([el.id]);
|
|
9435
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
|
+
});
|
|
9436
9609
|
return Array.from(ids)
|
|
9437
9610
|
.map((id) => getEl(this.svg, id))
|
|
9438
9611
|
.filter((candidate) => candidate != null);
|
|
@@ -9841,9 +10014,11 @@ class AnimationController {
|
|
|
9841
10014
|
// ── highlight ────────────────────────────────────────────
|
|
9842
10015
|
_doHighlight(target) {
|
|
9843
10016
|
this.svg
|
|
9844
|
-
.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")
|
|
9845
10018
|
.forEach((e) => e.classList.remove("hl"));
|
|
9846
|
-
|
|
10019
|
+
for (const el of this._resolveCascadeTargets(target)) {
|
|
10020
|
+
el.classList.add("hl");
|
|
10021
|
+
}
|
|
9847
10022
|
}
|
|
9848
10023
|
// ── fade / unfade ─────────────────────────────────────────
|
|
9849
10024
|
_doFade(target, doFade) {
|
|
@@ -9879,8 +10054,8 @@ class AnimationController {
|
|
|
9879
10054
|
}
|
|
9880
10055
|
// ── move ──────────────────────────────────────────────────
|
|
9881
10056
|
_doMove(target, step, silent) {
|
|
9882
|
-
const
|
|
9883
|
-
if (!
|
|
10057
|
+
const targets = this._resolveCascadeTargets(target);
|
|
10058
|
+
if (!targets.length)
|
|
9884
10059
|
return;
|
|
9885
10060
|
const cur = this._transforms.get(target) ?? {
|
|
9886
10061
|
tx: 0,
|
|
@@ -9893,12 +10068,14 @@ class AnimationController {
|
|
|
9893
10068
|
tx: cur.tx + (step.dx ?? 0),
|
|
9894
10069
|
ty: cur.ty + (step.dy ?? 0),
|
|
9895
10070
|
});
|
|
9896
|
-
|
|
10071
|
+
for (const el of targets) {
|
|
10072
|
+
this._writeTransform(el, target, silent, step.duration ?? 420);
|
|
10073
|
+
}
|
|
9897
10074
|
}
|
|
9898
10075
|
// ── scale ─────────────────────────────────────────────────
|
|
9899
10076
|
_doScale(target, step, silent) {
|
|
9900
|
-
const
|
|
9901
|
-
if (!
|
|
10077
|
+
const targets = this._resolveCascadeTargets(target);
|
|
10078
|
+
if (!targets.length)
|
|
9902
10079
|
return;
|
|
9903
10080
|
const cur = this._transforms.get(target) ?? {
|
|
9904
10081
|
tx: 0,
|
|
@@ -9907,12 +10084,14 @@ class AnimationController {
|
|
|
9907
10084
|
rotate: 0,
|
|
9908
10085
|
};
|
|
9909
10086
|
this._transforms.set(target, { ...cur, scale: step.factor ?? 1 });
|
|
9910
|
-
|
|
10087
|
+
for (const el of targets) {
|
|
10088
|
+
this._writeTransform(el, target, silent, step.duration ?? 350);
|
|
10089
|
+
}
|
|
9911
10090
|
}
|
|
9912
10091
|
// ── rotate ────────────────────────────────────────────────
|
|
9913
10092
|
_doRotate(target, step, silent) {
|
|
9914
|
-
const
|
|
9915
|
-
if (!
|
|
10093
|
+
const targets = this._resolveCascadeTargets(target);
|
|
10094
|
+
if (!targets.length)
|
|
9916
10095
|
return;
|
|
9917
10096
|
const cur = this._transforms.get(target) ?? {
|
|
9918
10097
|
tx: 0,
|
|
@@ -9924,7 +10103,9 @@ class AnimationController {
|
|
|
9924
10103
|
...cur,
|
|
9925
10104
|
rotate: cur.rotate + (step.deg ?? 0),
|
|
9926
10105
|
});
|
|
9927
|
-
|
|
10106
|
+
for (const el of targets) {
|
|
10107
|
+
this._writeTransform(el, target, silent, step.duration ?? 400);
|
|
10108
|
+
}
|
|
9928
10109
|
}
|
|
9929
10110
|
_doDraw(step, silent) {
|
|
9930
10111
|
const { target } = step;
|
|
@@ -10068,18 +10249,20 @@ class AnimationController {
|
|
|
10068
10249
|
return;
|
|
10069
10250
|
}
|
|
10070
10251
|
// ── Node draw ──────────────────────────────────────
|
|
10071
|
-
const
|
|
10072
|
-
if (!
|
|
10252
|
+
const nodeEls = this._resolveCascadeTargets(target).filter((el) => el.classList.contains("ng"));
|
|
10253
|
+
if (!nodeEls.length)
|
|
10073
10254
|
return;
|
|
10074
|
-
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
|
|
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);
|
|
10081
10265
|
}
|
|
10082
|
-
animateNodeDraw(nodeEl, step.duration ?? ANIMATION.nodeStrokeDur, step.duration ?? ANIMATION.textRevealMs);
|
|
10083
10266
|
}
|
|
10084
10267
|
}
|
|
10085
10268
|
// ── erase ─────────────────────────────────────────────────
|
|
@@ -10100,45 +10283,44 @@ class AnimationController {
|
|
|
10100
10283
|
}
|
|
10101
10284
|
// ── pulse ─────────────────────────────────────────────────
|
|
10102
10285
|
_doPulse(target, duration = 500) {
|
|
10103
|
-
|
|
10104
|
-
|
|
10105
|
-
|
|
10106
|
-
|
|
10107
|
-
|
|
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
|
+
}
|
|
10108
10293
|
}
|
|
10109
10294
|
// ── color ─────────────────────────────────────────────────
|
|
10110
10295
|
_doColor(target, color) {
|
|
10111
10296
|
if (!color)
|
|
10112
10297
|
return;
|
|
10113
|
-
const el
|
|
10114
|
-
|
|
10115
|
-
|
|
10116
|
-
|
|
10117
|
-
|
|
10118
|
-
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
10126
|
-
|
|
10127
|
-
|
|
10128
|
-
|
|
10129
|
-
|
|
10130
|
-
|
|
10131
|
-
|
|
10132
|
-
|
|
10133
|
-
if (attrFill === null && c.tagName === "path")
|
|
10134
|
-
return;
|
|
10135
|
-
c.style.fill = color;
|
|
10136
|
-
hit = true;
|
|
10137
|
-
});
|
|
10138
|
-
if (!hit) {
|
|
10139
|
-
el.querySelectorAll("text").forEach((t) => {
|
|
10140
|
-
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;
|
|
10141
10318
|
});
|
|
10319
|
+
if (!hit) {
|
|
10320
|
+
el.querySelectorAll("text").forEach((t) => {
|
|
10321
|
+
t.style.fill = color;
|
|
10322
|
+
});
|
|
10323
|
+
}
|
|
10142
10324
|
}
|
|
10143
10325
|
}
|
|
10144
10326
|
// ── narration ───────────────────────────────────────────
|
|
@@ -10732,7 +10914,7 @@ class EventEmitter {
|
|
|
10732
10914
|
}
|
|
10733
10915
|
|
|
10734
10916
|
function render(options) {
|
|
10735
|
-
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;
|
|
10736
10918
|
if (injectCSS && !document.getElementById("ai-diagram-css")) {
|
|
10737
10919
|
const style = document.createElement("style");
|
|
10738
10920
|
style.id = "ai-diagram-css";
|
|
@@ -10748,7 +10930,7 @@ function render(options) {
|
|
|
10748
10930
|
else {
|
|
10749
10931
|
el = rawContainer;
|
|
10750
10932
|
}
|
|
10751
|
-
const ast = parse(dsl);
|
|
10933
|
+
const ast = parse(dsl, { plugins });
|
|
10752
10934
|
const scene = buildSceneGraph(ast);
|
|
10753
10935
|
layout(scene);
|
|
10754
10936
|
let svg;
|
|
@@ -11092,6 +11274,7 @@ class SketchmarkCanvas {
|
|
|
11092
11274
|
const instance = render({
|
|
11093
11275
|
container: this.diagramWrap,
|
|
11094
11276
|
dsl: this.dsl,
|
|
11277
|
+
plugins: this.options.plugins,
|
|
11095
11278
|
renderer: this.renderer,
|
|
11096
11279
|
svgOptions: { interactive: true, showTitle: true, theme: this.options.svgOptions?.theme ?? this.theme, ...this.options.svgOptions },
|
|
11097
11280
|
canvasOptions: this.options.canvasOptions,
|
|
@@ -12169,6 +12352,7 @@ class SketchmarkEmbed {
|
|
|
12169
12352
|
const instance = render({
|
|
12170
12353
|
container: this.diagramWrap,
|
|
12171
12354
|
dsl: this.dsl,
|
|
12355
|
+
plugins: this.options.plugins,
|
|
12172
12356
|
renderer: "svg",
|
|
12173
12357
|
svgOptions: {
|
|
12174
12358
|
showTitle: true,
|