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