sketchmark 1.3.4 → 1.3.6
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 +33 -25
- package/dist/animation/index.d.ts +10 -0
- package/dist/animation/index.d.ts.map +1 -1
- package/dist/ast/types.d.ts +6 -0
- package/dist/ast/types.d.ts.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/index.cjs +238 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +238 -67
- package/dist/index.js.map +1 -1
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/renderer/canvas/index.d.ts.map +1 -1
- package/dist/renderer/shapes/icon.d.ts.map +1 -1
- package/dist/renderer/shapes/image.d.ts.map +1 -1
- package/dist/renderer/shapes/label-strip.d.ts +6 -0
- package/dist/renderer/shapes/label-strip.d.ts.map +1 -0
- package/dist/renderer/shapes/line.d.ts.map +1 -1
- package/dist/renderer/svg/index.d.ts.map +1 -1
- package/dist/scene/index.d.ts +6 -0
- package/dist/scene/index.d.ts.map +1 -1
- package/dist/sketchmark.iife.js +238 -67
- package/dist/ui/canvas.d.ts +1 -0
- package/dist/ui/canvas.d.ts.map +1 -1
- package/dist/ui/embed.d.ts +1 -0
- package/dist/ui/embed.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -598,6 +598,12 @@ function parse(src, options = {}) {
|
|
|
598
598
|
id,
|
|
599
599
|
shape,
|
|
600
600
|
label: props.label || "",
|
|
601
|
+
...(props["label-dx"] !== undefined
|
|
602
|
+
? { labelDx: parseFloat(props["label-dx"]) }
|
|
603
|
+
: {}),
|
|
604
|
+
...(props["label-dy"] !== undefined
|
|
605
|
+
? { labelDy: parseFloat(props["label-dy"]) }
|
|
606
|
+
: {}),
|
|
601
607
|
...(props.width ? { width: parseFloat(props.width) } : {}),
|
|
602
608
|
...(props.height ? { height: parseFloat(props.height) } : {}),
|
|
603
609
|
...(props.x ? { x: parseFloat(props.x) } : {}),
|
|
@@ -638,6 +644,12 @@ function parse(src, options = {}) {
|
|
|
638
644
|
id,
|
|
639
645
|
shape: "note",
|
|
640
646
|
label: (props.label ?? "").replace(/\\n/g, "\n"),
|
|
647
|
+
...(props["label-dx"] !== undefined
|
|
648
|
+
? { labelDx: parseFloat(props["label-dx"]) }
|
|
649
|
+
: {}),
|
|
650
|
+
...(props["label-dy"] !== undefined
|
|
651
|
+
? { labelDy: parseFloat(props["label-dy"]) }
|
|
652
|
+
: {}),
|
|
641
653
|
theme: props.theme,
|
|
642
654
|
...(meta ? { meta } : {}),
|
|
643
655
|
style: propsToStyle(props),
|
|
@@ -686,6 +698,8 @@ function parse(src, options = {}) {
|
|
|
686
698
|
kind: "group",
|
|
687
699
|
id,
|
|
688
700
|
label: props.label ?? "",
|
|
701
|
+
labelDx: props["label-dx"] !== undefined ? parseFloat(props["label-dx"]) : undefined,
|
|
702
|
+
labelDy: props["label-dy"] !== undefined ? parseFloat(props["label-dy"]) : undefined,
|
|
689
703
|
children: [],
|
|
690
704
|
layout: props.layout,
|
|
691
705
|
columns: props.columns !== undefined ? parseInt(props.columns, 10) : undefined,
|
|
@@ -730,6 +744,8 @@ function parse(src, options = {}) {
|
|
|
730
744
|
to: toTok.value,
|
|
731
745
|
connector: connector,
|
|
732
746
|
label: props.label,
|
|
747
|
+
labelDx: props["label-dx"] !== undefined ? parseFloat(props["label-dx"]) : undefined,
|
|
748
|
+
labelDy: props["label-dy"] !== undefined ? parseFloat(props["label-dy"]) : undefined,
|
|
733
749
|
fromAnchor: props["anchor-from"],
|
|
734
750
|
toAnchor: props["anchor-to"],
|
|
735
751
|
dashed,
|
|
@@ -1231,6 +1247,7 @@ const LAYOUT = {
|
|
|
1231
1247
|
const NODE = {
|
|
1232
1248
|
minW: 90, // minimum auto-sized node width (px)
|
|
1233
1249
|
maxW: 300, // maximum auto-sized node width (px)
|
|
1250
|
+
mediaLabelH: 20, // reserved bottom strip for icon/image/line labels (px)
|
|
1234
1251
|
basePad: 26, // base padding added to label width (px)
|
|
1235
1252
|
};
|
|
1236
1253
|
// ── Shape-specific sizing ──────────────────────────────────
|
|
@@ -3554,6 +3571,8 @@ function buildSceneGraph(ast) {
|
|
|
3554
3571
|
id: n.id,
|
|
3555
3572
|
shape: n.shape,
|
|
3556
3573
|
label: n.label,
|
|
3574
|
+
labelDx: n.labelDx,
|
|
3575
|
+
labelDy: n.labelDy,
|
|
3557
3576
|
style: { ...ast.styles[n.id], ...themeStyle, ...n.style },
|
|
3558
3577
|
groupId: nodeParentById.get(n.id),
|
|
3559
3578
|
width: n.width,
|
|
@@ -3579,6 +3598,8 @@ function buildSceneGraph(ast) {
|
|
|
3579
3598
|
return {
|
|
3580
3599
|
id: g.id,
|
|
3581
3600
|
label: g.label,
|
|
3601
|
+
labelDx: g.labelDx,
|
|
3602
|
+
labelDy: g.labelDy,
|
|
3582
3603
|
parentId: groupParentById.get(g.id),
|
|
3583
3604
|
children: g.children,
|
|
3584
3605
|
layout: (g.layout ?? "column"),
|
|
@@ -3656,6 +3677,8 @@ function buildSceneGraph(ast) {
|
|
|
3656
3677
|
to: e.to,
|
|
3657
3678
|
connector: e.connector,
|
|
3658
3679
|
label: e.label,
|
|
3680
|
+
labelDx: e.labelDx,
|
|
3681
|
+
labelDy: e.labelDy,
|
|
3659
3682
|
fromAnchor: e.fromAnchor,
|
|
3660
3683
|
toAnchor: e.toAnchor,
|
|
3661
3684
|
dashed: e.dashed ?? false,
|
|
@@ -3997,10 +4020,25 @@ const textShape = {
|
|
|
3997
4020
|
},
|
|
3998
4021
|
};
|
|
3999
4022
|
|
|
4023
|
+
const MEDIA_LABEL_SHAPES = new Set(["icon", "image", "line"]);
|
|
4024
|
+
function usesBottomLabelStrip(shape) {
|
|
4025
|
+
return MEDIA_LABEL_SHAPES.has(shape);
|
|
4026
|
+
}
|
|
4027
|
+
function getBottomLabelStripHeight(node) {
|
|
4028
|
+
return usesBottomLabelStrip(node.shape) && node.label ? NODE.mediaLabelH : 0;
|
|
4029
|
+
}
|
|
4030
|
+
function getBottomLabelContentHeight(node) {
|
|
4031
|
+
return node.h - getBottomLabelStripHeight(node);
|
|
4032
|
+
}
|
|
4033
|
+
function getBottomLabelCenterY(node) {
|
|
4034
|
+
const stripH = getBottomLabelStripHeight(node);
|
|
4035
|
+
return stripH > 0 ? node.y + node.h - stripH / 2 : node.y + node.h / 2;
|
|
4036
|
+
}
|
|
4037
|
+
|
|
4000
4038
|
const iconShape = {
|
|
4001
4039
|
size(n, labelW) {
|
|
4002
4040
|
const iconBase = 48;
|
|
4003
|
-
const labelH = n
|
|
4041
|
+
const labelH = getBottomLabelStripHeight(n);
|
|
4004
4042
|
n.w = n.w || Math.max(iconBase, n.label ? labelW : 0);
|
|
4005
4043
|
n.h = n.h || (iconBase + labelH);
|
|
4006
4044
|
},
|
|
@@ -4013,8 +4051,7 @@ const iconShape = {
|
|
|
4013
4051
|
const iconColor = s.color
|
|
4014
4052
|
? encodeURIComponent(String(s.color))
|
|
4015
4053
|
: encodeURIComponent(String(palette.nodeStroke));
|
|
4016
|
-
const
|
|
4017
|
-
const iconAreaH = n.h - labelSpace;
|
|
4054
|
+
const iconAreaH = getBottomLabelContentHeight(n);
|
|
4018
4055
|
const iconSize = Math.min(n.w, iconAreaH) - 4;
|
|
4019
4056
|
const iconUrl = `https://api.iconify.design/${prefix}/${name}.svg?color=${iconColor}&width=${iconSize}&height=${iconSize}`;
|
|
4020
4057
|
const img = document.createElementNS(SVG_NS, "image");
|
|
@@ -4058,8 +4095,7 @@ const iconShape = {
|
|
|
4058
4095
|
const iconColor = s.color
|
|
4059
4096
|
? encodeURIComponent(String(s.color))
|
|
4060
4097
|
: encodeURIComponent(String(palette.nodeStroke));
|
|
4061
|
-
const
|
|
4062
|
-
const iconAreaH = n.h - iconLabelSpace;
|
|
4098
|
+
const iconAreaH = getBottomLabelContentHeight(n);
|
|
4063
4099
|
const iconSize = Math.min(n.w, iconAreaH) - 4;
|
|
4064
4100
|
const iconUrl = `https://api.iconify.design/${prefix}/${name}.svg?color=${iconColor}&width=${iconSize}&height=${iconSize}`;
|
|
4065
4101
|
const img = new Image();
|
|
@@ -4102,21 +4138,21 @@ const imageShape = {
|
|
|
4102
4138
|
const w = n.w || Math.max(MIN_W, Math.min(MAX_W, labelW));
|
|
4103
4139
|
n.w = w;
|
|
4104
4140
|
if (!n.h) {
|
|
4141
|
+
const labelH = getBottomLabelStripHeight(n);
|
|
4105
4142
|
if (labelW > w) {
|
|
4106
4143
|
const fontSize = Number(n.style?.fontSize ?? 14);
|
|
4107
4144
|
const lines = Math.ceil(labelW / (w - 16));
|
|
4108
|
-
n.h = Math.max(52, lines * fontSize * 1.5 +
|
|
4145
|
+
n.h = Math.max(52, lines * fontSize * 1.5 + labelH);
|
|
4109
4146
|
}
|
|
4110
4147
|
else {
|
|
4111
|
-
n.h = 52;
|
|
4148
|
+
n.h = 52 + labelH;
|
|
4112
4149
|
}
|
|
4113
4150
|
}
|
|
4114
4151
|
},
|
|
4115
4152
|
renderSVG(rc, n, _palette, opts) {
|
|
4116
4153
|
const s = n.style ?? {};
|
|
4117
4154
|
if (n.imageUrl) {
|
|
4118
|
-
const
|
|
4119
|
-
const imgAreaH = n.h - imgLabelSpace;
|
|
4155
|
+
const imgAreaH = getBottomLabelContentHeight(n);
|
|
4120
4156
|
const img = document.createElementNS(SVG_NS, "image");
|
|
4121
4157
|
img.setAttribute("href", n.imageUrl);
|
|
4122
4158
|
img.setAttribute("x", String(n.x + 1));
|
|
@@ -4148,8 +4184,7 @@ const imageShape = {
|
|
|
4148
4184
|
renderCanvas(rc, ctx, n, _palette, opts) {
|
|
4149
4185
|
const s = n.style ?? {};
|
|
4150
4186
|
if (n.imageUrl) {
|
|
4151
|
-
const
|
|
4152
|
-
const imgAreaH = n.h - imgLblSpace;
|
|
4187
|
+
const imgAreaH = getBottomLabelContentHeight(n);
|
|
4153
4188
|
const img = new Image();
|
|
4154
4189
|
img.crossOrigin = "anonymous";
|
|
4155
4190
|
img.onload = () => {
|
|
@@ -4326,17 +4361,17 @@ const noteShape = {
|
|
|
4326
4361
|
|
|
4327
4362
|
const lineShape = {
|
|
4328
4363
|
size(n, labelW) {
|
|
4329
|
-
const labelH = n
|
|
4364
|
+
const labelH = getBottomLabelStripHeight(n);
|
|
4330
4365
|
n.w = n.width ?? Math.max(MIN_W, labelW + 20);
|
|
4331
4366
|
n.h = n.height ?? (6 + labelH);
|
|
4332
4367
|
},
|
|
4333
4368
|
renderSVG(rc, n, _palette, opts) {
|
|
4334
|
-
const labelH = n
|
|
4369
|
+
const labelH = getBottomLabelStripHeight(n);
|
|
4335
4370
|
const lineY = n.y + (n.h - labelH) / 2;
|
|
4336
4371
|
return [rc.line(n.x, lineY, n.x + n.w, lineY, opts)];
|
|
4337
4372
|
},
|
|
4338
4373
|
renderCanvas(rc, _ctx, n, _palette, opts) {
|
|
4339
|
-
const labelH = n
|
|
4374
|
+
const labelH = getBottomLabelStripHeight(n);
|
|
4340
4375
|
const lineY = n.y + (n.h - labelH) / 2;
|
|
4341
4376
|
rc.line(n.x, lineY, n.x + n.w, lineY, opts);
|
|
4342
4377
|
},
|
|
@@ -5582,18 +5617,18 @@ function renderRoughChartSVG(rc, c, palette, isDark) {
|
|
|
5582
5617
|
stroke: bgStroke, strokeWidth: Number(s.strokeWidth ?? 1.2),
|
|
5583
5618
|
...(s.strokeDash ? { strokeLineDash: s.strokeDash } : {}),
|
|
5584
5619
|
}));
|
|
5620
|
+
const { px, py, pw, ph, titleH, cx, cy } = chartLayout(c);
|
|
5585
5621
|
// Title
|
|
5586
5622
|
if (c.label) {
|
|
5587
|
-
cg.appendChild(mkT(c.label, c.x + c.w / 2, c.y +
|
|
5623
|
+
cg.appendChild(mkT(c.label, c.x + c.w / 2, c.y + titleH / 2 + 2, cFontSize, cFontWeight, lc, 'middle', cFont));
|
|
5588
5624
|
}
|
|
5589
|
-
const { px, py, pw, ph, cx, cy } = chartLayout(c);
|
|
5590
5625
|
// ── Pie / Donut ──────────────────────────────────────────
|
|
5591
5626
|
if (c.chartType === 'pie' || c.chartType === 'donut') {
|
|
5592
5627
|
const { segments, total } = parsePie(c.data);
|
|
5593
|
-
const r = Math.min(c.w * 0.38, (c.h -
|
|
5628
|
+
const r = Math.min(c.w * 0.38, (c.h - titleH) * 0.44);
|
|
5594
5629
|
const ir = c.chartType === 'donut' ? r * 0.48 : 0;
|
|
5595
5630
|
const legendX = c.x + 8;
|
|
5596
|
-
const legendY = c.y +
|
|
5631
|
+
const legendY = c.y + titleH + 4;
|
|
5597
5632
|
let angle = -Math.PI / 2;
|
|
5598
5633
|
for (const seg of segments) {
|
|
5599
5634
|
const sweep = (seg.value / total) * Math.PI * 2;
|
|
@@ -5631,7 +5666,7 @@ function renderRoughChartSVG(rc, c, palette, isDark) {
|
|
|
5631
5666
|
strokeWidth: 1.2,
|
|
5632
5667
|
}));
|
|
5633
5668
|
});
|
|
5634
|
-
legend(cg, pts.map(p => p.label), CHART_COLORS, c.x + 8, c.y +
|
|
5669
|
+
legend(cg, pts.map(p => p.label), CHART_COLORS, c.x + 8, c.y + titleH + 4, lc, cFont);
|
|
5635
5670
|
return cg;
|
|
5636
5671
|
}
|
|
5637
5672
|
// ── Bar / Line / Area ─────────────────────────────────────
|
|
@@ -8377,9 +8412,10 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
8377
8412
|
}));
|
|
8378
8413
|
// ── Group label typography ──────────────────────────
|
|
8379
8414
|
const gTypo = resolveTypography(gs, { fontSize: GROUP_LABEL.fontSize, fontWeight: GROUP_LABEL.fontWeight, textAlign: "left", padding: GROUP_LABEL.padding }, diagramFont, palette.groupLabel);
|
|
8380
|
-
const gTextX = computeTextX(gTypo, g.x, g.w);
|
|
8415
|
+
const gTextX = computeTextX(gTypo, g.x, g.w) + (g.labelDx ?? 0);
|
|
8416
|
+
const gTextY = g.y + gTypo.padding + (g.labelDy ?? 0);
|
|
8381
8417
|
if (g.label) {
|
|
8382
|
-
gg.appendChild(mkText(g.label, gTextX,
|
|
8418
|
+
gg.appendChild(mkText(g.label, gTextX, gTextY, gTypo.fontSize, gTypo.fontWeight, gTypo.textColor, gTypo.textAnchor, gTypo.font, gTypo.letterSpacing));
|
|
8383
8419
|
}
|
|
8384
8420
|
GL.appendChild(gg);
|
|
8385
8421
|
}
|
|
@@ -8431,8 +8467,8 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
8431
8467
|
eg.appendChild(startHead);
|
|
8432
8468
|
}
|
|
8433
8469
|
if (e.label) {
|
|
8434
|
-
const mx = (x1 + x2) / 2 - ny * EDGE.labelOffset;
|
|
8435
|
-
const my = (y1 + y2) / 2 + nx * EDGE.labelOffset;
|
|
8470
|
+
const mx = (x1 + x2) / 2 - ny * EDGE.labelOffset + (e.labelDx ?? 0);
|
|
8471
|
+
const my = (y1 + y2) / 2 + nx * EDGE.labelOffset + (e.labelDy ?? 0);
|
|
8436
8472
|
const tw = Math.max(e.label.length * 7 + 12, 36);
|
|
8437
8473
|
const bg = se("rect");
|
|
8438
8474
|
bg.setAttribute("x", String(mx - tw / 2));
|
|
@@ -8500,7 +8536,7 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
8500
8536
|
// ── Node / text typography ─────────────────────────
|
|
8501
8537
|
const isText = n.shape === "text";
|
|
8502
8538
|
const isNote = n.shape === "note";
|
|
8503
|
-
const
|
|
8539
|
+
const usesBottomStrip = usesBottomLabelStrip(n.shape);
|
|
8504
8540
|
const typo = resolveTypography(n.style, {
|
|
8505
8541
|
fontSize: isText ? 13 : isNote ? 12 : 14,
|
|
8506
8542
|
fontWeight: isText || isNote ? 400 : 500,
|
|
@@ -8518,20 +8554,22 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
8518
8554
|
: n.x + typo.padding)
|
|
8519
8555
|
: computeTextX(typo, n.x, n.w);
|
|
8520
8556
|
const fontStr = buildFontStr(typo.fontSize, typo.fontWeight, typo.font);
|
|
8521
|
-
const shouldWrap = !
|
|
8557
|
+
const shouldWrap = !usesBottomStrip && !n.label.includes('\n');
|
|
8522
8558
|
const innerW = shapeInnerTextWidth(n.shape, n.w, typo.padding);
|
|
8523
8559
|
const lines = shouldWrap
|
|
8524
8560
|
? wrapText(n.label, innerW, typo.fontSize, fontStr)
|
|
8525
8561
|
: n.label.split('\n');
|
|
8526
|
-
const textCY =
|
|
8527
|
-
? n
|
|
8562
|
+
const textCY = usesBottomStrip
|
|
8563
|
+
? getBottomLabelCenterY(n)
|
|
8528
8564
|
: isNote
|
|
8529
8565
|
? computeTextCY(typo, n.y, n.h, lines.length, FOLD + typo.padding)
|
|
8530
8566
|
: computeTextCY(typo, n.y, n.h, lines.length);
|
|
8567
|
+
const labelX = textX + (n.labelDx ?? 0);
|
|
8568
|
+
const labelY = textCY + (n.labelDy ?? 0);
|
|
8531
8569
|
if (n.label) {
|
|
8532
8570
|
ng.appendChild(lines.length > 1
|
|
8533
|
-
? mkMultilineText(lines,
|
|
8534
|
-
: mkText(n.label,
|
|
8571
|
+
? mkMultilineText(lines, labelX, labelY, typo.fontSize, typo.fontWeight, typo.textColor, typo.textAnchor, typo.lineHeight, typo.font, typo.letterSpacing)
|
|
8572
|
+
: mkText(n.label, labelX, labelY, typo.fontSize, typo.fontWeight, typo.textColor, typo.textAnchor, typo.font, typo.letterSpacing));
|
|
8535
8573
|
}
|
|
8536
8574
|
if (options.interactive) {
|
|
8537
8575
|
ng.style.cursor = "pointer";
|
|
@@ -8845,6 +8883,7 @@ function drawRoughChartCanvas(rc, ctx, c, pal, R) {
|
|
|
8845
8883
|
strokeWidth: Number(s.strokeWidth ?? 1.2),
|
|
8846
8884
|
...(s.strokeDash ? { strokeLineDash: s.strokeDash } : {}),
|
|
8847
8885
|
});
|
|
8886
|
+
const { px, py, pw, ph, titleH, cx, cy } = chartLayout(c);
|
|
8848
8887
|
// Title
|
|
8849
8888
|
if (c.label) {
|
|
8850
8889
|
ctx.save();
|
|
@@ -8852,17 +8891,16 @@ function drawRoughChartCanvas(rc, ctx, c, pal, R) {
|
|
|
8852
8891
|
ctx.fillStyle = lc;
|
|
8853
8892
|
ctx.textAlign = 'center';
|
|
8854
8893
|
ctx.textBaseline = 'middle';
|
|
8855
|
-
ctx.fillText(c.label, c.x + c.w / 2, c.y +
|
|
8894
|
+
ctx.fillText(c.label, c.x + c.w / 2, c.y + titleH / 2 + 2);
|
|
8856
8895
|
ctx.restore();
|
|
8857
8896
|
}
|
|
8858
|
-
const { px, py, pw, ph, cx, cy } = chartLayout(c);
|
|
8859
8897
|
// ── Pie / Donut ──────────────────────────────────────────
|
|
8860
8898
|
if (c.chartType === 'pie' || c.chartType === 'donut') {
|
|
8861
8899
|
const { segments, total } = parsePie(c.data);
|
|
8862
|
-
const r = Math.min(c.w * 0.38, (c.h -
|
|
8900
|
+
const r = Math.min(c.w * 0.38, (c.h - titleH) * 0.44);
|
|
8863
8901
|
const ir = c.chartType === 'donut' ? r * 0.48 : 0;
|
|
8864
8902
|
const legendX = c.x + 8;
|
|
8865
|
-
const legendY = c.y +
|
|
8903
|
+
const legendY = c.y + titleH + 4;
|
|
8866
8904
|
let angle = -Math.PI / 2;
|
|
8867
8905
|
segments.forEach((seg, i) => {
|
|
8868
8906
|
const sweep = (seg.value / total) * Math.PI * 2;
|
|
@@ -8890,7 +8928,7 @@ function drawRoughChartCanvas(rc, ctx, c, pal, R) {
|
|
|
8890
8928
|
strokeWidth: 1.2,
|
|
8891
8929
|
});
|
|
8892
8930
|
});
|
|
8893
|
-
drawLegend(ctx, pts.map(p => p.label), CHART_COLORS, c.x + 8, c.y +
|
|
8931
|
+
drawLegend(ctx, pts.map(p => p.label), CHART_COLORS, c.x + 8, c.y + titleH + 4, lc, cFont);
|
|
8894
8932
|
ctx.globalAlpha = 1;
|
|
8895
8933
|
return;
|
|
8896
8934
|
}
|
|
@@ -9125,8 +9163,9 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
9125
9163
|
});
|
|
9126
9164
|
if (g.label) {
|
|
9127
9165
|
const gTypo = resolveTypography(gs, { fontSize: GROUP_LABEL.fontSize, fontWeight: GROUP_LABEL.fontWeight, textAlign: "left", padding: GROUP_LABEL.padding }, diagramFont, palette.groupLabel);
|
|
9128
|
-
const gTextX = computeTextX(gTypo, g.x, g.w);
|
|
9129
|
-
|
|
9166
|
+
const gTextX = computeTextX(gTypo, g.x, g.w) + (g.labelDx ?? 0);
|
|
9167
|
+
const gTextY = g.y + gTypo.padding + 2 + (g.labelDy ?? 0);
|
|
9168
|
+
drawText(ctx, g.label, gTextX, gTextY, gTypo.fontSize, gTypo.fontWeight, gTypo.textColor, gTypo.textAlign, gTypo.font, gTypo.letterSpacing);
|
|
9130
9169
|
}
|
|
9131
9170
|
if (gs.opacity != null)
|
|
9132
9171
|
ctx.globalAlpha = 1;
|
|
@@ -9164,8 +9203,8 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
9164
9203
|
if (arrowAt === 'start' || arrowAt === 'both')
|
|
9165
9204
|
drawArrowHead(rc, x1, y1, Math.atan2(y1 - y2, x1 - x2), ecol, hashStr$3(e.from + 'back'));
|
|
9166
9205
|
if (e.label) {
|
|
9167
|
-
const mx = (x1 + x2) / 2 - ny * EDGE.labelOffset;
|
|
9168
|
-
const my = (y1 + y2) / 2 + nx * EDGE.labelOffset;
|
|
9206
|
+
const mx = (x1 + x2) / 2 - ny * EDGE.labelOffset + (e.labelDx ?? 0);
|
|
9207
|
+
const my = (y1 + y2) / 2 + nx * EDGE.labelOffset + (e.labelDy ?? 0);
|
|
9169
9208
|
// ── Edge label: font, font-size, letter-spacing ──
|
|
9170
9209
|
// always center-anchored (single line)
|
|
9171
9210
|
const eFontSize = Number(e.style?.fontSize ?? EDGE.labelFontSize);
|
|
@@ -9205,7 +9244,7 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
9205
9244
|
// ── Node / text typography ─────────────────────────
|
|
9206
9245
|
const isText = n.shape === 'text';
|
|
9207
9246
|
const isNote = n.shape === 'note';
|
|
9208
|
-
const
|
|
9247
|
+
const usesBottomStrip = usesBottomLabelStrip(n.shape);
|
|
9209
9248
|
const typo = resolveTypography(n.style, {
|
|
9210
9249
|
fontSize: isText ? 13 : isNote ? 12 : 14,
|
|
9211
9250
|
fontWeight: isText || isNote ? 400 : 500,
|
|
@@ -9223,23 +9262,25 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
9223
9262
|
: n.x + typo.padding)
|
|
9224
9263
|
: computeTextX(typo, n.x, n.w);
|
|
9225
9264
|
const fontStr = buildFontStr(typo.fontSize, typo.fontWeight, typo.font);
|
|
9226
|
-
const shouldWrap = !
|
|
9265
|
+
const shouldWrap = !usesBottomStrip && !n.label.includes('\n');
|
|
9227
9266
|
const innerW = shapeInnerTextWidth(n.shape, n.w, typo.padding);
|
|
9228
9267
|
const rawLines = n.label.split('\n');
|
|
9229
9268
|
const lines = shouldWrap && rawLines.length === 1
|
|
9230
9269
|
? wrapText(n.label, innerW, typo.fontSize, fontStr)
|
|
9231
9270
|
: rawLines;
|
|
9232
|
-
const textCY =
|
|
9233
|
-
? n
|
|
9271
|
+
const textCY = usesBottomStrip
|
|
9272
|
+
? getBottomLabelCenterY(n)
|
|
9234
9273
|
: isNote
|
|
9235
9274
|
? computeTextCY(typo, n.y, n.h, lines.length, FOLD + typo.padding)
|
|
9236
9275
|
: computeTextCY(typo, n.y, n.h, lines.length);
|
|
9276
|
+
const labelX = textX + (n.labelDx ?? 0);
|
|
9277
|
+
const labelY = textCY + (n.labelDy ?? 0);
|
|
9237
9278
|
if (n.label) {
|
|
9238
9279
|
if (lines.length > 1) {
|
|
9239
|
-
drawMultilineText(ctx, lines,
|
|
9280
|
+
drawMultilineText(ctx, lines, labelX, labelY, typo.fontSize, typo.fontWeight, typo.textColor, typo.textAlign, typo.lineHeight, typo.font, typo.letterSpacing);
|
|
9240
9281
|
}
|
|
9241
9282
|
else {
|
|
9242
|
-
drawText(ctx, lines[0] ?? '',
|
|
9283
|
+
drawText(ctx, lines[0] ?? '', labelX, labelY, typo.fontSize, typo.fontWeight, typo.textColor, typo.textAlign, typo.font, typo.letterSpacing);
|
|
9243
9284
|
}
|
|
9244
9285
|
}
|
|
9245
9286
|
if (hasTx)
|
|
@@ -9577,7 +9618,7 @@ function buildNodeGuidePath(el) {
|
|
|
9577
9618
|
[x + 1, y + h - 1],
|
|
9578
9619
|
]);
|
|
9579
9620
|
case "line": {
|
|
9580
|
-
const labelH = el.querySelector("text") ?
|
|
9621
|
+
const labelH = el.querySelector("text") ? NODE.mediaLabelH : 0;
|
|
9581
9622
|
const lineY = y + (h - labelH) / 2;
|
|
9582
9623
|
return `M ${x} ${lineY} L ${x + w} ${lineY}`;
|
|
9583
9624
|
}
|
|
@@ -9948,8 +9989,12 @@ class AnimationController {
|
|
|
9948
9989
|
this._rc = _rc;
|
|
9949
9990
|
this._config = _config;
|
|
9950
9991
|
this._step = -1;
|
|
9992
|
+
this._isPlaying = false;
|
|
9993
|
+
this._playRunId = 0;
|
|
9951
9994
|
this._pendingStepTimers = new Set();
|
|
9952
9995
|
this._pendingNarrationTimers = new Set();
|
|
9996
|
+
this._playbackDelayTimerId = null;
|
|
9997
|
+
this._resolvePlaybackDelay = null;
|
|
9953
9998
|
this._transforms = new Map();
|
|
9954
9999
|
this._listeners = [];
|
|
9955
10000
|
// ── Narration caption ──
|
|
@@ -9965,6 +10010,7 @@ class AnimationController {
|
|
|
9965
10010
|
// ── TTS ──
|
|
9966
10011
|
this._tts = false;
|
|
9967
10012
|
this._speechDone = null;
|
|
10013
|
+
this._resolveSpeechDone = null;
|
|
9968
10014
|
this.drawTargetEdges = getDrawTargetEdgeIds(steps);
|
|
9969
10015
|
this.drawTargetNodes = getDrawTargetNodeIds(steps);
|
|
9970
10016
|
// Groups: non-edge draw steps whose target has a #group-{id} element in the SVG.
|
|
@@ -10188,6 +10234,9 @@ class AnimationController {
|
|
|
10188
10234
|
get atEnd() {
|
|
10189
10235
|
return this._step === this.steps.length - 1;
|
|
10190
10236
|
}
|
|
10237
|
+
get isPlaying() {
|
|
10238
|
+
return this._isPlaying;
|
|
10239
|
+
}
|
|
10191
10240
|
on(listener) {
|
|
10192
10241
|
this._listeners.push(listener);
|
|
10193
10242
|
return () => {
|
|
@@ -10205,12 +10254,14 @@ class AnimationController {
|
|
|
10205
10254
|
l(e);
|
|
10206
10255
|
}
|
|
10207
10256
|
reset() {
|
|
10257
|
+
this.stop();
|
|
10208
10258
|
this._step = -1;
|
|
10209
10259
|
this._clearAll();
|
|
10210
10260
|
this.emit("animation-reset");
|
|
10211
10261
|
}
|
|
10212
10262
|
/** Remove caption and annotation layer from the DOM */
|
|
10213
10263
|
destroy() {
|
|
10264
|
+
this.stop();
|
|
10214
10265
|
this._clearAll();
|
|
10215
10266
|
this._captionEl?.remove();
|
|
10216
10267
|
this._captionEl = null;
|
|
@@ -10221,16 +10272,11 @@ class AnimationController {
|
|
|
10221
10272
|
this._pointerEl = null;
|
|
10222
10273
|
}
|
|
10223
10274
|
next() {
|
|
10224
|
-
|
|
10225
|
-
|
|
10226
|
-
this._step++;
|
|
10227
|
-
this._applyStep(this._step, false);
|
|
10228
|
-
this.emit("step-change");
|
|
10229
|
-
if (!this.canNext)
|
|
10230
|
-
this.emit("animation-end");
|
|
10231
|
-
return true;
|
|
10275
|
+
this.stop();
|
|
10276
|
+
return this._advanceNext();
|
|
10232
10277
|
}
|
|
10233
10278
|
prev() {
|
|
10279
|
+
this.stop();
|
|
10234
10280
|
if (!this.canPrev)
|
|
10235
10281
|
return false;
|
|
10236
10282
|
this._step--;
|
|
@@ -10241,18 +10287,33 @@ class AnimationController {
|
|
|
10241
10287
|
return true;
|
|
10242
10288
|
}
|
|
10243
10289
|
async play(msPerStep = 900) {
|
|
10290
|
+
if (this._isPlaying || !this.canNext)
|
|
10291
|
+
return;
|
|
10292
|
+
const runId = ++this._playRunId;
|
|
10293
|
+
this._isPlaying = true;
|
|
10244
10294
|
this.emit("animation-start");
|
|
10245
|
-
|
|
10246
|
-
|
|
10247
|
-
|
|
10248
|
-
|
|
10249
|
-
|
|
10250
|
-
|
|
10251
|
-
|
|
10252
|
-
|
|
10295
|
+
try {
|
|
10296
|
+
while (this.canNext && this._playRunId === runId) {
|
|
10297
|
+
const nextStep = this.steps[this._step + 1];
|
|
10298
|
+
if (!this._advanceNext())
|
|
10299
|
+
break;
|
|
10300
|
+
if (this._playRunId !== runId)
|
|
10301
|
+
break;
|
|
10302
|
+
await Promise.all([
|
|
10303
|
+
this._waitForPlaybackDelay(this._playbackWaitMs(nextStep, msPerStep)),
|
|
10304
|
+
this._speechDone ?? Promise.resolve(),
|
|
10305
|
+
]);
|
|
10306
|
+
}
|
|
10307
|
+
}
|
|
10308
|
+
finally {
|
|
10309
|
+
if (this._playRunId === runId) {
|
|
10310
|
+
this._isPlaying = false;
|
|
10311
|
+
this._cancelPlaybackDelay();
|
|
10312
|
+
}
|
|
10253
10313
|
}
|
|
10254
10314
|
}
|
|
10255
10315
|
goTo(index) {
|
|
10316
|
+
this.stop();
|
|
10256
10317
|
index = Math.max(-1, Math.min(this.steps.length - 1, index));
|
|
10257
10318
|
if (index === this._step)
|
|
10258
10319
|
return;
|
|
@@ -10266,6 +10327,30 @@ class AnimationController {
|
|
|
10266
10327
|
}
|
|
10267
10328
|
this.emit("step-change");
|
|
10268
10329
|
}
|
|
10330
|
+
stop() {
|
|
10331
|
+
if (!this._isPlaying && !this._resolvePlaybackDelay) {
|
|
10332
|
+
this._clearPendingStepTimers();
|
|
10333
|
+
this._cancelNarrationTyping();
|
|
10334
|
+
this._cancelSpeech();
|
|
10335
|
+
return;
|
|
10336
|
+
}
|
|
10337
|
+
this._isPlaying = false;
|
|
10338
|
+
this._playRunId += 1;
|
|
10339
|
+
this._cancelPlaybackDelay();
|
|
10340
|
+
this._clearPendingStepTimers();
|
|
10341
|
+
this._cancelNarrationTyping();
|
|
10342
|
+
this._cancelSpeech();
|
|
10343
|
+
}
|
|
10344
|
+
_advanceNext() {
|
|
10345
|
+
if (!this.canNext)
|
|
10346
|
+
return false;
|
|
10347
|
+
this._step++;
|
|
10348
|
+
this._applyStep(this._step, false);
|
|
10349
|
+
this.emit("step-change");
|
|
10350
|
+
if (!this.canNext)
|
|
10351
|
+
this.emit("animation-end");
|
|
10352
|
+
return true;
|
|
10353
|
+
}
|
|
10269
10354
|
_clearTimerBucket(bucket) {
|
|
10270
10355
|
bucket.forEach((id) => window.clearTimeout(id));
|
|
10271
10356
|
bucket.clear();
|
|
@@ -10291,6 +10376,34 @@ class AnimationController {
|
|
|
10291
10376
|
_scheduleStep(fn, delayMs) {
|
|
10292
10377
|
this._scheduleTimer(fn, delayMs, this._pendingStepTimers);
|
|
10293
10378
|
}
|
|
10379
|
+
_waitForPlaybackDelay(delayMs) {
|
|
10380
|
+
this._cancelPlaybackDelay();
|
|
10381
|
+
return new Promise((resolve) => {
|
|
10382
|
+
let settled = false;
|
|
10383
|
+
const finish = () => {
|
|
10384
|
+
if (settled)
|
|
10385
|
+
return;
|
|
10386
|
+
settled = true;
|
|
10387
|
+
if (this._playbackDelayTimerId !== null) {
|
|
10388
|
+
window.clearTimeout(this._playbackDelayTimerId);
|
|
10389
|
+
this._playbackDelayTimerId = null;
|
|
10390
|
+
}
|
|
10391
|
+
if (this._resolvePlaybackDelay === finish) {
|
|
10392
|
+
this._resolvePlaybackDelay = null;
|
|
10393
|
+
}
|
|
10394
|
+
resolve();
|
|
10395
|
+
};
|
|
10396
|
+
this._resolvePlaybackDelay = finish;
|
|
10397
|
+
if (delayMs <= 0) {
|
|
10398
|
+
finish();
|
|
10399
|
+
return;
|
|
10400
|
+
}
|
|
10401
|
+
this._playbackDelayTimerId = window.setTimeout(finish, delayMs);
|
|
10402
|
+
});
|
|
10403
|
+
}
|
|
10404
|
+
_cancelPlaybackDelay() {
|
|
10405
|
+
this._resolvePlaybackDelay?.();
|
|
10406
|
+
}
|
|
10294
10407
|
_stepWaitMs(step, fallbackMs) {
|
|
10295
10408
|
const delay = Math.max(0, step.delay ?? 0);
|
|
10296
10409
|
const duration = Math.max(0, step.duration ?? 0);
|
|
@@ -10332,6 +10445,7 @@ class AnimationController {
|
|
|
10332
10445
|
return this._stepWaitMs(step, fallbackMs);
|
|
10333
10446
|
}
|
|
10334
10447
|
_clearAll() {
|
|
10448
|
+
this._cancelPlaybackDelay();
|
|
10335
10449
|
this._clearPendingStepTimers();
|
|
10336
10450
|
this._cancelNarrationTyping();
|
|
10337
10451
|
this._cancelSpeech();
|
|
@@ -10943,16 +11057,30 @@ class AnimationController {
|
|
|
10943
11057
|
utter.rate = 0.95;
|
|
10944
11058
|
utter.pitch = 1;
|
|
10945
11059
|
utter.lang = "en-US";
|
|
10946
|
-
// Track when speech actually finishes
|
|
11060
|
+
// Track when speech actually finishes so play() can block until the utterance ends.
|
|
10947
11061
|
this._speechDone = new Promise((resolve) => {
|
|
10948
|
-
|
|
10949
|
-
|
|
11062
|
+
let settled = false;
|
|
11063
|
+
const finish = () => {
|
|
11064
|
+
if (settled)
|
|
11065
|
+
return;
|
|
11066
|
+
settled = true;
|
|
11067
|
+
if (this._resolveSpeechDone === finish) {
|
|
11068
|
+
this._resolveSpeechDone = null;
|
|
11069
|
+
this._speechDone = null;
|
|
11070
|
+
}
|
|
11071
|
+
resolve();
|
|
11072
|
+
};
|
|
11073
|
+
this._resolveSpeechDone = finish;
|
|
11074
|
+
utter.onend = finish;
|
|
11075
|
+
utter.onerror = finish;
|
|
10950
11076
|
});
|
|
10951
11077
|
speechSynthesis.speak(utter);
|
|
10952
11078
|
}
|
|
10953
11079
|
_cancelSpeech() {
|
|
10954
11080
|
if (typeof speechSynthesis !== "undefined")
|
|
10955
11081
|
speechSynthesis.cancel();
|
|
11082
|
+
this._resolveSpeechDone?.();
|
|
11083
|
+
this._resolveSpeechDone = null;
|
|
10956
11084
|
this._speechDone = null;
|
|
10957
11085
|
}
|
|
10958
11086
|
/** Pre-warm the speech engine with a silent utterance to eliminate cold-start delay */
|
|
@@ -11761,7 +11889,13 @@ class SketchmarkCanvas {
|
|
|
11761
11889
|
this.resetButton.addEventListener("click", () => this.resetAnimation());
|
|
11762
11890
|
this.prevButton.addEventListener("click", () => this.prevStep());
|
|
11763
11891
|
this.nextButton.addEventListener("click", () => this.nextStep());
|
|
11764
|
-
this.playButton.addEventListener("click", () =>
|
|
11892
|
+
this.playButton.addEventListener("click", () => {
|
|
11893
|
+
if (this.playInFlight) {
|
|
11894
|
+
this.stopPlayback();
|
|
11895
|
+
return;
|
|
11896
|
+
}
|
|
11897
|
+
void this.play();
|
|
11898
|
+
});
|
|
11765
11899
|
this.captionButton.addEventListener("click", () => this.setCaptionVisible(!this.showCaption));
|
|
11766
11900
|
this.ttsButton.addEventListener("click", () => this.setTtsEnabled(!this.getTtsEnabled()));
|
|
11767
11901
|
this.viewport.addEventListener("pointerdown", this.onPointerDown);
|
|
@@ -11825,6 +11959,7 @@ class SketchmarkCanvas {
|
|
|
11825
11959
|
this.dsl = normalizeNewlines(nextDsl);
|
|
11826
11960
|
this.clearError();
|
|
11827
11961
|
this.mirroredEditor?.clearError();
|
|
11962
|
+
this.playInFlight = false;
|
|
11828
11963
|
this.animUnsub?.();
|
|
11829
11964
|
this.animUnsub = null;
|
|
11830
11965
|
this.instance?.anim?.destroy();
|
|
@@ -11895,9 +12030,16 @@ class SketchmarkCanvas {
|
|
|
11895
12030
|
this.syncAnimationUi();
|
|
11896
12031
|
}
|
|
11897
12032
|
}
|
|
12033
|
+
stopPlayback() {
|
|
12034
|
+
this.playInFlight = false;
|
|
12035
|
+
if (this.renderer === "svg")
|
|
12036
|
+
this.instance?.anim.stop();
|
|
12037
|
+
this.syncAnimationUi();
|
|
12038
|
+
}
|
|
11898
12039
|
nextStep() {
|
|
11899
12040
|
if (!this.instance || this.renderer !== "svg")
|
|
11900
12041
|
return;
|
|
12042
|
+
this.playInFlight = false;
|
|
11901
12043
|
this.instance.anim.next();
|
|
11902
12044
|
this.syncAnimationUi();
|
|
11903
12045
|
this.focusCurrentStep();
|
|
@@ -11905,6 +12047,7 @@ class SketchmarkCanvas {
|
|
|
11905
12047
|
prevStep() {
|
|
11906
12048
|
if (!this.instance || this.renderer !== "svg")
|
|
11907
12049
|
return;
|
|
12050
|
+
this.playInFlight = false;
|
|
11908
12051
|
this.instance.anim.prev();
|
|
11909
12052
|
this.syncAnimationUi();
|
|
11910
12053
|
this.focusCurrentStep();
|
|
@@ -11912,6 +12055,7 @@ class SketchmarkCanvas {
|
|
|
11912
12055
|
resetAnimation() {
|
|
11913
12056
|
if (!this.instance || this.renderer !== "svg")
|
|
11914
12057
|
return;
|
|
12058
|
+
this.playInFlight = false;
|
|
11915
12059
|
this.instance.anim.reset();
|
|
11916
12060
|
this.syncAnimationUi();
|
|
11917
12061
|
}
|
|
@@ -11940,6 +12084,7 @@ class SketchmarkCanvas {
|
|
|
11940
12084
|
this.render();
|
|
11941
12085
|
}
|
|
11942
12086
|
destroy() {
|
|
12087
|
+
this.playInFlight = false;
|
|
11943
12088
|
this.editorCleanup?.();
|
|
11944
12089
|
this.animUnsub?.();
|
|
11945
12090
|
this.instance?.anim?.destroy();
|
|
@@ -12014,6 +12159,9 @@ class SketchmarkCanvas {
|
|
|
12014
12159
|
this.prevButton.disabled = true;
|
|
12015
12160
|
this.nextButton.disabled = true;
|
|
12016
12161
|
this.resetButton.disabled = true;
|
|
12162
|
+
this.playButton.textContent = "Play";
|
|
12163
|
+
this.playButton.classList.remove("is-active");
|
|
12164
|
+
this.playButton.setAttribute("aria-pressed", "false");
|
|
12017
12165
|
this.playButton.disabled = true;
|
|
12018
12166
|
this.syncToggleUi();
|
|
12019
12167
|
return;
|
|
@@ -12023,7 +12171,10 @@ class SketchmarkCanvas {
|
|
|
12023
12171
|
this.prevButton.disabled = !anim.canPrev;
|
|
12024
12172
|
this.nextButton.disabled = !anim.canNext;
|
|
12025
12173
|
this.resetButton.disabled = false;
|
|
12026
|
-
this.playButton.
|
|
12174
|
+
this.playButton.textContent = this.playInFlight ? "Stop" : "Play";
|
|
12175
|
+
this.playButton.classList.toggle("is-active", this.playInFlight);
|
|
12176
|
+
this.playButton.setAttribute("aria-pressed", this.playInFlight ? "true" : "false");
|
|
12177
|
+
this.playButton.disabled = this.playInFlight ? false : !anim.canNext;
|
|
12027
12178
|
this.syncToggleUi();
|
|
12028
12179
|
}
|
|
12029
12180
|
getStepTarget(stepItem) {
|
|
@@ -12929,6 +13080,10 @@ class SketchmarkEmbed {
|
|
|
12929
13080
|
this.btnPrev.addEventListener("click", () => this.prevStep());
|
|
12930
13081
|
this.btnNext.addEventListener("click", () => this.nextStep());
|
|
12931
13082
|
this.btnPlay.addEventListener("click", () => {
|
|
13083
|
+
if (this.playInFlight) {
|
|
13084
|
+
this.stopPlayback();
|
|
13085
|
+
return;
|
|
13086
|
+
}
|
|
12932
13087
|
void this.play();
|
|
12933
13088
|
});
|
|
12934
13089
|
this.btnCaption.addEventListener("click", () => this.setCaptionVisible(!this.showCaption));
|
|
@@ -12986,6 +13141,7 @@ class SketchmarkEmbed {
|
|
|
12986
13141
|
}
|
|
12987
13142
|
this.clearError();
|
|
12988
13143
|
this.stopMotion();
|
|
13144
|
+
this.playInFlight = false;
|
|
12989
13145
|
this.animUnsub?.();
|
|
12990
13146
|
this.animUnsub = null;
|
|
12991
13147
|
this.instance?.anim?.destroy();
|
|
@@ -13058,9 +13214,15 @@ class SketchmarkEmbed {
|
|
|
13058
13214
|
this.syncControls();
|
|
13059
13215
|
}
|
|
13060
13216
|
}
|
|
13217
|
+
stopPlayback() {
|
|
13218
|
+
this.playInFlight = false;
|
|
13219
|
+
this.instance?.anim.stop();
|
|
13220
|
+
this.syncControls();
|
|
13221
|
+
}
|
|
13061
13222
|
nextStep() {
|
|
13062
13223
|
if (!this.instance)
|
|
13063
13224
|
return;
|
|
13225
|
+
this.playInFlight = false;
|
|
13064
13226
|
this.instance.anim.next();
|
|
13065
13227
|
this.syncControls();
|
|
13066
13228
|
if (this.options.autoFocus !== false && this.options.autoFocusOnStep !== false) {
|
|
@@ -13070,6 +13232,7 @@ class SketchmarkEmbed {
|
|
|
13070
13232
|
prevStep() {
|
|
13071
13233
|
if (!this.instance)
|
|
13072
13234
|
return;
|
|
13235
|
+
this.playInFlight = false;
|
|
13073
13236
|
this.instance.anim.prev();
|
|
13074
13237
|
this.syncControls();
|
|
13075
13238
|
if (this.options.autoFocus !== false && this.options.autoFocusOnStep !== false) {
|
|
@@ -13079,6 +13242,7 @@ class SketchmarkEmbed {
|
|
|
13079
13242
|
resetAnimation() {
|
|
13080
13243
|
if (!this.instance)
|
|
13081
13244
|
return;
|
|
13245
|
+
this.playInFlight = false;
|
|
13082
13246
|
this.instance.anim.reset();
|
|
13083
13247
|
this.syncControls();
|
|
13084
13248
|
}
|
|
@@ -13105,6 +13269,7 @@ class SketchmarkEmbed {
|
|
|
13105
13269
|
}
|
|
13106
13270
|
destroy() {
|
|
13107
13271
|
this.stopMotion();
|
|
13272
|
+
this.playInFlight = false;
|
|
13108
13273
|
this.animUnsub?.();
|
|
13109
13274
|
this.instance?.anim?.destroy();
|
|
13110
13275
|
this.instance = null;
|
|
@@ -13137,6 +13302,9 @@ class SketchmarkEmbed {
|
|
|
13137
13302
|
this.btnRestart.disabled = true;
|
|
13138
13303
|
this.btnPrev.disabled = true;
|
|
13139
13304
|
this.btnNext.disabled = true;
|
|
13305
|
+
this.btnPlay.textContent = "Play";
|
|
13306
|
+
this.btnPlay.classList.remove("is-active");
|
|
13307
|
+
this.btnPlay.setAttribute("aria-pressed", "false");
|
|
13140
13308
|
this.btnPlay.disabled = true;
|
|
13141
13309
|
return;
|
|
13142
13310
|
}
|
|
@@ -13145,7 +13313,10 @@ class SketchmarkEmbed {
|
|
|
13145
13313
|
this.btnRestart.disabled = false;
|
|
13146
13314
|
this.btnPrev.disabled = !anim.canPrev;
|
|
13147
13315
|
this.btnNext.disabled = !anim.canNext;
|
|
13148
|
-
this.btnPlay.
|
|
13316
|
+
this.btnPlay.textContent = this.playInFlight ? "Stop" : "Play";
|
|
13317
|
+
this.btnPlay.classList.toggle("is-active", this.playInFlight);
|
|
13318
|
+
this.btnPlay.setAttribute("aria-pressed", this.playInFlight ? "true" : "false");
|
|
13319
|
+
this.btnPlay.disabled = this.playInFlight ? false : !anim.canNext;
|
|
13149
13320
|
}
|
|
13150
13321
|
syncViewControls() {
|
|
13151
13322
|
const hasView = !!this.instance?.svg;
|