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.
- 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.js
CHANGED
|
@@ -144,10 +144,16 @@ function tokenize$1(src) {
|
|
|
144
144
|
val += "\n";
|
|
145
145
|
else if (esc === "t")
|
|
146
146
|
val += "\t";
|
|
147
|
+
else if (esc === "r")
|
|
148
|
+
val += "\r";
|
|
147
149
|
else if (esc === "\\")
|
|
148
150
|
val += "\\";
|
|
151
|
+
else if (esc === q)
|
|
152
|
+
val += q;
|
|
153
|
+
else if (esc)
|
|
154
|
+
val += `\\${esc}`;
|
|
149
155
|
else
|
|
150
|
-
val +=
|
|
156
|
+
val += "\\";
|
|
151
157
|
}
|
|
152
158
|
else
|
|
153
159
|
val += src[i];
|
|
@@ -224,6 +230,47 @@ function tokenize$1(src) {
|
|
|
224
230
|
return tokens;
|
|
225
231
|
}
|
|
226
232
|
|
|
233
|
+
function pluginMessage(plugin, stage, error) {
|
|
234
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
235
|
+
return `Plugin "${plugin.name}" ${stage} failed: ${detail}`;
|
|
236
|
+
}
|
|
237
|
+
function applyPluginPreprocessors(source, plugins = []) {
|
|
238
|
+
let nextSource = source;
|
|
239
|
+
for (const plugin of plugins) {
|
|
240
|
+
if (!plugin.preprocess)
|
|
241
|
+
continue;
|
|
242
|
+
try {
|
|
243
|
+
const transformed = plugin.preprocess(nextSource);
|
|
244
|
+
if (typeof transformed !== "string") {
|
|
245
|
+
throw new Error("preprocess must return a string");
|
|
246
|
+
}
|
|
247
|
+
nextSource = transformed;
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
throw new Error(pluginMessage(plugin, "preprocess", error));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return nextSource;
|
|
254
|
+
}
|
|
255
|
+
function applyPluginAstTransforms(ast, plugins = []) {
|
|
256
|
+
let nextAst = ast;
|
|
257
|
+
for (const plugin of plugins) {
|
|
258
|
+
if (!plugin.transformAst)
|
|
259
|
+
continue;
|
|
260
|
+
try {
|
|
261
|
+
const transformed = plugin.transformAst(nextAst);
|
|
262
|
+
if (!transformed || transformed.kind !== "diagram") {
|
|
263
|
+
throw new Error('transformAst must return a DiagramAST with kind="diagram"');
|
|
264
|
+
}
|
|
265
|
+
nextAst = transformed;
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
throw new Error(pluginMessage(plugin, "transformAst", error));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return nextAst;
|
|
272
|
+
}
|
|
273
|
+
|
|
227
274
|
// ============================================================
|
|
228
275
|
// sketchmark - Parser (Tokens -> DiagramAST)
|
|
229
276
|
// ============================================================
|
|
@@ -310,9 +357,10 @@ function isValueToken(t) {
|
|
|
310
357
|
function isPropKeyToken(t) {
|
|
311
358
|
return !!t && (t.type === "IDENT" || t.type === "KEYWORD");
|
|
312
359
|
}
|
|
313
|
-
function parse(src) {
|
|
360
|
+
function parse(src, options = {}) {
|
|
314
361
|
resetUid();
|
|
315
|
-
const
|
|
362
|
+
const preparedSource = applyPluginPreprocessors(src, options.plugins);
|
|
363
|
+
const tokens = tokenize$1(preparedSource).filter((t) => t.type !== "NEWLINE" || t.value === "\n");
|
|
316
364
|
const flat = [];
|
|
317
365
|
let lastNL = false;
|
|
318
366
|
for (const t of tokens) {
|
|
@@ -495,6 +543,7 @@ function parse(src) {
|
|
|
495
543
|
const toks = lineTokens();
|
|
496
544
|
const id = requireExplicitId(keywordTok, toks);
|
|
497
545
|
const props = parseSimpleProps(toks, 1);
|
|
546
|
+
const meta = extractNodeMeta(props);
|
|
498
547
|
const node = {
|
|
499
548
|
kind: "node",
|
|
500
549
|
id,
|
|
@@ -502,11 +551,14 @@ function parse(src) {
|
|
|
502
551
|
label: props.label || "",
|
|
503
552
|
...(props.width ? { width: parseFloat(props.width) } : {}),
|
|
504
553
|
...(props.height ? { height: parseFloat(props.height) } : {}),
|
|
554
|
+
...(props.x ? { x: parseFloat(props.x) } : {}),
|
|
555
|
+
...(props.y ? { y: parseFloat(props.y) } : {}),
|
|
505
556
|
...(props.deg ? { deg: parseFloat(props.deg) } : {}),
|
|
506
557
|
...(props.dx ? { dx: parseFloat(props.dx) } : {}),
|
|
507
558
|
...(props.dy ? { dy: parseFloat(props.dy) } : {}),
|
|
508
559
|
...(props.factor ? { factor: parseFloat(props.factor) } : {}),
|
|
509
560
|
...(props.theme ? { theme: props.theme } : {}),
|
|
561
|
+
...(meta ? { meta } : {}),
|
|
510
562
|
style: propsToStyle(props),
|
|
511
563
|
};
|
|
512
564
|
if (props.url)
|
|
@@ -531,17 +583,32 @@ function parse(src) {
|
|
|
531
583
|
j = 2;
|
|
532
584
|
}
|
|
533
585
|
Object.assign(props, parseSimpleProps(toks, j));
|
|
586
|
+
const meta = extractNodeMeta(props);
|
|
534
587
|
return {
|
|
535
588
|
kind: "node",
|
|
536
589
|
id,
|
|
537
590
|
shape: "note",
|
|
538
591
|
label: (props.label ?? "").replace(/\\n/g, "\n"),
|
|
539
592
|
theme: props.theme,
|
|
593
|
+
...(meta ? { meta } : {}),
|
|
540
594
|
style: propsToStyle(props),
|
|
541
595
|
...(props.width ? { width: parseFloat(props.width) } : {}),
|
|
542
596
|
...(props.height ? { height: parseFloat(props.height) } : {}),
|
|
597
|
+
...(props.x ? { x: parseFloat(props.x) } : {}),
|
|
598
|
+
...(props.y ? { y: parseFloat(props.y) } : {}),
|
|
599
|
+
...(props.deg ? { deg: parseFloat(props.deg) } : {}),
|
|
600
|
+
...(props.dx ? { dx: parseFloat(props.dx) } : {}),
|
|
601
|
+
...(props.dy ? { dy: parseFloat(props.dy) } : {}),
|
|
602
|
+
...(props.factor ? { factor: parseFloat(props.factor) } : {}),
|
|
543
603
|
};
|
|
544
604
|
}
|
|
605
|
+
function extractNodeMeta(props) {
|
|
606
|
+
const meta = {};
|
|
607
|
+
if (props["animation-parent"]) {
|
|
608
|
+
meta.animationParent = props["animation-parent"];
|
|
609
|
+
}
|
|
610
|
+
return Object.keys(meta).length ? meta : undefined;
|
|
611
|
+
}
|
|
545
612
|
function parseGroup() {
|
|
546
613
|
const keywordTok = cur();
|
|
547
614
|
skip();
|
|
@@ -579,6 +646,8 @@ function parse(src) {
|
|
|
579
646
|
justify: props.justify,
|
|
580
647
|
theme: props.theme,
|
|
581
648
|
style: propsToStyle(props),
|
|
649
|
+
x: props.x !== undefined ? parseFloat(props.x) : undefined,
|
|
650
|
+
y: props.y !== undefined ? parseFloat(props.y) : undefined,
|
|
582
651
|
width: props.width !== undefined ? parseFloat(props.width) : undefined,
|
|
583
652
|
height: props.height !== undefined ? parseFloat(props.height) : undefined,
|
|
584
653
|
};
|
|
@@ -612,6 +681,8 @@ function parse(src) {
|
|
|
612
681
|
to: toTok.value,
|
|
613
682
|
connector: connector,
|
|
614
683
|
label: props.label,
|
|
684
|
+
fromAnchor: props["anchor-from"],
|
|
685
|
+
toAnchor: props["anchor-to"],
|
|
615
686
|
dashed,
|
|
616
687
|
bidirectional,
|
|
617
688
|
style: propsToStyle(props),
|
|
@@ -750,6 +821,8 @@ function parse(src) {
|
|
|
750
821
|
chartType: chartType.replace("-chart", ""),
|
|
751
822
|
label: props.label ?? props.title,
|
|
752
823
|
data: { headers, rows },
|
|
824
|
+
x: props.x ? parseFloat(props.x) : undefined,
|
|
825
|
+
y: props.y ? parseFloat(props.y) : undefined,
|
|
753
826
|
width: props.width ? parseFloat(props.width) : undefined,
|
|
754
827
|
height: props.height ? parseFloat(props.height) : undefined,
|
|
755
828
|
theme: props.theme,
|
|
@@ -775,6 +848,8 @@ function parse(src) {
|
|
|
775
848
|
id,
|
|
776
849
|
label: props.label ?? "",
|
|
777
850
|
rows: [],
|
|
851
|
+
x: props.x ? parseFloat(props.x) : undefined,
|
|
852
|
+
y: props.y ? parseFloat(props.y) : undefined,
|
|
778
853
|
theme: props.theme,
|
|
779
854
|
style: propsToStyle(props),
|
|
780
855
|
};
|
|
@@ -830,6 +905,8 @@ function parse(src) {
|
|
|
830
905
|
kind: "markdown",
|
|
831
906
|
id,
|
|
832
907
|
content: content.trim(),
|
|
908
|
+
x: props.x ? parseFloat(props.x) : undefined,
|
|
909
|
+
y: props.y ? parseFloat(props.y) : undefined,
|
|
833
910
|
width: props.width ? parseFloat(props.width) : undefined,
|
|
834
911
|
height: props.height ? parseFloat(props.height) : undefined,
|
|
835
912
|
theme: props.theme,
|
|
@@ -919,6 +996,7 @@ function parse(src) {
|
|
|
919
996
|
registerAuthoredId(grp.id, "group", t);
|
|
920
997
|
if (isBare) {
|
|
921
998
|
grp.label = "";
|
|
999
|
+
grp.padding = grp.padding ?? 0;
|
|
922
1000
|
grp.style = {
|
|
923
1001
|
...grp.style,
|
|
924
1002
|
fill: grp.style?.fill ?? "none",
|
|
@@ -1089,7 +1167,7 @@ function parse(src) {
|
|
|
1089
1167
|
node.style = { ...ast.styles[node.id], ...node.style };
|
|
1090
1168
|
}
|
|
1091
1169
|
}
|
|
1092
|
-
return ast;
|
|
1170
|
+
return applyPluginAstTransforms(ast, options.plugins);
|
|
1093
1171
|
}
|
|
1094
1172
|
|
|
1095
1173
|
// ============================================================
|
|
@@ -3438,6 +3516,8 @@ function buildSceneGraph(ast) {
|
|
|
3438
3516
|
groupId: nodeParentById.get(n.id),
|
|
3439
3517
|
width: n.width,
|
|
3440
3518
|
height: n.height,
|
|
3519
|
+
authoredX: n.x,
|
|
3520
|
+
authoredY: n.y,
|
|
3441
3521
|
deg: n.deg,
|
|
3442
3522
|
dx: n.dx,
|
|
3443
3523
|
dy: n.dy,
|
|
@@ -3466,6 +3546,8 @@ function buildSceneGraph(ast) {
|
|
|
3466
3546
|
align: (g.align ?? "start"),
|
|
3467
3547
|
justify: (g.justify ?? "start"),
|
|
3468
3548
|
style: { ...ast.styles[g.id], ...themeStyle, ...g.style },
|
|
3549
|
+
authoredX: g.x,
|
|
3550
|
+
authoredY: g.y,
|
|
3469
3551
|
width: g.width,
|
|
3470
3552
|
height: g.height,
|
|
3471
3553
|
x: 0,
|
|
@@ -3485,6 +3567,8 @@ function buildSceneGraph(ast) {
|
|
|
3485
3567
|
headerH: TABLE.headerH,
|
|
3486
3568
|
labelH: TABLE.labelH,
|
|
3487
3569
|
style: { ...ast.styles[t.id], ...themeStyle, ...t.style },
|
|
3570
|
+
authoredX: t.x,
|
|
3571
|
+
authoredY: t.y,
|
|
3488
3572
|
x: 0,
|
|
3489
3573
|
y: 0,
|
|
3490
3574
|
w: 0,
|
|
@@ -3499,6 +3583,8 @@ function buildSceneGraph(ast) {
|
|
|
3499
3583
|
label: c.label,
|
|
3500
3584
|
data: c.data,
|
|
3501
3585
|
style: { ...ast.styles[c.id], ...themeStyle, ...c.style },
|
|
3586
|
+
authoredX: c.x,
|
|
3587
|
+
authoredY: c.y,
|
|
3502
3588
|
x: 0,
|
|
3503
3589
|
y: 0,
|
|
3504
3590
|
w: c.width ?? CHART.defaultW,
|
|
@@ -3514,6 +3600,8 @@ function buildSceneGraph(ast) {
|
|
|
3514
3600
|
style: { ...ast.styles[m.id], ...themeStyle, ...m.style },
|
|
3515
3601
|
width: m.width,
|
|
3516
3602
|
height: m.height,
|
|
3603
|
+
authoredX: m.x,
|
|
3604
|
+
authoredY: m.y,
|
|
3517
3605
|
x: 0,
|
|
3518
3606
|
y: 0,
|
|
3519
3607
|
w: 0,
|
|
@@ -3526,6 +3614,8 @@ function buildSceneGraph(ast) {
|
|
|
3526
3614
|
to: e.to,
|
|
3527
3615
|
connector: e.connector,
|
|
3528
3616
|
label: e.label,
|
|
3617
|
+
fromAnchor: e.fromAnchor,
|
|
3618
|
+
toAnchor: e.toAnchor,
|
|
3529
3619
|
dashed: e.dashed ?? false,
|
|
3530
3620
|
bidirectional: e.bidirectional ?? false,
|
|
3531
3621
|
style: e.style ?? {},
|
|
@@ -4103,28 +4193,13 @@ function connMeta(connector) {
|
|
|
4103
4193
|
return { arrowAt: "start", dashed };
|
|
4104
4194
|
return { arrowAt: "end", dashed };
|
|
4105
4195
|
}
|
|
4106
|
-
// ── Generic rect connection point ────────────────────────────────────────
|
|
4107
|
-
function rectConnPoint$1(rx, ry, rw, rh, ox, oy) {
|
|
4108
|
-
const cx = rx + rw / 2, cy = ry + rh / 2;
|
|
4109
|
-
const dx = ox - cx, dy = oy - cy;
|
|
4110
|
-
if (Math.abs(dx) < 0.01 && Math.abs(dy) < 0.01)
|
|
4111
|
-
return [cx, cy];
|
|
4112
|
-
const hw = rw / 2 - 2, hh = rh / 2 - 2;
|
|
4113
|
-
const tx = Math.abs(dx) > 0.01 ? hw / Math.abs(dx) : 1e9;
|
|
4114
|
-
const ty = Math.abs(dy) > 0.01 ? hh / Math.abs(dy) : 1e9;
|
|
4115
|
-
const t = Math.min(tx, ty);
|
|
4116
|
-
return [cx + t * dx, cy + t * dy];
|
|
4117
|
-
}
|
|
4118
4196
|
// ── Resolve an endpoint entity by ID across all maps ─────────────────────
|
|
4119
4197
|
function resolveEndpoint(id, nm, tm, gm, cm) {
|
|
4120
4198
|
return nm.get(id) ?? tm.get(id) ?? gm.get(id) ?? cm.get(id) ?? null;
|
|
4121
4199
|
}
|
|
4122
4200
|
// ── Get connection point for any entity ──────────────────────────────────
|
|
4123
|
-
function getConnPoint(src, dstCX, dstCY) {
|
|
4124
|
-
|
|
4125
|
-
return connPoint(src, { x: dstCX - 1, y: dstCY - 1, w: 2, h: 2});
|
|
4126
|
-
}
|
|
4127
|
-
return rectConnPoint$1(src.x, src.y, src.w, src.h, dstCX, dstCY);
|
|
4201
|
+
function getConnPoint(src, dstCX, dstCY, anchor) {
|
|
4202
|
+
return anchoredConnPoint(src, anchor, dstCX, dstCY);
|
|
4128
4203
|
}
|
|
4129
4204
|
// ── Group depth (for paint order) ────────────────────────────────────────
|
|
4130
4205
|
function groupDepth(g, gm) {
|
|
@@ -4361,6 +4436,12 @@ function iW(r, em) {
|
|
|
4361
4436
|
function iH(r, em) {
|
|
4362
4437
|
return em.get(r.id).h;
|
|
4363
4438
|
}
|
|
4439
|
+
function iAuthX(r, em) {
|
|
4440
|
+
return em.get(r.id).authoredX ?? 0;
|
|
4441
|
+
}
|
|
4442
|
+
function iAuthY(r, em) {
|
|
4443
|
+
return em.get(r.id).authoredY ?? 0;
|
|
4444
|
+
}
|
|
4364
4445
|
function setPos(r, x, y, em) {
|
|
4365
4446
|
const e = em.get(r.id);
|
|
4366
4447
|
e.x = Math.round(x);
|
|
@@ -4407,6 +4488,12 @@ function measure(g, gm, tm, cm, mdm, em) {
|
|
|
4407
4488
|
g.w = cols * cellW + (cols - 1) * gap + pad * 2;
|
|
4408
4489
|
g.h = rows * cellH + (rows - 1) * gap + pad * 2 + labelH;
|
|
4409
4490
|
}
|
|
4491
|
+
else if (layout === "absolute") {
|
|
4492
|
+
const maxRight = Math.max(0, ...kids.map((r) => iAuthX(r, em) + iW(r, em)));
|
|
4493
|
+
const maxBottom = Math.max(0, ...kids.map((r) => iAuthY(r, em) + iH(r, em)));
|
|
4494
|
+
g.w = maxRight + pad * 2;
|
|
4495
|
+
g.h = maxBottom + pad * 2 + labelH;
|
|
4496
|
+
}
|
|
4410
4497
|
else {
|
|
4411
4498
|
// column (default)
|
|
4412
4499
|
g.w = Math.max(...ws) + pad * 2;
|
|
@@ -4497,6 +4584,11 @@ function place(g, gm, em) {
|
|
|
4497
4584
|
setPos(ref, contentX + (i % cols) * (cellW + gap), contentY + Math.floor(i / cols) * (cellH + gap), em);
|
|
4498
4585
|
});
|
|
4499
4586
|
}
|
|
4587
|
+
else if (layout === "absolute") {
|
|
4588
|
+
kids.forEach((ref) => {
|
|
4589
|
+
setPos(ref, contentX + iAuthX(ref, em), contentY + iAuthY(ref, em), em);
|
|
4590
|
+
});
|
|
4591
|
+
}
|
|
4500
4592
|
else {
|
|
4501
4593
|
// column (default)
|
|
4502
4594
|
const ws = kids.map((r) => iW(r, em));
|
|
@@ -4542,6 +4634,50 @@ function connPoint(n, other) {
|
|
|
4542
4634
|
const t = Math.min(tx, ty);
|
|
4543
4635
|
return [cx + t * dx, cy + t * dy];
|
|
4544
4636
|
}
|
|
4637
|
+
function clampInset(value) {
|
|
4638
|
+
return Math.max(2, value);
|
|
4639
|
+
}
|
|
4640
|
+
function anchoredConnPoint(entity, anchor, otherCX, otherCY) {
|
|
4641
|
+
if (!anchor) {
|
|
4642
|
+
if (entity.shape && otherCX != null && otherCY != null) {
|
|
4643
|
+
return connPoint(entity, { x: otherCX - 1, y: otherCY - 1, w: 2, h: 2});
|
|
4644
|
+
}
|
|
4645
|
+
if (otherCX != null && otherCY != null) {
|
|
4646
|
+
return rectConnPoint(entity.x, entity.y, entity.w, entity.h, otherCX, otherCY);
|
|
4647
|
+
}
|
|
4648
|
+
return [entity.x + entity.w / 2, entity.y + entity.h / 2];
|
|
4649
|
+
}
|
|
4650
|
+
const insetX = clampInset(Math.min(10, entity.w / 2));
|
|
4651
|
+
const insetY = clampInset(Math.min(10, entity.h / 2));
|
|
4652
|
+
const left = entity.x + insetX;
|
|
4653
|
+
const right = entity.x + entity.w - insetX;
|
|
4654
|
+
const top = entity.y + insetY;
|
|
4655
|
+
const bottom = entity.y + entity.h - insetY;
|
|
4656
|
+
const cx = entity.x + entity.w / 2;
|
|
4657
|
+
const cy = entity.y + entity.h / 2;
|
|
4658
|
+
switch (anchor) {
|
|
4659
|
+
case "top":
|
|
4660
|
+
return [cx, top];
|
|
4661
|
+
case "right":
|
|
4662
|
+
return [right, cy];
|
|
4663
|
+
case "bottom":
|
|
4664
|
+
return [cx, bottom];
|
|
4665
|
+
case "left":
|
|
4666
|
+
return [left, cy];
|
|
4667
|
+
case "center":
|
|
4668
|
+
return [cx, cy];
|
|
4669
|
+
case "top-left":
|
|
4670
|
+
return [left, top];
|
|
4671
|
+
case "top-right":
|
|
4672
|
+
return [right, top];
|
|
4673
|
+
case "bottom-left":
|
|
4674
|
+
return [left, bottom];
|
|
4675
|
+
case "bottom-right":
|
|
4676
|
+
return [right, bottom];
|
|
4677
|
+
default:
|
|
4678
|
+
return [cx, cy];
|
|
4679
|
+
}
|
|
4680
|
+
}
|
|
4545
4681
|
function rectConnPoint(rx, ry, rw, rh, ox, oy) {
|
|
4546
4682
|
const cx = rx + rw / 2, cy = ry + rh / 2;
|
|
4547
4683
|
const dx = ox - cx, dy = oy - cy;
|
|
@@ -4573,17 +4709,6 @@ function routeEdges(sg) {
|
|
|
4573
4709
|
return c;
|
|
4574
4710
|
return null;
|
|
4575
4711
|
}
|
|
4576
|
-
function connPt(src, dstCX, dstCY) {
|
|
4577
|
-
// SceneNode has a .shape field; use the existing connPoint for it
|
|
4578
|
-
if ("shape" in src && src.shape) {
|
|
4579
|
-
return connPoint(src, {
|
|
4580
|
-
x: dstCX - 1,
|
|
4581
|
-
y: dstCY - 1,
|
|
4582
|
-
w: 2,
|
|
4583
|
-
h: 2});
|
|
4584
|
-
}
|
|
4585
|
-
return rectConnPoint(src.x, src.y, src.w, src.h, dstCX, dstCY);
|
|
4586
|
-
}
|
|
4587
4712
|
for (const e of sg.edges) {
|
|
4588
4713
|
const src = resolve(e.from);
|
|
4589
4714
|
const dst = resolve(e.to);
|
|
@@ -4593,7 +4718,10 @@ function routeEdges(sg) {
|
|
|
4593
4718
|
}
|
|
4594
4719
|
const dstCX = dst.x + dst.w / 2, dstCY = dst.y + dst.h / 2;
|
|
4595
4720
|
const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
|
|
4596
|
-
e.points = [
|
|
4721
|
+
e.points = [
|
|
4722
|
+
anchoredConnPoint(src, e.fromAnchor, dstCX, dstCY),
|
|
4723
|
+
anchoredConnPoint(dst, e.toAnchor, srcCX, srcCY),
|
|
4724
|
+
];
|
|
4597
4725
|
}
|
|
4598
4726
|
}
|
|
4599
4727
|
function computeBounds(sg, margin) {
|
|
@@ -4679,6 +4807,7 @@ function layout(sg) {
|
|
|
4679
4807
|
const rootCols = Number(sg.config["columns"] ?? 1);
|
|
4680
4808
|
const useGrid = rootLayout === "grid" && rootCols > 0;
|
|
4681
4809
|
const useColumn = rootLayout === "column";
|
|
4810
|
+
const useAbsolute = rootLayout === "absolute";
|
|
4682
4811
|
if (useGrid) {
|
|
4683
4812
|
// ── Grid: per-row heights, per-column widths (no wasted space) ──
|
|
4684
4813
|
const cols = rootCols;
|
|
@@ -4710,6 +4839,13 @@ function layout(sg) {
|
|
|
4710
4839
|
e.y = rowY[Math.floor(idx / cols)];
|
|
4711
4840
|
});
|
|
4712
4841
|
}
|
|
4842
|
+
else if (useAbsolute) {
|
|
4843
|
+
for (const ref of rootOrder) {
|
|
4844
|
+
const e = em.get(ref.id);
|
|
4845
|
+
e.x = MARGIN + (e.authoredX ?? 0);
|
|
4846
|
+
e.y = MARGIN + (e.authoredY ?? 0);
|
|
4847
|
+
}
|
|
4848
|
+
}
|
|
4713
4849
|
else {
|
|
4714
4850
|
// ── Row or Column linear flow ──────────────────────────
|
|
4715
4851
|
let pos = MARGIN;
|
|
@@ -7726,8 +7862,8 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
7726
7862
|
continue;
|
|
7727
7863
|
const dstCX = dst.x + dst.w / 2, dstCY = dst.y + dst.h / 2;
|
|
7728
7864
|
const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
|
|
7729
|
-
const [x1, y1] = getConnPoint(src, dstCX, dstCY);
|
|
7730
|
-
const [x2, y2] = getConnPoint(dst, srcCX, srcCY);
|
|
7865
|
+
const [x1, y1] = getConnPoint(src, dstCX, dstCY, e.fromAnchor);
|
|
7866
|
+
const [x2, y2] = getConnPoint(dst, srcCX, srcCY, e.toAnchor);
|
|
7731
7867
|
const eg = mkGroup(`edge-${e.from}-${e.to}`, "eg");
|
|
7732
7868
|
if (e.style?.opacity != null)
|
|
7733
7869
|
eg.setAttribute("opacity", String(e.style.opacity));
|
|
@@ -7804,6 +7940,8 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
7804
7940
|
ng.dataset.h = String(n.h);
|
|
7805
7941
|
if (n.pathData)
|
|
7806
7942
|
ng.dataset.pathData = n.pathData;
|
|
7943
|
+
if (n.meta?.animationParent)
|
|
7944
|
+
ng.dataset.animationParent = n.meta.animationParent;
|
|
7807
7945
|
if (n.style?.opacity != null)
|
|
7808
7946
|
ng.setAttribute("opacity", String(n.style.opacity));
|
|
7809
7947
|
// ── Static transform (deg, dx, dy, factor) ──────────
|
|
@@ -8457,8 +8595,8 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
8457
8595
|
continue;
|
|
8458
8596
|
const dstCX = dst.x + dst.w / 2, dstCY = dst.y + dst.h / 2;
|
|
8459
8597
|
const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
|
|
8460
|
-
const [x1, y1] = getConnPoint(src, dstCX, dstCY);
|
|
8461
|
-
const [x2, y2] = getConnPoint(dst, srcCX, srcCY);
|
|
8598
|
+
const [x1, y1] = getConnPoint(src, dstCX, dstCY, e.fromAnchor);
|
|
8599
|
+
const [x2, y2] = getConnPoint(dst, srcCX, srcCY, e.toAnchor);
|
|
8462
8600
|
if (e.style?.opacity != null)
|
|
8463
8601
|
ctx.globalAlpha = Number(e.style.opacity);
|
|
8464
8602
|
const ecol = String(e.style?.stroke ?? palette.edgeStroke);
|
|
@@ -9315,6 +9453,13 @@ class AnimationController {
|
|
|
9315
9453
|
this.drawTargetNodes.delete(`node-${s.target}`);
|
|
9316
9454
|
}
|
|
9317
9455
|
}
|
|
9456
|
+
this._relatedElementIdsByPrimaryId = this._buildRelatedElementIndex();
|
|
9457
|
+
for (const nodeId of Array.from(this.drawTargetNodes)) {
|
|
9458
|
+
const relatedIds = this._relatedElementIdsByPrimaryId.get(nodeId);
|
|
9459
|
+
if (!relatedIds)
|
|
9460
|
+
continue;
|
|
9461
|
+
relatedIds.forEach((id) => this.drawTargetNodes.add(id));
|
|
9462
|
+
}
|
|
9318
9463
|
this._drawStepIndexByElementId = this._buildDrawStepIndex();
|
|
9319
9464
|
const { parentGroupByElementId, groupDescendantIds } = this._buildGroupVisibilityIndex();
|
|
9320
9465
|
this._parentGroupByElementId = parentGroupByElementId;
|
|
@@ -9345,10 +9490,30 @@ class AnimationController {
|
|
|
9345
9490
|
const el = resolveNonEdgeDrawEl(this.svg, step.target);
|
|
9346
9491
|
if (el && !drawStepIndexByElementId.has(el.id)) {
|
|
9347
9492
|
drawStepIndexByElementId.set(el.id, stepIndex);
|
|
9493
|
+
this._relatedElementIdsByPrimaryId.get(el.id)?.forEach((relatedId) => {
|
|
9494
|
+
if (!drawStepIndexByElementId.has(relatedId)) {
|
|
9495
|
+
drawStepIndexByElementId.set(relatedId, stepIndex);
|
|
9496
|
+
}
|
|
9497
|
+
});
|
|
9348
9498
|
}
|
|
9349
9499
|
});
|
|
9350
9500
|
return drawStepIndexByElementId;
|
|
9351
9501
|
}
|
|
9502
|
+
_buildRelatedElementIndex() {
|
|
9503
|
+
const relatedElementIdsByPrimaryId = new Map();
|
|
9504
|
+
this.svg.querySelectorAll(POSITIONABLE_SELECTOR).forEach((el) => {
|
|
9505
|
+
const animationParent = el.dataset.animationParent;
|
|
9506
|
+
if (!animationParent)
|
|
9507
|
+
return;
|
|
9508
|
+
const primaryEl = resolveNonEdgeDrawEl(this.svg, animationParent);
|
|
9509
|
+
if (!primaryEl || primaryEl.id === el.id)
|
|
9510
|
+
return;
|
|
9511
|
+
const related = relatedElementIdsByPrimaryId.get(primaryEl.id) ?? new Set();
|
|
9512
|
+
related.add(el.id);
|
|
9513
|
+
relatedElementIdsByPrimaryId.set(primaryEl.id, related);
|
|
9514
|
+
});
|
|
9515
|
+
return relatedElementIdsByPrimaryId;
|
|
9516
|
+
}
|
|
9352
9517
|
_buildGroupVisibilityIndex() {
|
|
9353
9518
|
const parentGroupByElementId = new Map();
|
|
9354
9519
|
const directChildIdsByGroup = new Map();
|
|
@@ -9427,10 +9592,18 @@ class AnimationController {
|
|
|
9427
9592
|
const el = resolveEl(this.svg, target);
|
|
9428
9593
|
if (!el)
|
|
9429
9594
|
return [];
|
|
9430
|
-
if (!el.id.startsWith("group-"))
|
|
9431
|
-
|
|
9595
|
+
if (!el.id.startsWith("group-")) {
|
|
9596
|
+
const ids = new Set([el.id]);
|
|
9597
|
+
this._relatedElementIdsByPrimaryId.get(el.id)?.forEach((id) => ids.add(id));
|
|
9598
|
+
return Array.from(ids)
|
|
9599
|
+
.map((id) => getEl(this.svg, id))
|
|
9600
|
+
.filter((candidate) => candidate != null);
|
|
9601
|
+
}
|
|
9432
9602
|
const ids = new Set([el.id]);
|
|
9433
9603
|
this._groupDescendantIds.get(el.id)?.forEach((id) => ids.add(id));
|
|
9604
|
+
Array.from(ids).forEach((id) => {
|
|
9605
|
+
this._relatedElementIdsByPrimaryId.get(id)?.forEach((relatedId) => ids.add(relatedId));
|
|
9606
|
+
});
|
|
9434
9607
|
return Array.from(ids)
|
|
9435
9608
|
.map((id) => getEl(this.svg, id))
|
|
9436
9609
|
.filter((candidate) => candidate != null);
|
|
@@ -9839,9 +10012,11 @@ class AnimationController {
|
|
|
9839
10012
|
// ── highlight ────────────────────────────────────────────
|
|
9840
10013
|
_doHighlight(target) {
|
|
9841
10014
|
this.svg
|
|
9842
|
-
.querySelectorAll(".ng.hl, .tg.hl, .ntg.hl, .cg.hl, .eg.hl")
|
|
10015
|
+
.querySelectorAll(".ng.hl, .gg.hl, .tg.hl, .ntg.hl, .cg.hl, .mdg.hl, .eg.hl")
|
|
9843
10016
|
.forEach((e) => e.classList.remove("hl"));
|
|
9844
|
-
|
|
10017
|
+
for (const el of this._resolveCascadeTargets(target)) {
|
|
10018
|
+
el.classList.add("hl");
|
|
10019
|
+
}
|
|
9845
10020
|
}
|
|
9846
10021
|
// ── fade / unfade ─────────────────────────────────────────
|
|
9847
10022
|
_doFade(target, doFade) {
|
|
@@ -9877,8 +10052,8 @@ class AnimationController {
|
|
|
9877
10052
|
}
|
|
9878
10053
|
// ── move ──────────────────────────────────────────────────
|
|
9879
10054
|
_doMove(target, step, silent) {
|
|
9880
|
-
const
|
|
9881
|
-
if (!
|
|
10055
|
+
const targets = this._resolveCascadeTargets(target);
|
|
10056
|
+
if (!targets.length)
|
|
9882
10057
|
return;
|
|
9883
10058
|
const cur = this._transforms.get(target) ?? {
|
|
9884
10059
|
tx: 0,
|
|
@@ -9891,12 +10066,14 @@ class AnimationController {
|
|
|
9891
10066
|
tx: cur.tx + (step.dx ?? 0),
|
|
9892
10067
|
ty: cur.ty + (step.dy ?? 0),
|
|
9893
10068
|
});
|
|
9894
|
-
|
|
10069
|
+
for (const el of targets) {
|
|
10070
|
+
this._writeTransform(el, target, silent, step.duration ?? 420);
|
|
10071
|
+
}
|
|
9895
10072
|
}
|
|
9896
10073
|
// ── scale ─────────────────────────────────────────────────
|
|
9897
10074
|
_doScale(target, step, silent) {
|
|
9898
|
-
const
|
|
9899
|
-
if (!
|
|
10075
|
+
const targets = this._resolveCascadeTargets(target);
|
|
10076
|
+
if (!targets.length)
|
|
9900
10077
|
return;
|
|
9901
10078
|
const cur = this._transforms.get(target) ?? {
|
|
9902
10079
|
tx: 0,
|
|
@@ -9905,12 +10082,14 @@ class AnimationController {
|
|
|
9905
10082
|
rotate: 0,
|
|
9906
10083
|
};
|
|
9907
10084
|
this._transforms.set(target, { ...cur, scale: step.factor ?? 1 });
|
|
9908
|
-
|
|
10085
|
+
for (const el of targets) {
|
|
10086
|
+
this._writeTransform(el, target, silent, step.duration ?? 350);
|
|
10087
|
+
}
|
|
9909
10088
|
}
|
|
9910
10089
|
// ── rotate ────────────────────────────────────────────────
|
|
9911
10090
|
_doRotate(target, step, silent) {
|
|
9912
|
-
const
|
|
9913
|
-
if (!
|
|
10091
|
+
const targets = this._resolveCascadeTargets(target);
|
|
10092
|
+
if (!targets.length)
|
|
9914
10093
|
return;
|
|
9915
10094
|
const cur = this._transforms.get(target) ?? {
|
|
9916
10095
|
tx: 0,
|
|
@@ -9922,7 +10101,9 @@ class AnimationController {
|
|
|
9922
10101
|
...cur,
|
|
9923
10102
|
rotate: cur.rotate + (step.deg ?? 0),
|
|
9924
10103
|
});
|
|
9925
|
-
|
|
10104
|
+
for (const el of targets) {
|
|
10105
|
+
this._writeTransform(el, target, silent, step.duration ?? 400);
|
|
10106
|
+
}
|
|
9926
10107
|
}
|
|
9927
10108
|
_doDraw(step, silent) {
|
|
9928
10109
|
const { target } = step;
|
|
@@ -10066,18 +10247,20 @@ class AnimationController {
|
|
|
10066
10247
|
return;
|
|
10067
10248
|
}
|
|
10068
10249
|
// ── Node draw ──────────────────────────────────────
|
|
10069
|
-
const
|
|
10070
|
-
if (!
|
|
10250
|
+
const nodeEls = this._resolveCascadeTargets(target).filter((el) => el.classList.contains("ng"));
|
|
10251
|
+
if (!nodeEls.length)
|
|
10071
10252
|
return;
|
|
10072
|
-
|
|
10073
|
-
|
|
10074
|
-
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10253
|
+
for (const nodeEl of nodeEls) {
|
|
10254
|
+
showDrawEl(nodeEl);
|
|
10255
|
+
if (silent) {
|
|
10256
|
+
revealNodeInstant(nodeEl);
|
|
10257
|
+
}
|
|
10258
|
+
else {
|
|
10259
|
+
if (!nodeGuidePathEl(nodeEl) && !nodeEl.querySelector("path")?.style.strokeDasharray) {
|
|
10260
|
+
prepareNodeForDraw(nodeEl);
|
|
10261
|
+
}
|
|
10262
|
+
animateNodeDraw(nodeEl, step.duration ?? ANIMATION.nodeStrokeDur, step.duration ?? ANIMATION.textRevealMs);
|
|
10079
10263
|
}
|
|
10080
|
-
animateNodeDraw(nodeEl, step.duration ?? ANIMATION.nodeStrokeDur, step.duration ?? ANIMATION.textRevealMs);
|
|
10081
10264
|
}
|
|
10082
10265
|
}
|
|
10083
10266
|
// ── erase ─────────────────────────────────────────────────
|
|
@@ -10098,45 +10281,44 @@ class AnimationController {
|
|
|
10098
10281
|
}
|
|
10099
10282
|
// ── pulse ─────────────────────────────────────────────────
|
|
10100
10283
|
_doPulse(target, duration = 500) {
|
|
10101
|
-
|
|
10102
|
-
|
|
10103
|
-
|
|
10104
|
-
|
|
10105
|
-
|
|
10284
|
+
for (const el of this._resolveCascadeTargets(target)) {
|
|
10285
|
+
el.animate([
|
|
10286
|
+
{ filter: "brightness(1)" },
|
|
10287
|
+
{ filter: "brightness(1.6)" },
|
|
10288
|
+
{ filter: "brightness(1)" },
|
|
10289
|
+
], { duration, iterations: 3 });
|
|
10290
|
+
}
|
|
10106
10291
|
}
|
|
10107
10292
|
// ── color ─────────────────────────────────────────────────
|
|
10108
10293
|
_doColor(target, color) {
|
|
10109
10294
|
if (!color)
|
|
10110
10295
|
return;
|
|
10111
|
-
const el
|
|
10112
|
-
|
|
10113
|
-
|
|
10114
|
-
|
|
10115
|
-
|
|
10116
|
-
|
|
10117
|
-
|
|
10118
|
-
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
10126
|
-
|
|
10127
|
-
|
|
10128
|
-
|
|
10129
|
-
|
|
10130
|
-
|
|
10131
|
-
if (attrFill === null && c.tagName === "path")
|
|
10132
|
-
return;
|
|
10133
|
-
c.style.fill = color;
|
|
10134
|
-
hit = true;
|
|
10135
|
-
});
|
|
10136
|
-
if (!hit) {
|
|
10137
|
-
el.querySelectorAll("text").forEach((t) => {
|
|
10138
|
-
t.style.fill = color;
|
|
10296
|
+
for (const el of this._resolveCascadeTargets(target)) {
|
|
10297
|
+
if (parseEdgeTarget(target)) {
|
|
10298
|
+
el.querySelectorAll("path, line, polyline").forEach((p) => {
|
|
10299
|
+
p.style.stroke = color;
|
|
10300
|
+
});
|
|
10301
|
+
el.querySelectorAll("polygon").forEach((p) => {
|
|
10302
|
+
p.style.fill = color;
|
|
10303
|
+
p.style.stroke = color;
|
|
10304
|
+
});
|
|
10305
|
+
continue;
|
|
10306
|
+
}
|
|
10307
|
+
let hit = false;
|
|
10308
|
+
el.querySelectorAll("path, rect, ellipse, polygon").forEach((c) => {
|
|
10309
|
+
const attrFill = c.getAttribute("fill");
|
|
10310
|
+
if (attrFill === "none")
|
|
10311
|
+
return;
|
|
10312
|
+
if (attrFill === null && c.tagName === "path")
|
|
10313
|
+
return;
|
|
10314
|
+
c.style.fill = color;
|
|
10315
|
+
hit = true;
|
|
10139
10316
|
});
|
|
10317
|
+
if (!hit) {
|
|
10318
|
+
el.querySelectorAll("text").forEach((t) => {
|
|
10319
|
+
t.style.fill = color;
|
|
10320
|
+
});
|
|
10321
|
+
}
|
|
10140
10322
|
}
|
|
10141
10323
|
}
|
|
10142
10324
|
// ── narration ───────────────────────────────────────────
|
|
@@ -10730,7 +10912,7 @@ class EventEmitter {
|
|
|
10730
10912
|
}
|
|
10731
10913
|
|
|
10732
10914
|
function render(options) {
|
|
10733
|
-
const { container: rawContainer, dsl, renderer = "svg", injectCSS = true, tts, svgOptions = {}, canvasOptions = {}, onNodeClick, onReady, } = options;
|
|
10915
|
+
const { container: rawContainer, dsl, plugins, renderer = "svg", injectCSS = true, tts, svgOptions = {}, canvasOptions = {}, onNodeClick, onReady, } = options;
|
|
10734
10916
|
if (injectCSS && !document.getElementById("ai-diagram-css")) {
|
|
10735
10917
|
const style = document.createElement("style");
|
|
10736
10918
|
style.id = "ai-diagram-css";
|
|
@@ -10746,7 +10928,7 @@ function render(options) {
|
|
|
10746
10928
|
else {
|
|
10747
10929
|
el = rawContainer;
|
|
10748
10930
|
}
|
|
10749
|
-
const ast = parse(dsl);
|
|
10931
|
+
const ast = parse(dsl, { plugins });
|
|
10750
10932
|
const scene = buildSceneGraph(ast);
|
|
10751
10933
|
layout(scene);
|
|
10752
10934
|
let svg;
|
|
@@ -11090,6 +11272,7 @@ class SketchmarkCanvas {
|
|
|
11090
11272
|
const instance = render({
|
|
11091
11273
|
container: this.diagramWrap,
|
|
11092
11274
|
dsl: this.dsl,
|
|
11275
|
+
plugins: this.options.plugins,
|
|
11093
11276
|
renderer: this.renderer,
|
|
11094
11277
|
svgOptions: { interactive: true, showTitle: true, theme: this.options.svgOptions?.theme ?? this.theme, ...this.options.svgOptions },
|
|
11095
11278
|
canvasOptions: this.options.canvasOptions,
|
|
@@ -12167,6 +12350,7 @@ class SketchmarkEmbed {
|
|
|
12167
12350
|
const instance = render({
|
|
12168
12351
|
container: this.diagramWrap,
|
|
12169
12352
|
dsl: this.dsl,
|
|
12353
|
+
plugins: this.options.plugins,
|
|
12170
12354
|
renderer: "svg",
|
|
12171
12355
|
svgOptions: {
|
|
12172
12356
|
showTitle: true,
|