sketchmark 1.1.3 → 1.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sketchmark might be problematic. Click here for more details.
- package/README.md +149 -132
- package/dist/animation/index.d.ts.map +1 -1
- package/dist/index.cjs +366 -71
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +366 -71
- package/dist/index.js.map +1 -1
- package/dist/render.d.ts +1 -0
- package/dist/render.d.ts.map +1 -1
- package/dist/sketchmark.iife.js +366 -71
- package/dist/ui/canvas.d.ts +12 -0
- package/dist/ui/canvas.d.ts.map +1 -1
- package/dist/ui/embed.d.ts +36 -1
- package/dist/ui/embed.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -8807,6 +8807,7 @@ function clearDashOverridesAfter(el, delayMs) {
|
|
|
8807
8807
|
}, delayMs);
|
|
8808
8808
|
}
|
|
8809
8809
|
const NODE_DRAW_GUIDE_ATTR = "data-node-draw-guide";
|
|
8810
|
+
const TEXT_REVEAL_CLIP_ATTR = "data-text-reveal-clip-id";
|
|
8810
8811
|
const GUIDED_NODE_SHAPES = new Set([
|
|
8811
8812
|
"box",
|
|
8812
8813
|
"circle",
|
|
@@ -8935,6 +8936,7 @@ function clearNodeDrawStyles(el) {
|
|
|
8935
8936
|
});
|
|
8936
8937
|
const text = nodeText(el);
|
|
8937
8938
|
if (text) {
|
|
8939
|
+
clearTextReveal(text);
|
|
8938
8940
|
text.style.opacity = text.style.transition = "";
|
|
8939
8941
|
}
|
|
8940
8942
|
}
|
|
@@ -8980,55 +8982,74 @@ function revealNodeInstant(el) {
|
|
|
8980
8982
|
clearNodeDrawStyles(el);
|
|
8981
8983
|
}
|
|
8982
8984
|
// ── Text writing reveal (clipPath) ───────────────────────
|
|
8985
|
+
function clearTextReveal(textEl, clipId) {
|
|
8986
|
+
const activeClipId = textEl.getAttribute(TEXT_REVEAL_CLIP_ATTR);
|
|
8987
|
+
const shouldClearCurrentClip = !clipId || activeClipId === clipId;
|
|
8988
|
+
if (shouldClearCurrentClip) {
|
|
8989
|
+
textEl.removeAttribute("clip-path");
|
|
8990
|
+
textEl.removeAttribute(TEXT_REVEAL_CLIP_ATTR);
|
|
8991
|
+
}
|
|
8992
|
+
const clipIdToRemove = clipId ?? activeClipId;
|
|
8993
|
+
if (clipIdToRemove) {
|
|
8994
|
+
textEl.ownerSVGElement?.querySelector(`#${clipIdToRemove}`)?.remove();
|
|
8995
|
+
}
|
|
8996
|
+
}
|
|
8983
8997
|
function animateTextReveal(textEl, delayMs, durationMs = ANIMATION.textRevealMs) {
|
|
8984
8998
|
const ownerSvg = textEl.ownerSVGElement;
|
|
8999
|
+
clearTextReveal(textEl);
|
|
8985
9000
|
if (!ownerSvg) {
|
|
8986
9001
|
// fallback: just fade
|
|
8987
9002
|
textEl.style.transition = `opacity ${ANIMATION.textFade}ms ease ${delayMs}ms`;
|
|
8988
9003
|
textEl.style.opacity = "1";
|
|
8989
9004
|
return;
|
|
8990
9005
|
}
|
|
8991
|
-
|
|
9006
|
+
const bbox = textEl.getBBox?.();
|
|
9007
|
+
if (!bbox || bbox.width === 0) {
|
|
9008
|
+
// fallback if can't measure
|
|
9009
|
+
textEl.style.transition = `opacity ${ANIMATION.textFade}ms ease ${delayMs}ms`;
|
|
9010
|
+
textEl.style.opacity = "1";
|
|
9011
|
+
return;
|
|
9012
|
+
}
|
|
9013
|
+
let defs = ownerSvg.querySelector("defs");
|
|
9014
|
+
if (!defs) {
|
|
9015
|
+
defs = document.createElementNS(SVG_NS$1, "defs");
|
|
9016
|
+
ownerSvg.insertBefore(defs, ownerSvg.firstChild);
|
|
9017
|
+
}
|
|
9018
|
+
const clipId = `skm-clip-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
9019
|
+
const clipPath = document.createElementNS(SVG_NS$1, "clipPath");
|
|
9020
|
+
clipPath.setAttribute("id", clipId);
|
|
9021
|
+
const rect = document.createElementNS(SVG_NS$1, "rect");
|
|
9022
|
+
rect.setAttribute("x", String(bbox.x - 2));
|
|
9023
|
+
rect.setAttribute("y", String(bbox.y - 2));
|
|
9024
|
+
rect.setAttribute("width", "0");
|
|
9025
|
+
rect.setAttribute("height", String(bbox.height + 4));
|
|
9026
|
+
clipPath.appendChild(rect);
|
|
9027
|
+
defs.appendChild(clipPath);
|
|
9028
|
+
textEl.setAttribute("clip-path", `url(#${clipId})`);
|
|
9029
|
+
textEl.setAttribute(TEXT_REVEAL_CLIP_ATTR, clipId);
|
|
8992
9030
|
textEl.style.opacity = "1";
|
|
8993
|
-
|
|
9031
|
+
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
9032
|
+
rect.style.transition = `width ${durationMs}ms cubic-bezier(.4,0,.2,1) ${delayMs}ms`;
|
|
9033
|
+
rect.setAttribute("width", String(bbox.width + 4));
|
|
9034
|
+
}));
|
|
9035
|
+
// Cleanup after animation
|
|
8994
9036
|
setTimeout(() => {
|
|
8995
|
-
|
|
8996
|
-
|
|
8997
|
-
// fallback if can't measure
|
|
8998
|
-
return;
|
|
8999
|
-
}
|
|
9000
|
-
let defs = ownerSvg.querySelector("defs");
|
|
9001
|
-
if (!defs) {
|
|
9002
|
-
defs = document.createElementNS(SVG_NS$1, "defs");
|
|
9003
|
-
ownerSvg.insertBefore(defs, ownerSvg.firstChild);
|
|
9004
|
-
}
|
|
9005
|
-
const clipId = `skm-clip-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
9006
|
-
const clipPath = document.createElementNS(SVG_NS$1, "clipPath");
|
|
9007
|
-
clipPath.setAttribute("id", clipId);
|
|
9008
|
-
const rect = document.createElementNS(SVG_NS$1, "rect");
|
|
9009
|
-
rect.setAttribute("x", String(bbox.x - 2));
|
|
9010
|
-
rect.setAttribute("y", String(bbox.y - 2));
|
|
9011
|
-
rect.setAttribute("width", "0");
|
|
9012
|
-
rect.setAttribute("height", String(bbox.height + 4));
|
|
9013
|
-
clipPath.appendChild(rect);
|
|
9014
|
-
defs.appendChild(clipPath);
|
|
9015
|
-
textEl.setAttribute("clip-path", `url(#${clipId})`);
|
|
9016
|
-
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
9017
|
-
rect.style.transition = `width ${durationMs}ms cubic-bezier(.4,0,.2,1)`;
|
|
9018
|
-
rect.setAttribute("width", String(bbox.width + 4));
|
|
9019
|
-
}));
|
|
9020
|
-
// Cleanup after animation
|
|
9021
|
-
setTimeout(() => {
|
|
9022
|
-
textEl.removeAttribute("clip-path");
|
|
9023
|
-
clipPath.remove();
|
|
9024
|
-
}, durationMs + 50);
|
|
9025
|
-
}, delayMs);
|
|
9037
|
+
clearTextReveal(textEl, clipId);
|
|
9038
|
+
}, delayMs + durationMs + 50);
|
|
9026
9039
|
}
|
|
9027
|
-
function animateNodeDraw(el, strokeDur = ANIMATION.nodeStrokeDur) {
|
|
9040
|
+
function animateNodeDraw(el, strokeDur = ANIMATION.nodeStrokeDur, textOnlyDur = ANIMATION.textRevealMs) {
|
|
9028
9041
|
showDrawEl(el);
|
|
9029
9042
|
const guide = nodeGuidePathEl(el);
|
|
9030
9043
|
if (!guide) {
|
|
9031
9044
|
const firstPath = el.querySelector("path");
|
|
9045
|
+
const text = nodeText(el);
|
|
9046
|
+
if (!firstPath && el.dataset.nodeShape === "text" && text) {
|
|
9047
|
+
animateTextReveal(text, 0, textOnlyDur);
|
|
9048
|
+
setTimeout(() => {
|
|
9049
|
+
clearNodeDrawStyles(el);
|
|
9050
|
+
}, textOnlyDur + 80);
|
|
9051
|
+
return;
|
|
9052
|
+
}
|
|
9032
9053
|
if (!firstPath?.style.strokeDasharray)
|
|
9033
9054
|
prepareForDraw(el);
|
|
9034
9055
|
animateShapeDraw(el, strokeDur, ANIMATION.nodeStagger);
|
|
@@ -9422,8 +9443,17 @@ class AnimationController {
|
|
|
9422
9443
|
}
|
|
9423
9444
|
/** Enable/disable browser text-to-speech for narrate steps */
|
|
9424
9445
|
get tts() { return this._tts; }
|
|
9425
|
-
set tts(on) {
|
|
9426
|
-
|
|
9446
|
+
set tts(on) {
|
|
9447
|
+
const next = !!on;
|
|
9448
|
+
const changed = next !== this._tts;
|
|
9449
|
+
this._tts = next;
|
|
9450
|
+
if (!next) {
|
|
9451
|
+
this._cancelSpeech();
|
|
9452
|
+
return;
|
|
9453
|
+
}
|
|
9454
|
+
if (changed)
|
|
9455
|
+
this._warmUpSpeech();
|
|
9456
|
+
}
|
|
9427
9457
|
get currentStep() {
|
|
9428
9458
|
return this._step;
|
|
9429
9459
|
}
|
|
@@ -10049,7 +10079,7 @@ class AnimationController {
|
|
|
10049
10079
|
if (!nodeGuidePathEl(nodeEl) && !nodeEl.querySelector("path")?.style.strokeDasharray) {
|
|
10050
10080
|
prepareNodeForDraw(nodeEl);
|
|
10051
10081
|
}
|
|
10052
|
-
animateNodeDraw(nodeEl, step.duration ?? ANIMATION.nodeStrokeDur);
|
|
10082
|
+
animateNodeDraw(nodeEl, step.duration ?? ANIMATION.nodeStrokeDur, step.duration ?? ANIMATION.textRevealMs);
|
|
10053
10083
|
}
|
|
10054
10084
|
}
|
|
10055
10085
|
// ── erase ─────────────────────────────────────────────────
|
|
@@ -10702,7 +10732,7 @@ class EventEmitter {
|
|
|
10702
10732
|
}
|
|
10703
10733
|
|
|
10704
10734
|
function render(options) {
|
|
10705
|
-
const { container: rawContainer, dsl, renderer = "svg", injectCSS = true, svgOptions = {}, canvasOptions = {}, onNodeClick, onReady, } = options;
|
|
10735
|
+
const { container: rawContainer, dsl, renderer = "svg", injectCSS = true, tts, svgOptions = {}, canvasOptions = {}, onNodeClick, onReady, } = options;
|
|
10706
10736
|
if (injectCSS && !document.getElementById("ai-diagram-css")) {
|
|
10707
10737
|
const style = document.createElement("style");
|
|
10708
10738
|
style.id = "ai-diagram-css";
|
|
@@ -10751,6 +10781,9 @@ function render(options) {
|
|
|
10751
10781
|
const containerEl = el instanceof SVGSVGElement ? undefined : el;
|
|
10752
10782
|
anim = new AnimationController(svg, ast.steps, containerEl, rc, ast.config);
|
|
10753
10783
|
}
|
|
10784
|
+
if (typeof tts === "boolean") {
|
|
10785
|
+
anim.tts = tts;
|
|
10786
|
+
}
|
|
10754
10787
|
onReady?.(anim, svg);
|
|
10755
10788
|
return {
|
|
10756
10789
|
scene,
|
|
@@ -10802,13 +10835,14 @@ function toError(error) {
|
|
|
10802
10835
|
const CANVAS_STYLE_ID = "sketchmark-canvas-ui";
|
|
10803
10836
|
const CANVAS_CSS = `
|
|
10804
10837
|
.skm-canvas{display:flex;flex-direction:column;width:100%;height:100%;min-height:320px;overflow:hidden;border:1px solid #caba98;border-radius:10px;background:#f8f4ea;color:#3a2010;font-family:"Courier New",monospace}
|
|
10805
|
-
.skm-canvas__animbar{display:flex;align-items:center;gap:6px;padding:6px 10px;background:#eee7d8;border-bottom:1px solid #caba98;flex-shrink:0}
|
|
10838
|
+
.skm-canvas__animbar{display:flex;align-items:center;gap:6px;padding:6px 10px;background:#eee7d8;border-bottom:1px solid #caba98;flex-shrink:0;flex-wrap:wrap}
|
|
10806
10839
|
.skm-canvas__status{min-width:96px;text-align:center;color:#6a4820;font-size:11px}
|
|
10807
10840
|
.skm-canvas__label{color:#8a6040;font-size:11px;font-style:italic}
|
|
10808
10841
|
.skm-canvas__spacer{flex:1}
|
|
10809
10842
|
.skm-canvas__stats{color:#9a7848;font-size:10px}
|
|
10810
10843
|
.skm-canvas__button{border:1px solid #caba98;background:#f5eedd;color:#3a2010;border-radius:6px;padding:4px 9px;font:inherit;font-size:11px;cursor:pointer;transition:background .12s ease,border-color .12s ease,color .12s ease}
|
|
10811
10844
|
.skm-canvas__button:hover:not(:disabled){background:#c8a060;border-color:#c8a060;color:#fff}
|
|
10845
|
+
.skm-canvas__button.is-active{background:#c8a060;border-color:#c8a060;color:#fff}
|
|
10812
10846
|
.skm-canvas__button:disabled{opacity:.45;cursor:default}
|
|
10813
10847
|
.skm-canvas__error{display:none;padding:8px 12px;background:#280a0a;border-bottom:1px solid #5a1818;color:#f07070;font-size:11px;line-height:1.4;white-space:pre-wrap;flex-shrink:0}
|
|
10814
10848
|
.skm-canvas__error.is-visible{display:block}
|
|
@@ -10830,6 +10864,8 @@ class SketchmarkCanvas {
|
|
|
10830
10864
|
this.instance = null;
|
|
10831
10865
|
this.emitter = new EventEmitter();
|
|
10832
10866
|
this.dsl = "";
|
|
10867
|
+
this.showCaption = true;
|
|
10868
|
+
this.ttsOverride = null;
|
|
10833
10869
|
this.panX = 60;
|
|
10834
10870
|
this.panY = 60;
|
|
10835
10871
|
this.zoom = 1;
|
|
@@ -10917,6 +10953,8 @@ class SketchmarkCanvas {
|
|
|
10917
10953
|
this.options = options;
|
|
10918
10954
|
this.renderer = options.renderer ?? "svg";
|
|
10919
10955
|
this.theme = options.theme ?? "light";
|
|
10956
|
+
this.showCaption = options.showCaption !== false;
|
|
10957
|
+
this.ttsOverride = typeof options.tts === "boolean" ? options.tts : null;
|
|
10920
10958
|
this.dsl = normalizeNewlines(options.dsl ?? "");
|
|
10921
10959
|
injectStyleOnce(CANVAS_STYLE_ID, CANVAS_CSS);
|
|
10922
10960
|
const host = resolveContainer(options.container);
|
|
@@ -10935,6 +10973,8 @@ class SketchmarkCanvas {
|
|
|
10935
10973
|
<span class="skm-canvas__status">No steps</span>
|
|
10936
10974
|
<button type="button" class="skm-canvas__button" data-action="next">Next</button>
|
|
10937
10975
|
<button type="button" class="skm-canvas__button" data-action="play">Play</button>
|
|
10976
|
+
<button type="button" class="skm-canvas__button" data-action="toggle-caption">Caption On</button>
|
|
10977
|
+
<button type="button" class="skm-canvas__button" data-action="toggle-tts">TTS Off</button>
|
|
10938
10978
|
<span class="skm-canvas__label"></span>
|
|
10939
10979
|
<span class="skm-canvas__spacer"></span>
|
|
10940
10980
|
<span class="skm-canvas__stats"></span>
|
|
@@ -10969,6 +11009,8 @@ class SketchmarkCanvas {
|
|
|
10969
11009
|
this.prevButton = this.root.querySelector('[data-action="prev"]');
|
|
10970
11010
|
this.nextButton = this.root.querySelector('[data-action="next"]');
|
|
10971
11011
|
this.resetButton = this.root.querySelector('[data-action="reset"]');
|
|
11012
|
+
this.captionButton = this.root.querySelector('[data-action="toggle-caption"]');
|
|
11013
|
+
this.ttsButton = this.root.querySelector('[data-action="toggle-tts"]');
|
|
10972
11014
|
this.gridPattern = this.root.querySelector(`#${patternId}`);
|
|
10973
11015
|
this.gridDot = this.gridPattern.querySelector("circle");
|
|
10974
11016
|
this.root.querySelector('[data-action="fit"]')?.addEventListener("click", () => this.fitContent());
|
|
@@ -10979,6 +11021,8 @@ class SketchmarkCanvas {
|
|
|
10979
11021
|
this.prevButton.addEventListener("click", () => this.prevStep());
|
|
10980
11022
|
this.nextButton.addEventListener("click", () => this.nextStep());
|
|
10981
11023
|
this.playButton.addEventListener("click", () => void this.play());
|
|
11024
|
+
this.captionButton.addEventListener("click", () => this.setCaptionVisible(!this.showCaption));
|
|
11025
|
+
this.ttsButton.addEventListener("click", () => this.setTtsEnabled(!this.getTtsEnabled()));
|
|
10982
11026
|
this.viewport.addEventListener("pointerdown", this.onPointerDown);
|
|
10983
11027
|
this.viewport.addEventListener("pointermove", this.onPointerMove);
|
|
10984
11028
|
this.viewport.addEventListener("pointerup", this.onStopPanning);
|
|
@@ -11000,6 +11044,16 @@ class SketchmarkCanvas {
|
|
|
11000
11044
|
if (renderNow)
|
|
11001
11045
|
this.render();
|
|
11002
11046
|
}
|
|
11047
|
+
setCaptionVisible(visible) {
|
|
11048
|
+
this.showCaption = visible;
|
|
11049
|
+
this.applyCaptionVisibility(this.instance);
|
|
11050
|
+
this.syncToggleUi();
|
|
11051
|
+
}
|
|
11052
|
+
setTtsEnabled(enabled) {
|
|
11053
|
+
this.ttsOverride = enabled;
|
|
11054
|
+
this.applyTtsSetting(this.instance);
|
|
11055
|
+
this.syncToggleUi();
|
|
11056
|
+
}
|
|
11003
11057
|
bindEditor(editor, options = {}) {
|
|
11004
11058
|
this.editorCleanup?.();
|
|
11005
11059
|
const renderOnRun = options.renderOnRun !== false;
|
|
@@ -11044,6 +11098,8 @@ class SketchmarkCanvas {
|
|
|
11044
11098
|
onNodeClick: this.options.onNodeClick,
|
|
11045
11099
|
});
|
|
11046
11100
|
this.instance = instance;
|
|
11101
|
+
this.applyCaptionVisibility(instance);
|
|
11102
|
+
this.applyTtsSetting(instance);
|
|
11047
11103
|
this.statsLabel.textContent = `${instance.scene.nodes.length}n / ${instance.scene.edges.length}e / ${instance.scene.groups.length}g`;
|
|
11048
11104
|
if (this.renderer === "svg") {
|
|
11049
11105
|
this.animUnsub = instance.anim.on((event) => {
|
|
@@ -11176,6 +11232,37 @@ class SketchmarkCanvas {
|
|
|
11176
11232
|
this.zoom = clampedZoom;
|
|
11177
11233
|
this.applyTransform();
|
|
11178
11234
|
}
|
|
11235
|
+
applyCaptionVisibility(instance) {
|
|
11236
|
+
const caption = instance?.anim.captionElement;
|
|
11237
|
+
if (!caption)
|
|
11238
|
+
return;
|
|
11239
|
+
caption.style.display = this.showCaption ? "" : "none";
|
|
11240
|
+
caption.setAttribute("aria-hidden", this.showCaption ? "false" : "true");
|
|
11241
|
+
}
|
|
11242
|
+
applyTtsSetting(instance) {
|
|
11243
|
+
if (!instance || this.ttsOverride === null)
|
|
11244
|
+
return;
|
|
11245
|
+
instance.anim.tts = this.ttsOverride;
|
|
11246
|
+
}
|
|
11247
|
+
getTtsEnabled() {
|
|
11248
|
+
if (this.ttsOverride !== null)
|
|
11249
|
+
return this.ttsOverride;
|
|
11250
|
+
return !!this.instance?.anim.tts;
|
|
11251
|
+
}
|
|
11252
|
+
syncToggleUi() {
|
|
11253
|
+
const canToggleCaption = this.renderer === "svg" && !!this.instance;
|
|
11254
|
+
const canToggleTts = canToggleCaption &&
|
|
11255
|
+
typeof speechSynthesis !== "undefined";
|
|
11256
|
+
const ttsEnabled = this.getTtsEnabled();
|
|
11257
|
+
this.captionButton.textContent = this.showCaption ? "Caption On" : "Caption Off";
|
|
11258
|
+
this.captionButton.classList.toggle("is-active", this.showCaption);
|
|
11259
|
+
this.captionButton.setAttribute("aria-pressed", this.showCaption ? "true" : "false");
|
|
11260
|
+
this.captionButton.disabled = !canToggleCaption;
|
|
11261
|
+
this.ttsButton.textContent = ttsEnabled ? "TTS On" : "TTS Off";
|
|
11262
|
+
this.ttsButton.classList.toggle("is-active", ttsEnabled);
|
|
11263
|
+
this.ttsButton.setAttribute("aria-pressed", ttsEnabled ? "true" : "false");
|
|
11264
|
+
this.ttsButton.disabled = !canToggleTts;
|
|
11265
|
+
}
|
|
11179
11266
|
syncAnimationUi() {
|
|
11180
11267
|
const anim = this.instance?.anim;
|
|
11181
11268
|
const canAnimate = this.renderer === "svg" && !!anim && anim.total > 0;
|
|
@@ -11186,6 +11273,7 @@ class SketchmarkCanvas {
|
|
|
11186
11273
|
this.nextButton.disabled = true;
|
|
11187
11274
|
this.resetButton.disabled = true;
|
|
11188
11275
|
this.playButton.disabled = true;
|
|
11276
|
+
this.syncToggleUi();
|
|
11189
11277
|
return;
|
|
11190
11278
|
}
|
|
11191
11279
|
this.stepDisplay.textContent = anim.currentStep < 0 ? `${anim.total} steps` : `${anim.currentStep + 1} / ${anim.total}`;
|
|
@@ -11194,6 +11282,7 @@ class SketchmarkCanvas {
|
|
|
11194
11282
|
this.nextButton.disabled = !anim.canNext;
|
|
11195
11283
|
this.resetButton.disabled = false;
|
|
11196
11284
|
this.playButton.disabled = this.playInFlight || !anim.canNext;
|
|
11285
|
+
this.syncToggleUi();
|
|
11197
11286
|
}
|
|
11198
11287
|
getStepTarget(stepItem) {
|
|
11199
11288
|
if (!stepItem)
|
|
@@ -11850,6 +11939,7 @@ const EMBED_CSS = `
|
|
|
11850
11939
|
|
|
11851
11940
|
.skm-embed__controls {
|
|
11852
11941
|
display: flex;
|
|
11942
|
+
flex-wrap: wrap;
|
|
11853
11943
|
align-items: center;
|
|
11854
11944
|
gap: 8px;
|
|
11855
11945
|
padding: 10px 12px;
|
|
@@ -11867,6 +11957,13 @@ const EMBED_CSS = `
|
|
|
11867
11957
|
display: none;
|
|
11868
11958
|
}
|
|
11869
11959
|
|
|
11960
|
+
.skm-embed__controls-group {
|
|
11961
|
+
display: flex;
|
|
11962
|
+
align-items: center;
|
|
11963
|
+
gap: 8px;
|
|
11964
|
+
flex-wrap: wrap;
|
|
11965
|
+
}
|
|
11966
|
+
|
|
11870
11967
|
.skm-embed__button {
|
|
11871
11968
|
border: 1px solid #caba98;
|
|
11872
11969
|
background: #f5eedd;
|
|
@@ -11885,6 +11982,12 @@ const EMBED_CSS = `
|
|
|
11885
11982
|
color: #fff;
|
|
11886
11983
|
}
|
|
11887
11984
|
|
|
11985
|
+
.skm-embed__button.is-active {
|
|
11986
|
+
background: #c8a060;
|
|
11987
|
+
border-color: #c8a060;
|
|
11988
|
+
color: #fff;
|
|
11989
|
+
}
|
|
11990
|
+
|
|
11888
11991
|
.skm-embed--dark .skm-embed__button {
|
|
11889
11992
|
border-color: #4a3520;
|
|
11890
11993
|
background: #22190e;
|
|
@@ -11902,6 +12005,17 @@ const EMBED_CSS = `
|
|
|
11902
12005
|
cursor: default;
|
|
11903
12006
|
}
|
|
11904
12007
|
|
|
12008
|
+
.skm-embed__zoom {
|
|
12009
|
+
min-width: 48px;
|
|
12010
|
+
text-align: center;
|
|
12011
|
+
color: #8a6040;
|
|
12012
|
+
font-size: 11px;
|
|
12013
|
+
}
|
|
12014
|
+
|
|
12015
|
+
.skm-embed--dark .skm-embed__zoom {
|
|
12016
|
+
color: #d0b176;
|
|
12017
|
+
}
|
|
12018
|
+
|
|
11905
12019
|
.skm-embed__step {
|
|
11906
12020
|
margin-left: auto;
|
|
11907
12021
|
min-width: 96px;
|
|
@@ -11920,13 +12034,19 @@ class SketchmarkEmbed {
|
|
|
11920
12034
|
this.emitter = new EventEmitter();
|
|
11921
12035
|
this.animUnsub = null;
|
|
11922
12036
|
this.playInFlight = false;
|
|
12037
|
+
this.showCaption = true;
|
|
12038
|
+
this.ttsOverride = null;
|
|
12039
|
+
this.zoom = 1;
|
|
11923
12040
|
this.offsetX = 0;
|
|
11924
12041
|
this.offsetY = 0;
|
|
12042
|
+
this.autoFitEnabled = true;
|
|
11925
12043
|
this.motionFrame = null;
|
|
11926
12044
|
this.resizeObserver = null;
|
|
11927
12045
|
this.options = options;
|
|
11928
12046
|
this.dsl = normalizeNewlines(options.dsl);
|
|
11929
12047
|
this.theme = options.theme ?? "light";
|
|
12048
|
+
this.showCaption = options.showCaption !== false;
|
|
12049
|
+
this.ttsOverride = typeof options.tts === "boolean" ? options.tts : null;
|
|
11930
12050
|
injectStyleOnce(EMBED_STYLE_ID, EMBED_CSS);
|
|
11931
12051
|
const host = resolveContainer(options.container);
|
|
11932
12052
|
host.innerHTML = "";
|
|
@@ -11942,10 +12062,22 @@ class SketchmarkEmbed {
|
|
|
11942
12062
|
</div>
|
|
11943
12063
|
<div class="skm-embed__error"></div>
|
|
11944
12064
|
<div class="skm-embed__controls">
|
|
11945
|
-
<
|
|
11946
|
-
|
|
11947
|
-
|
|
11948
|
-
|
|
12065
|
+
<div class="skm-embed__controls-group">
|
|
12066
|
+
<button type="button" class="skm-embed__button" data-action="zoom-out">-</button>
|
|
12067
|
+
<span class="skm-embed__zoom">100%</span>
|
|
12068
|
+
<button type="button" class="skm-embed__button" data-action="zoom-in">+</button>
|
|
12069
|
+
<button type="button" class="skm-embed__button" data-action="fit">Reset</button>
|
|
12070
|
+
</div>
|
|
12071
|
+
<div class="skm-embed__controls-group">
|
|
12072
|
+
<button type="button" class="skm-embed__button" data-action="restart">Restart</button>
|
|
12073
|
+
<button type="button" class="skm-embed__button" data-action="prev">Prev</button>
|
|
12074
|
+
<button type="button" class="skm-embed__button" data-action="next">Next</button>
|
|
12075
|
+
<button type="button" class="skm-embed__button" data-action="play">Play</button>
|
|
12076
|
+
</div>
|
|
12077
|
+
<div class="skm-embed__controls-group">
|
|
12078
|
+
<button type="button" class="skm-embed__button" data-action="toggle-caption">Caption On</button>
|
|
12079
|
+
<button type="button" class="skm-embed__button" data-action="toggle-tts">TTS Off</button>
|
|
12080
|
+
</div>
|
|
11949
12081
|
<span class="skm-embed__step">No steps</span>
|
|
11950
12082
|
</div>
|
|
11951
12083
|
`;
|
|
@@ -11955,17 +12087,28 @@ class SketchmarkEmbed {
|
|
|
11955
12087
|
this.errorElement = this.root.querySelector(".skm-embed__error");
|
|
11956
12088
|
this.controlsElement = this.root.querySelector(".skm-embed__controls");
|
|
11957
12089
|
this.stepInfoElement = this.root.querySelector(".skm-embed__step");
|
|
11958
|
-
this.
|
|
12090
|
+
this.zoomInfoElement = this.root.querySelector(".skm-embed__zoom");
|
|
12091
|
+
this.btnFit = this.root.querySelector('[data-action="fit"]');
|
|
12092
|
+
this.btnZoomIn = this.root.querySelector('[data-action="zoom-in"]');
|
|
12093
|
+
this.btnZoomOut = this.root.querySelector('[data-action="zoom-out"]');
|
|
12094
|
+
this.btnRestart = this.root.querySelector('[data-action="restart"]');
|
|
11959
12095
|
this.btnPrev = this.root.querySelector('[data-action="prev"]');
|
|
11960
12096
|
this.btnNext = this.root.querySelector('[data-action="next"]');
|
|
11961
12097
|
this.btnPlay = this.root.querySelector('[data-action="play"]');
|
|
12098
|
+
this.btnCaption = this.root.querySelector('[data-action="toggle-caption"]');
|
|
12099
|
+
this.btnTts = this.root.querySelector('[data-action="toggle-tts"]');
|
|
11962
12100
|
this.controlsElement.classList.toggle("is-hidden", options.showControls === false);
|
|
11963
|
-
this.
|
|
12101
|
+
this.btnFit.addEventListener("click", () => this.resetView());
|
|
12102
|
+
this.btnZoomIn.addEventListener("click", () => this.zoomIn());
|
|
12103
|
+
this.btnZoomOut.addEventListener("click", () => this.zoomOut());
|
|
12104
|
+
this.btnRestart.addEventListener("click", () => this.resetAnimation());
|
|
11964
12105
|
this.btnPrev.addEventListener("click", () => this.prevStep());
|
|
11965
12106
|
this.btnNext.addEventListener("click", () => this.nextStep());
|
|
11966
12107
|
this.btnPlay.addEventListener("click", () => {
|
|
11967
12108
|
void this.play();
|
|
11968
12109
|
});
|
|
12110
|
+
this.btnCaption.addEventListener("click", () => this.setCaptionVisible(!this.showCaption));
|
|
12111
|
+
this.btnTts.addEventListener("click", () => this.setTtsEnabled(!this.getTtsEnabled()));
|
|
11969
12112
|
if (typeof ResizeObserver !== "undefined") {
|
|
11970
12113
|
this.resizeObserver = new ResizeObserver(() => {
|
|
11971
12114
|
this.positionViewport(false);
|
|
@@ -11983,6 +12126,16 @@ class SketchmarkEmbed {
|
|
|
11983
12126
|
if (renderNow)
|
|
11984
12127
|
this.render();
|
|
11985
12128
|
}
|
|
12129
|
+
setCaptionVisible(visible) {
|
|
12130
|
+
this.showCaption = visible;
|
|
12131
|
+
this.applyCaptionVisibility(this.instance);
|
|
12132
|
+
this.syncToggleControls();
|
|
12133
|
+
}
|
|
12134
|
+
setTtsEnabled(enabled) {
|
|
12135
|
+
this.ttsOverride = enabled;
|
|
12136
|
+
this.applyTtsSetting(this.instance);
|
|
12137
|
+
this.syncToggleControls();
|
|
12138
|
+
}
|
|
11986
12139
|
setSize(width, height) {
|
|
11987
12140
|
this.applySize(width, height);
|
|
11988
12141
|
this.positionViewport(false);
|
|
@@ -12006,7 +12159,12 @@ class SketchmarkEmbed {
|
|
|
12006
12159
|
this.animUnsub = null;
|
|
12007
12160
|
this.instance?.anim?.destroy();
|
|
12008
12161
|
this.instance = null;
|
|
12162
|
+
this.autoFitEnabled = true;
|
|
12163
|
+
this.zoom = 1;
|
|
12164
|
+
this.offsetX = 0;
|
|
12165
|
+
this.offsetY = 0;
|
|
12009
12166
|
this.diagramWrap.innerHTML = "";
|
|
12167
|
+
this.applyTransform();
|
|
12010
12168
|
try {
|
|
12011
12169
|
const instance = render({
|
|
12012
12170
|
container: this.diagramWrap,
|
|
@@ -12022,6 +12180,8 @@ class SketchmarkEmbed {
|
|
|
12022
12180
|
onNodeClick: this.options.onNodeClick,
|
|
12023
12181
|
});
|
|
12024
12182
|
this.instance = instance;
|
|
12183
|
+
this.applyCaptionVisibility(instance);
|
|
12184
|
+
this.applyTtsSetting(instance);
|
|
12025
12185
|
this.animUnsub = instance.anim.on((event) => {
|
|
12026
12186
|
this.syncControls();
|
|
12027
12187
|
if (event.type === "step-change") {
|
|
@@ -12087,6 +12247,21 @@ class SketchmarkEmbed {
|
|
|
12087
12247
|
this.syncControls();
|
|
12088
12248
|
this.positionViewport(true);
|
|
12089
12249
|
}
|
|
12250
|
+
fitToViewport(animated = false) {
|
|
12251
|
+
if (!this.instance?.svg)
|
|
12252
|
+
return;
|
|
12253
|
+
this.autoFitEnabled = true;
|
|
12254
|
+
this.positionViewport(animated);
|
|
12255
|
+
}
|
|
12256
|
+
resetView(animated = false) {
|
|
12257
|
+
this.fitToViewport(animated);
|
|
12258
|
+
}
|
|
12259
|
+
zoomIn() {
|
|
12260
|
+
this.zoomAroundViewportCenter(1.2);
|
|
12261
|
+
}
|
|
12262
|
+
zoomOut() {
|
|
12263
|
+
this.zoomAroundViewportCenter(0.8);
|
|
12264
|
+
}
|
|
12090
12265
|
exportSVG(filename) {
|
|
12091
12266
|
this.instance?.exportSVG(filename);
|
|
12092
12267
|
}
|
|
@@ -12109,10 +12284,15 @@ class SketchmarkEmbed {
|
|
|
12109
12284
|
return typeof value === "number" ? `${value}px` : value;
|
|
12110
12285
|
}
|
|
12111
12286
|
syncControls() {
|
|
12287
|
+
this.syncAnimationControls();
|
|
12288
|
+
this.syncViewControls();
|
|
12289
|
+
this.syncToggleControls();
|
|
12290
|
+
}
|
|
12291
|
+
syncAnimationControls() {
|
|
12112
12292
|
const anim = this.instance?.anim;
|
|
12113
12293
|
if (!anim || !anim.total) {
|
|
12114
12294
|
this.stepInfoElement.textContent = "No steps";
|
|
12115
|
-
this.
|
|
12295
|
+
this.btnRestart.disabled = true;
|
|
12116
12296
|
this.btnPrev.disabled = true;
|
|
12117
12297
|
this.btnNext.disabled = true;
|
|
12118
12298
|
this.btnPlay.disabled = true;
|
|
@@ -12120,56 +12300,69 @@ class SketchmarkEmbed {
|
|
|
12120
12300
|
}
|
|
12121
12301
|
this.stepInfoElement.textContent =
|
|
12122
12302
|
anim.currentStep < 0 ? `${anim.total} steps` : `${anim.currentStep + 1} / ${anim.total}`;
|
|
12123
|
-
this.
|
|
12303
|
+
this.btnRestart.disabled = false;
|
|
12124
12304
|
this.btnPrev.disabled = !anim.canPrev;
|
|
12125
12305
|
this.btnNext.disabled = !anim.canNext;
|
|
12126
12306
|
this.btnPlay.disabled = this.playInFlight || !anim.canNext;
|
|
12127
12307
|
}
|
|
12308
|
+
syncViewControls() {
|
|
12309
|
+
const hasView = !!this.instance?.svg;
|
|
12310
|
+
const zoomMin = this.getZoomMin();
|
|
12311
|
+
const zoomMax = this.getZoomMax();
|
|
12312
|
+
this.zoomInfoElement.textContent = `${Math.round(this.zoom * 100)}%`;
|
|
12313
|
+
this.btnFit.disabled = !hasView;
|
|
12314
|
+
this.btnZoomOut.disabled = !hasView || this.zoom <= zoomMin + 0.001;
|
|
12315
|
+
this.btnZoomIn.disabled = !hasView || this.zoom >= zoomMax - 0.001;
|
|
12316
|
+
}
|
|
12128
12317
|
positionViewport(animated) {
|
|
12129
|
-
|
|
12130
|
-
|
|
12131
|
-
const svg = this.instance.svg;
|
|
12132
|
-
const svgWidth = parseFloat(svg.getAttribute("width") || "0");
|
|
12133
|
-
const svgHeight = parseFloat(svg.getAttribute("height") || "0");
|
|
12134
|
-
if (!svgWidth || !svgHeight)
|
|
12318
|
+
const size = this.getContentSize();
|
|
12319
|
+
if (!size)
|
|
12135
12320
|
return;
|
|
12321
|
+
const { width: svgWidth, height: svgHeight } = size;
|
|
12136
12322
|
const viewportRect = this.viewport.getBoundingClientRect();
|
|
12137
12323
|
const viewWidth = viewportRect.width || this.viewport.clientWidth;
|
|
12138
12324
|
const viewHeight = viewportRect.height || this.viewport.clientHeight;
|
|
12139
12325
|
if (!viewWidth || !viewHeight)
|
|
12140
12326
|
return;
|
|
12141
|
-
|
|
12327
|
+
if (this.autoFitEnabled) {
|
|
12328
|
+
this.zoom = this.getFitZoom(svgWidth, svgHeight, viewWidth, viewHeight);
|
|
12329
|
+
}
|
|
12330
|
+
this.syncViewControls();
|
|
12331
|
+
const scaledWidth = svgWidth * this.zoom;
|
|
12332
|
+
const scaledHeight = svgHeight * this.zoom;
|
|
12333
|
+
const focusTarget = this.getFocusTarget();
|
|
12334
|
+
const sceneIsLarge = scaledWidth > viewWidth || scaledHeight > viewHeight;
|
|
12142
12335
|
const shouldFocus = sceneIsLarge &&
|
|
12143
12336
|
this.options.autoFocus !== false &&
|
|
12144
|
-
!!
|
|
12337
|
+
!!focusTarget;
|
|
12145
12338
|
if (!shouldFocus) {
|
|
12146
|
-
this.animateTo(
|
|
12339
|
+
this.animateTo(scaledWidth <= viewWidth ? (viewWidth - scaledWidth) / 2 : 0, scaledHeight <= viewHeight ? (viewHeight - scaledHeight) / 2 : 0, animated);
|
|
12147
12340
|
return;
|
|
12148
12341
|
}
|
|
12149
|
-
const target = this.findTargetElement(
|
|
12342
|
+
const target = this.findTargetElement(focusTarget);
|
|
12150
12343
|
if (!target) {
|
|
12151
12344
|
this.animateTo(0, 0, animated);
|
|
12152
12345
|
return;
|
|
12153
12346
|
}
|
|
12154
|
-
const
|
|
12155
|
-
|
|
12156
|
-
|
|
12157
|
-
|
|
12158
|
-
|
|
12159
|
-
let nextX = viewWidth / 2 -
|
|
12160
|
-
let nextY = viewHeight / 2 -
|
|
12347
|
+
const targetBox = this.getTargetBox(target, viewportRect);
|
|
12348
|
+
if (!targetBox) {
|
|
12349
|
+
this.animateTo(0, 0, animated);
|
|
12350
|
+
return;
|
|
12351
|
+
}
|
|
12352
|
+
let nextX = viewWidth / 2 - targetBox.centerX;
|
|
12353
|
+
let nextY = viewHeight / 2 - targetBox.centerY;
|
|
12161
12354
|
const padding = this.options.focusPadding ?? 24;
|
|
12162
|
-
if (
|
|
12163
|
-
nextX = (viewWidth -
|
|
12355
|
+
if (scaledWidth <= viewWidth) {
|
|
12356
|
+
nextX = (viewWidth - scaledWidth) / 2;
|
|
12164
12357
|
}
|
|
12165
12358
|
else {
|
|
12166
|
-
nextX = clamp(nextX, viewWidth -
|
|
12359
|
+
nextX = clamp(nextX, viewWidth - scaledWidth - padding, padding);
|
|
12167
12360
|
}
|
|
12168
|
-
if (
|
|
12169
|
-
nextY = (viewHeight -
|
|
12361
|
+
if (scaledHeight <= viewHeight) {
|
|
12362
|
+
nextY = (viewHeight - scaledHeight) / 2;
|
|
12170
12363
|
}
|
|
12171
12364
|
else {
|
|
12172
|
-
nextY = clamp(nextY, viewHeight -
|
|
12365
|
+
nextY = clamp(nextY, viewHeight - scaledHeight - padding, padding);
|
|
12173
12366
|
}
|
|
12174
12367
|
this.animateTo(nextX, nextY, animated);
|
|
12175
12368
|
}
|
|
@@ -12201,7 +12394,109 @@ class SketchmarkEmbed {
|
|
|
12201
12394
|
this.motionFrame = requestAnimationFrame(frame);
|
|
12202
12395
|
}
|
|
12203
12396
|
applyTransform() {
|
|
12204
|
-
this.world.style.transform = `translate(${this.offsetX}px, ${this.offsetY}px)`;
|
|
12397
|
+
this.world.style.transform = `translate(${this.offsetX}px, ${this.offsetY}px) scale(${this.zoom})`;
|
|
12398
|
+
this.zoomInfoElement.textContent = `${Math.round(this.zoom * 100)}%`;
|
|
12399
|
+
}
|
|
12400
|
+
getContentSize() {
|
|
12401
|
+
if (!this.instance?.svg)
|
|
12402
|
+
return null;
|
|
12403
|
+
const svg = this.instance.svg;
|
|
12404
|
+
const width = parseFloat(svg.getAttribute("width") || "0");
|
|
12405
|
+
const height = parseFloat(svg.getAttribute("height") || "0");
|
|
12406
|
+
if (!width || !height)
|
|
12407
|
+
return null;
|
|
12408
|
+
return { width, height };
|
|
12409
|
+
}
|
|
12410
|
+
getFitZoom(svgWidth, svgHeight, viewWidth, viewHeight) {
|
|
12411
|
+
const padding = this.getFitPadding(viewWidth, viewHeight);
|
|
12412
|
+
const availableWidth = Math.max(viewWidth - padding * 2, 1);
|
|
12413
|
+
const availableHeight = Math.max(viewHeight - padding * 2, 1);
|
|
12414
|
+
const nextZoom = Math.min(availableWidth / svgWidth, availableHeight / svgHeight, 1);
|
|
12415
|
+
return clamp(nextZoom || 1, this.getZoomMin(), this.getZoomMax());
|
|
12416
|
+
}
|
|
12417
|
+
getFitPadding(viewWidth, viewHeight) {
|
|
12418
|
+
if (typeof this.options.fitPadding === "number") {
|
|
12419
|
+
return Math.max(0, this.options.fitPadding);
|
|
12420
|
+
}
|
|
12421
|
+
return Math.max(16, Math.min(40, Math.round(Math.min(viewWidth, viewHeight) * 0.08)));
|
|
12422
|
+
}
|
|
12423
|
+
getZoomMin() {
|
|
12424
|
+
return this.options.zoomMin ?? 0.08;
|
|
12425
|
+
}
|
|
12426
|
+
getZoomMax() {
|
|
12427
|
+
return this.options.zoomMax ?? 4;
|
|
12428
|
+
}
|
|
12429
|
+
zoomAroundViewportCenter(factor) {
|
|
12430
|
+
if (!this.instance?.svg)
|
|
12431
|
+
return;
|
|
12432
|
+
const pivotX = this.viewport.clientWidth / 2;
|
|
12433
|
+
const pivotY = this.viewport.clientHeight / 2;
|
|
12434
|
+
this.zoomTo(this.zoom * factor, pivotX, pivotY);
|
|
12435
|
+
}
|
|
12436
|
+
zoomTo(nextZoom, pivotX, pivotY) {
|
|
12437
|
+
const clampedZoom = clamp(nextZoom, this.getZoomMin(), this.getZoomMax());
|
|
12438
|
+
const ratio = clampedZoom / this.zoom;
|
|
12439
|
+
if (!Number.isFinite(ratio) || ratio === 1) {
|
|
12440
|
+
this.syncViewControls();
|
|
12441
|
+
return;
|
|
12442
|
+
}
|
|
12443
|
+
this.stopMotion();
|
|
12444
|
+
this.autoFitEnabled = false;
|
|
12445
|
+
this.offsetX = pivotX - (pivotX - this.offsetX) * ratio;
|
|
12446
|
+
this.offsetY = pivotY - (pivotY - this.offsetY) * ratio;
|
|
12447
|
+
this.zoom = clampedZoom;
|
|
12448
|
+
this.applyTransform();
|
|
12449
|
+
this.syncViewControls();
|
|
12450
|
+
}
|
|
12451
|
+
applyCaptionVisibility(instance) {
|
|
12452
|
+
const caption = instance?.anim.captionElement;
|
|
12453
|
+
if (!caption)
|
|
12454
|
+
return;
|
|
12455
|
+
caption.style.display = this.showCaption ? "" : "none";
|
|
12456
|
+
caption.setAttribute("aria-hidden", this.showCaption ? "false" : "true");
|
|
12457
|
+
}
|
|
12458
|
+
applyTtsSetting(instance) {
|
|
12459
|
+
if (!instance || this.ttsOverride === null)
|
|
12460
|
+
return;
|
|
12461
|
+
instance.anim.tts = this.ttsOverride;
|
|
12462
|
+
}
|
|
12463
|
+
getTtsEnabled() {
|
|
12464
|
+
if (this.ttsOverride !== null)
|
|
12465
|
+
return this.ttsOverride;
|
|
12466
|
+
return !!this.instance?.anim.tts;
|
|
12467
|
+
}
|
|
12468
|
+
syncToggleControls() {
|
|
12469
|
+
const hasView = !!this.instance?.svg;
|
|
12470
|
+
const canToggleTts = hasView &&
|
|
12471
|
+
typeof speechSynthesis !== "undefined";
|
|
12472
|
+
const ttsEnabled = this.getTtsEnabled();
|
|
12473
|
+
this.btnCaption.textContent = this.showCaption ? "Caption On" : "Caption Off";
|
|
12474
|
+
this.btnCaption.classList.toggle("is-active", this.showCaption);
|
|
12475
|
+
this.btnCaption.setAttribute("aria-pressed", this.showCaption ? "true" : "false");
|
|
12476
|
+
this.btnCaption.disabled = !hasView;
|
|
12477
|
+
this.btnTts.textContent = ttsEnabled ? "TTS On" : "TTS Off";
|
|
12478
|
+
this.btnTts.classList.toggle("is-active", ttsEnabled);
|
|
12479
|
+
this.btnTts.setAttribute("aria-pressed", ttsEnabled ? "true" : "false");
|
|
12480
|
+
this.btnTts.disabled = !canToggleTts;
|
|
12481
|
+
}
|
|
12482
|
+
getTargetBox(target, viewportRect) {
|
|
12483
|
+
if (target instanceof SVGGraphicsElement) {
|
|
12484
|
+
try {
|
|
12485
|
+
const bounds = target.getBBox();
|
|
12486
|
+
return {
|
|
12487
|
+
centerX: (bounds.x + bounds.width / 2) * this.zoom,
|
|
12488
|
+
centerY: (bounds.y + bounds.height / 2) * this.zoom,
|
|
12489
|
+
};
|
|
12490
|
+
}
|
|
12491
|
+
catch {
|
|
12492
|
+
// Ignore and fall back to layout-based bounds below.
|
|
12493
|
+
}
|
|
12494
|
+
}
|
|
12495
|
+
const currentRect = target.getBoundingClientRect();
|
|
12496
|
+
return {
|
|
12497
|
+
centerX: currentRect.left - viewportRect.left - this.offsetX + currentRect.width / 2,
|
|
12498
|
+
centerY: currentRect.top - viewportRect.top - this.offsetY + currentRect.height / 2,
|
|
12499
|
+
};
|
|
12205
12500
|
}
|
|
12206
12501
|
getFocusTarget() {
|
|
12207
12502
|
const anim = this.instance?.anim;
|