sketchmark 1.1.3 → 1.1.4
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 +101 -104
- package/dist/animation/index.d.ts.map +1 -1
- package/dist/index.cjs +232 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +232 -67
- package/dist/index.js.map +1 -1
- package/dist/sketchmark.iife.js +232 -67
- package/dist/ui/embed.d.ts +24 -1
- package/dist/ui/embed.d.ts.map +1 -1
- package/package.json +11 -12
package/dist/index.js
CHANGED
|
@@ -8805,6 +8805,7 @@ function clearDashOverridesAfter(el, delayMs) {
|
|
|
8805
8805
|
}, delayMs);
|
|
8806
8806
|
}
|
|
8807
8807
|
const NODE_DRAW_GUIDE_ATTR = "data-node-draw-guide";
|
|
8808
|
+
const TEXT_REVEAL_CLIP_ATTR = "data-text-reveal-clip-id";
|
|
8808
8809
|
const GUIDED_NODE_SHAPES = new Set([
|
|
8809
8810
|
"box",
|
|
8810
8811
|
"circle",
|
|
@@ -8933,6 +8934,7 @@ function clearNodeDrawStyles(el) {
|
|
|
8933
8934
|
});
|
|
8934
8935
|
const text = nodeText(el);
|
|
8935
8936
|
if (text) {
|
|
8937
|
+
clearTextReveal(text);
|
|
8936
8938
|
text.style.opacity = text.style.transition = "";
|
|
8937
8939
|
}
|
|
8938
8940
|
}
|
|
@@ -8978,55 +8980,74 @@ function revealNodeInstant(el) {
|
|
|
8978
8980
|
clearNodeDrawStyles(el);
|
|
8979
8981
|
}
|
|
8980
8982
|
// ── Text writing reveal (clipPath) ───────────────────────
|
|
8983
|
+
function clearTextReveal(textEl, clipId) {
|
|
8984
|
+
const activeClipId = textEl.getAttribute(TEXT_REVEAL_CLIP_ATTR);
|
|
8985
|
+
const shouldClearCurrentClip = !clipId || activeClipId === clipId;
|
|
8986
|
+
if (shouldClearCurrentClip) {
|
|
8987
|
+
textEl.removeAttribute("clip-path");
|
|
8988
|
+
textEl.removeAttribute(TEXT_REVEAL_CLIP_ATTR);
|
|
8989
|
+
}
|
|
8990
|
+
const clipIdToRemove = clipId ?? activeClipId;
|
|
8991
|
+
if (clipIdToRemove) {
|
|
8992
|
+
textEl.ownerSVGElement?.querySelector(`#${clipIdToRemove}`)?.remove();
|
|
8993
|
+
}
|
|
8994
|
+
}
|
|
8981
8995
|
function animateTextReveal(textEl, delayMs, durationMs = ANIMATION.textRevealMs) {
|
|
8982
8996
|
const ownerSvg = textEl.ownerSVGElement;
|
|
8997
|
+
clearTextReveal(textEl);
|
|
8983
8998
|
if (!ownerSvg) {
|
|
8984
8999
|
// fallback: just fade
|
|
8985
9000
|
textEl.style.transition = `opacity ${ANIMATION.textFade}ms ease ${delayMs}ms`;
|
|
8986
9001
|
textEl.style.opacity = "1";
|
|
8987
9002
|
return;
|
|
8988
9003
|
}
|
|
8989
|
-
|
|
9004
|
+
const bbox = textEl.getBBox?.();
|
|
9005
|
+
if (!bbox || bbox.width === 0) {
|
|
9006
|
+
// fallback if can't measure
|
|
9007
|
+
textEl.style.transition = `opacity ${ANIMATION.textFade}ms ease ${delayMs}ms`;
|
|
9008
|
+
textEl.style.opacity = "1";
|
|
9009
|
+
return;
|
|
9010
|
+
}
|
|
9011
|
+
let defs = ownerSvg.querySelector("defs");
|
|
9012
|
+
if (!defs) {
|
|
9013
|
+
defs = document.createElementNS(SVG_NS$1, "defs");
|
|
9014
|
+
ownerSvg.insertBefore(defs, ownerSvg.firstChild);
|
|
9015
|
+
}
|
|
9016
|
+
const clipId = `skm-clip-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
9017
|
+
const clipPath = document.createElementNS(SVG_NS$1, "clipPath");
|
|
9018
|
+
clipPath.setAttribute("id", clipId);
|
|
9019
|
+
const rect = document.createElementNS(SVG_NS$1, "rect");
|
|
9020
|
+
rect.setAttribute("x", String(bbox.x - 2));
|
|
9021
|
+
rect.setAttribute("y", String(bbox.y - 2));
|
|
9022
|
+
rect.setAttribute("width", "0");
|
|
9023
|
+
rect.setAttribute("height", String(bbox.height + 4));
|
|
9024
|
+
clipPath.appendChild(rect);
|
|
9025
|
+
defs.appendChild(clipPath);
|
|
9026
|
+
textEl.setAttribute("clip-path", `url(#${clipId})`);
|
|
9027
|
+
textEl.setAttribute(TEXT_REVEAL_CLIP_ATTR, clipId);
|
|
8990
9028
|
textEl.style.opacity = "1";
|
|
8991
|
-
|
|
9029
|
+
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
9030
|
+
rect.style.transition = `width ${durationMs}ms cubic-bezier(.4,0,.2,1) ${delayMs}ms`;
|
|
9031
|
+
rect.setAttribute("width", String(bbox.width + 4));
|
|
9032
|
+
}));
|
|
9033
|
+
// Cleanup after animation
|
|
8992
9034
|
setTimeout(() => {
|
|
8993
|
-
|
|
8994
|
-
|
|
8995
|
-
// fallback if can't measure
|
|
8996
|
-
return;
|
|
8997
|
-
}
|
|
8998
|
-
let defs = ownerSvg.querySelector("defs");
|
|
8999
|
-
if (!defs) {
|
|
9000
|
-
defs = document.createElementNS(SVG_NS$1, "defs");
|
|
9001
|
-
ownerSvg.insertBefore(defs, ownerSvg.firstChild);
|
|
9002
|
-
}
|
|
9003
|
-
const clipId = `skm-clip-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
9004
|
-
const clipPath = document.createElementNS(SVG_NS$1, "clipPath");
|
|
9005
|
-
clipPath.setAttribute("id", clipId);
|
|
9006
|
-
const rect = document.createElementNS(SVG_NS$1, "rect");
|
|
9007
|
-
rect.setAttribute("x", String(bbox.x - 2));
|
|
9008
|
-
rect.setAttribute("y", String(bbox.y - 2));
|
|
9009
|
-
rect.setAttribute("width", "0");
|
|
9010
|
-
rect.setAttribute("height", String(bbox.height + 4));
|
|
9011
|
-
clipPath.appendChild(rect);
|
|
9012
|
-
defs.appendChild(clipPath);
|
|
9013
|
-
textEl.setAttribute("clip-path", `url(#${clipId})`);
|
|
9014
|
-
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
9015
|
-
rect.style.transition = `width ${durationMs}ms cubic-bezier(.4,0,.2,1)`;
|
|
9016
|
-
rect.setAttribute("width", String(bbox.width + 4));
|
|
9017
|
-
}));
|
|
9018
|
-
// Cleanup after animation
|
|
9019
|
-
setTimeout(() => {
|
|
9020
|
-
textEl.removeAttribute("clip-path");
|
|
9021
|
-
clipPath.remove();
|
|
9022
|
-
}, durationMs + 50);
|
|
9023
|
-
}, delayMs);
|
|
9035
|
+
clearTextReveal(textEl, clipId);
|
|
9036
|
+
}, delayMs + durationMs + 50);
|
|
9024
9037
|
}
|
|
9025
|
-
function animateNodeDraw(el, strokeDur = ANIMATION.nodeStrokeDur) {
|
|
9038
|
+
function animateNodeDraw(el, strokeDur = ANIMATION.nodeStrokeDur, textOnlyDur = ANIMATION.textRevealMs) {
|
|
9026
9039
|
showDrawEl(el);
|
|
9027
9040
|
const guide = nodeGuidePathEl(el);
|
|
9028
9041
|
if (!guide) {
|
|
9029
9042
|
const firstPath = el.querySelector("path");
|
|
9043
|
+
const text = nodeText(el);
|
|
9044
|
+
if (!firstPath && el.dataset.nodeShape === "text" && text) {
|
|
9045
|
+
animateTextReveal(text, 0, textOnlyDur);
|
|
9046
|
+
setTimeout(() => {
|
|
9047
|
+
clearNodeDrawStyles(el);
|
|
9048
|
+
}, textOnlyDur + 80);
|
|
9049
|
+
return;
|
|
9050
|
+
}
|
|
9030
9051
|
if (!firstPath?.style.strokeDasharray)
|
|
9031
9052
|
prepareForDraw(el);
|
|
9032
9053
|
animateShapeDraw(el, strokeDur, ANIMATION.nodeStagger);
|
|
@@ -10047,7 +10068,7 @@ class AnimationController {
|
|
|
10047
10068
|
if (!nodeGuidePathEl(nodeEl) && !nodeEl.querySelector("path")?.style.strokeDasharray) {
|
|
10048
10069
|
prepareNodeForDraw(nodeEl);
|
|
10049
10070
|
}
|
|
10050
|
-
animateNodeDraw(nodeEl, step.duration ?? ANIMATION.nodeStrokeDur);
|
|
10071
|
+
animateNodeDraw(nodeEl, step.duration ?? ANIMATION.nodeStrokeDur, step.duration ?? ANIMATION.textRevealMs);
|
|
10051
10072
|
}
|
|
10052
10073
|
}
|
|
10053
10074
|
// ── erase ─────────────────────────────────────────────────
|
|
@@ -11848,6 +11869,7 @@ const EMBED_CSS = `
|
|
|
11848
11869
|
|
|
11849
11870
|
.skm-embed__controls {
|
|
11850
11871
|
display: flex;
|
|
11872
|
+
flex-wrap: wrap;
|
|
11851
11873
|
align-items: center;
|
|
11852
11874
|
gap: 8px;
|
|
11853
11875
|
padding: 10px 12px;
|
|
@@ -11865,6 +11887,13 @@ const EMBED_CSS = `
|
|
|
11865
11887
|
display: none;
|
|
11866
11888
|
}
|
|
11867
11889
|
|
|
11890
|
+
.skm-embed__controls-group {
|
|
11891
|
+
display: flex;
|
|
11892
|
+
align-items: center;
|
|
11893
|
+
gap: 8px;
|
|
11894
|
+
flex-wrap: wrap;
|
|
11895
|
+
}
|
|
11896
|
+
|
|
11868
11897
|
.skm-embed__button {
|
|
11869
11898
|
border: 1px solid #caba98;
|
|
11870
11899
|
background: #f5eedd;
|
|
@@ -11900,6 +11929,17 @@ const EMBED_CSS = `
|
|
|
11900
11929
|
cursor: default;
|
|
11901
11930
|
}
|
|
11902
11931
|
|
|
11932
|
+
.skm-embed__zoom {
|
|
11933
|
+
min-width: 48px;
|
|
11934
|
+
text-align: center;
|
|
11935
|
+
color: #8a6040;
|
|
11936
|
+
font-size: 11px;
|
|
11937
|
+
}
|
|
11938
|
+
|
|
11939
|
+
.skm-embed--dark .skm-embed__zoom {
|
|
11940
|
+
color: #d0b176;
|
|
11941
|
+
}
|
|
11942
|
+
|
|
11903
11943
|
.skm-embed__step {
|
|
11904
11944
|
margin-left: auto;
|
|
11905
11945
|
min-width: 96px;
|
|
@@ -11918,8 +11958,10 @@ class SketchmarkEmbed {
|
|
|
11918
11958
|
this.emitter = new EventEmitter();
|
|
11919
11959
|
this.animUnsub = null;
|
|
11920
11960
|
this.playInFlight = false;
|
|
11961
|
+
this.zoom = 1;
|
|
11921
11962
|
this.offsetX = 0;
|
|
11922
11963
|
this.offsetY = 0;
|
|
11964
|
+
this.autoFitEnabled = true;
|
|
11923
11965
|
this.motionFrame = null;
|
|
11924
11966
|
this.resizeObserver = null;
|
|
11925
11967
|
this.options = options;
|
|
@@ -11940,10 +11982,18 @@ class SketchmarkEmbed {
|
|
|
11940
11982
|
</div>
|
|
11941
11983
|
<div class="skm-embed__error"></div>
|
|
11942
11984
|
<div class="skm-embed__controls">
|
|
11943
|
-
<
|
|
11944
|
-
|
|
11945
|
-
|
|
11946
|
-
|
|
11985
|
+
<div class="skm-embed__controls-group">
|
|
11986
|
+
<button type="button" class="skm-embed__button" data-action="zoom-out">-</button>
|
|
11987
|
+
<span class="skm-embed__zoom">100%</span>
|
|
11988
|
+
<button type="button" class="skm-embed__button" data-action="zoom-in">+</button>
|
|
11989
|
+
<button type="button" class="skm-embed__button" data-action="fit">Reset</button>
|
|
11990
|
+
</div>
|
|
11991
|
+
<div class="skm-embed__controls-group">
|
|
11992
|
+
<button type="button" class="skm-embed__button" data-action="restart">Restart</button>
|
|
11993
|
+
<button type="button" class="skm-embed__button" data-action="prev">Prev</button>
|
|
11994
|
+
<button type="button" class="skm-embed__button" data-action="next">Next</button>
|
|
11995
|
+
<button type="button" class="skm-embed__button" data-action="play">Play</button>
|
|
11996
|
+
</div>
|
|
11947
11997
|
<span class="skm-embed__step">No steps</span>
|
|
11948
11998
|
</div>
|
|
11949
11999
|
`;
|
|
@@ -11953,12 +12003,19 @@ class SketchmarkEmbed {
|
|
|
11953
12003
|
this.errorElement = this.root.querySelector(".skm-embed__error");
|
|
11954
12004
|
this.controlsElement = this.root.querySelector(".skm-embed__controls");
|
|
11955
12005
|
this.stepInfoElement = this.root.querySelector(".skm-embed__step");
|
|
11956
|
-
this.
|
|
12006
|
+
this.zoomInfoElement = this.root.querySelector(".skm-embed__zoom");
|
|
12007
|
+
this.btnFit = this.root.querySelector('[data-action="fit"]');
|
|
12008
|
+
this.btnZoomIn = this.root.querySelector('[data-action="zoom-in"]');
|
|
12009
|
+
this.btnZoomOut = this.root.querySelector('[data-action="zoom-out"]');
|
|
12010
|
+
this.btnRestart = this.root.querySelector('[data-action="restart"]');
|
|
11957
12011
|
this.btnPrev = this.root.querySelector('[data-action="prev"]');
|
|
11958
12012
|
this.btnNext = this.root.querySelector('[data-action="next"]');
|
|
11959
12013
|
this.btnPlay = this.root.querySelector('[data-action="play"]');
|
|
11960
12014
|
this.controlsElement.classList.toggle("is-hidden", options.showControls === false);
|
|
11961
|
-
this.
|
|
12015
|
+
this.btnFit.addEventListener("click", () => this.resetView());
|
|
12016
|
+
this.btnZoomIn.addEventListener("click", () => this.zoomIn());
|
|
12017
|
+
this.btnZoomOut.addEventListener("click", () => this.zoomOut());
|
|
12018
|
+
this.btnRestart.addEventListener("click", () => this.resetAnimation());
|
|
11962
12019
|
this.btnPrev.addEventListener("click", () => this.prevStep());
|
|
11963
12020
|
this.btnNext.addEventListener("click", () => this.nextStep());
|
|
11964
12021
|
this.btnPlay.addEventListener("click", () => {
|
|
@@ -12004,7 +12061,12 @@ class SketchmarkEmbed {
|
|
|
12004
12061
|
this.animUnsub = null;
|
|
12005
12062
|
this.instance?.anim?.destroy();
|
|
12006
12063
|
this.instance = null;
|
|
12064
|
+
this.autoFitEnabled = true;
|
|
12065
|
+
this.zoom = 1;
|
|
12066
|
+
this.offsetX = 0;
|
|
12067
|
+
this.offsetY = 0;
|
|
12007
12068
|
this.diagramWrap.innerHTML = "";
|
|
12069
|
+
this.applyTransform();
|
|
12008
12070
|
try {
|
|
12009
12071
|
const instance = render({
|
|
12010
12072
|
container: this.diagramWrap,
|
|
@@ -12085,6 +12147,21 @@ class SketchmarkEmbed {
|
|
|
12085
12147
|
this.syncControls();
|
|
12086
12148
|
this.positionViewport(true);
|
|
12087
12149
|
}
|
|
12150
|
+
fitToViewport(animated = false) {
|
|
12151
|
+
if (!this.instance?.svg)
|
|
12152
|
+
return;
|
|
12153
|
+
this.autoFitEnabled = true;
|
|
12154
|
+
this.positionViewport(animated);
|
|
12155
|
+
}
|
|
12156
|
+
resetView(animated = false) {
|
|
12157
|
+
this.fitToViewport(animated);
|
|
12158
|
+
}
|
|
12159
|
+
zoomIn() {
|
|
12160
|
+
this.zoomAroundViewportCenter(1.2);
|
|
12161
|
+
}
|
|
12162
|
+
zoomOut() {
|
|
12163
|
+
this.zoomAroundViewportCenter(0.8);
|
|
12164
|
+
}
|
|
12088
12165
|
exportSVG(filename) {
|
|
12089
12166
|
this.instance?.exportSVG(filename);
|
|
12090
12167
|
}
|
|
@@ -12107,10 +12184,14 @@ class SketchmarkEmbed {
|
|
|
12107
12184
|
return typeof value === "number" ? `${value}px` : value;
|
|
12108
12185
|
}
|
|
12109
12186
|
syncControls() {
|
|
12187
|
+
this.syncAnimationControls();
|
|
12188
|
+
this.syncViewControls();
|
|
12189
|
+
}
|
|
12190
|
+
syncAnimationControls() {
|
|
12110
12191
|
const anim = this.instance?.anim;
|
|
12111
12192
|
if (!anim || !anim.total) {
|
|
12112
12193
|
this.stepInfoElement.textContent = "No steps";
|
|
12113
|
-
this.
|
|
12194
|
+
this.btnRestart.disabled = true;
|
|
12114
12195
|
this.btnPrev.disabled = true;
|
|
12115
12196
|
this.btnNext.disabled = true;
|
|
12116
12197
|
this.btnPlay.disabled = true;
|
|
@@ -12118,56 +12199,69 @@ class SketchmarkEmbed {
|
|
|
12118
12199
|
}
|
|
12119
12200
|
this.stepInfoElement.textContent =
|
|
12120
12201
|
anim.currentStep < 0 ? `${anim.total} steps` : `${anim.currentStep + 1} / ${anim.total}`;
|
|
12121
|
-
this.
|
|
12202
|
+
this.btnRestart.disabled = false;
|
|
12122
12203
|
this.btnPrev.disabled = !anim.canPrev;
|
|
12123
12204
|
this.btnNext.disabled = !anim.canNext;
|
|
12124
12205
|
this.btnPlay.disabled = this.playInFlight || !anim.canNext;
|
|
12125
12206
|
}
|
|
12207
|
+
syncViewControls() {
|
|
12208
|
+
const hasView = !!this.instance?.svg;
|
|
12209
|
+
const zoomMin = this.getZoomMin();
|
|
12210
|
+
const zoomMax = this.getZoomMax();
|
|
12211
|
+
this.zoomInfoElement.textContent = `${Math.round(this.zoom * 100)}%`;
|
|
12212
|
+
this.btnFit.disabled = !hasView;
|
|
12213
|
+
this.btnZoomOut.disabled = !hasView || this.zoom <= zoomMin + 0.001;
|
|
12214
|
+
this.btnZoomIn.disabled = !hasView || this.zoom >= zoomMax - 0.001;
|
|
12215
|
+
}
|
|
12126
12216
|
positionViewport(animated) {
|
|
12127
|
-
|
|
12128
|
-
|
|
12129
|
-
const svg = this.instance.svg;
|
|
12130
|
-
const svgWidth = parseFloat(svg.getAttribute("width") || "0");
|
|
12131
|
-
const svgHeight = parseFloat(svg.getAttribute("height") || "0");
|
|
12132
|
-
if (!svgWidth || !svgHeight)
|
|
12217
|
+
const size = this.getContentSize();
|
|
12218
|
+
if (!size)
|
|
12133
12219
|
return;
|
|
12220
|
+
const { width: svgWidth, height: svgHeight } = size;
|
|
12134
12221
|
const viewportRect = this.viewport.getBoundingClientRect();
|
|
12135
12222
|
const viewWidth = viewportRect.width || this.viewport.clientWidth;
|
|
12136
12223
|
const viewHeight = viewportRect.height || this.viewport.clientHeight;
|
|
12137
12224
|
if (!viewWidth || !viewHeight)
|
|
12138
12225
|
return;
|
|
12139
|
-
|
|
12226
|
+
if (this.autoFitEnabled) {
|
|
12227
|
+
this.zoom = this.getFitZoom(svgWidth, svgHeight, viewWidth, viewHeight);
|
|
12228
|
+
}
|
|
12229
|
+
this.syncViewControls();
|
|
12230
|
+
const scaledWidth = svgWidth * this.zoom;
|
|
12231
|
+
const scaledHeight = svgHeight * this.zoom;
|
|
12232
|
+
const focusTarget = this.getFocusTarget();
|
|
12233
|
+
const sceneIsLarge = scaledWidth > viewWidth || scaledHeight > viewHeight;
|
|
12140
12234
|
const shouldFocus = sceneIsLarge &&
|
|
12141
12235
|
this.options.autoFocus !== false &&
|
|
12142
|
-
!!
|
|
12236
|
+
!!focusTarget;
|
|
12143
12237
|
if (!shouldFocus) {
|
|
12144
|
-
this.animateTo(
|
|
12238
|
+
this.animateTo(scaledWidth <= viewWidth ? (viewWidth - scaledWidth) / 2 : 0, scaledHeight <= viewHeight ? (viewHeight - scaledHeight) / 2 : 0, animated);
|
|
12145
12239
|
return;
|
|
12146
12240
|
}
|
|
12147
|
-
const target = this.findTargetElement(
|
|
12241
|
+
const target = this.findTargetElement(focusTarget);
|
|
12148
12242
|
if (!target) {
|
|
12149
12243
|
this.animateTo(0, 0, animated);
|
|
12150
12244
|
return;
|
|
12151
12245
|
}
|
|
12152
|
-
const
|
|
12153
|
-
|
|
12154
|
-
|
|
12155
|
-
|
|
12156
|
-
|
|
12157
|
-
let nextX = viewWidth / 2 -
|
|
12158
|
-
let nextY = viewHeight / 2 -
|
|
12246
|
+
const targetBox = this.getTargetBox(target, viewportRect);
|
|
12247
|
+
if (!targetBox) {
|
|
12248
|
+
this.animateTo(0, 0, animated);
|
|
12249
|
+
return;
|
|
12250
|
+
}
|
|
12251
|
+
let nextX = viewWidth / 2 - targetBox.centerX;
|
|
12252
|
+
let nextY = viewHeight / 2 - targetBox.centerY;
|
|
12159
12253
|
const padding = this.options.focusPadding ?? 24;
|
|
12160
|
-
if (
|
|
12161
|
-
nextX = (viewWidth -
|
|
12254
|
+
if (scaledWidth <= viewWidth) {
|
|
12255
|
+
nextX = (viewWidth - scaledWidth) / 2;
|
|
12162
12256
|
}
|
|
12163
12257
|
else {
|
|
12164
|
-
nextX = clamp(nextX, viewWidth -
|
|
12258
|
+
nextX = clamp(nextX, viewWidth - scaledWidth - padding, padding);
|
|
12165
12259
|
}
|
|
12166
|
-
if (
|
|
12167
|
-
nextY = (viewHeight -
|
|
12260
|
+
if (scaledHeight <= viewHeight) {
|
|
12261
|
+
nextY = (viewHeight - scaledHeight) / 2;
|
|
12168
12262
|
}
|
|
12169
12263
|
else {
|
|
12170
|
-
nextY = clamp(nextY, viewHeight -
|
|
12264
|
+
nextY = clamp(nextY, viewHeight - scaledHeight - padding, padding);
|
|
12171
12265
|
}
|
|
12172
12266
|
this.animateTo(nextX, nextY, animated);
|
|
12173
12267
|
}
|
|
@@ -12199,7 +12293,78 @@ class SketchmarkEmbed {
|
|
|
12199
12293
|
this.motionFrame = requestAnimationFrame(frame);
|
|
12200
12294
|
}
|
|
12201
12295
|
applyTransform() {
|
|
12202
|
-
this.world.style.transform = `translate(${this.offsetX}px, ${this.offsetY}px)`;
|
|
12296
|
+
this.world.style.transform = `translate(${this.offsetX}px, ${this.offsetY}px) scale(${this.zoom})`;
|
|
12297
|
+
this.zoomInfoElement.textContent = `${Math.round(this.zoom * 100)}%`;
|
|
12298
|
+
}
|
|
12299
|
+
getContentSize() {
|
|
12300
|
+
if (!this.instance?.svg)
|
|
12301
|
+
return null;
|
|
12302
|
+
const svg = this.instance.svg;
|
|
12303
|
+
const width = parseFloat(svg.getAttribute("width") || "0");
|
|
12304
|
+
const height = parseFloat(svg.getAttribute("height") || "0");
|
|
12305
|
+
if (!width || !height)
|
|
12306
|
+
return null;
|
|
12307
|
+
return { width, height };
|
|
12308
|
+
}
|
|
12309
|
+
getFitZoom(svgWidth, svgHeight, viewWidth, viewHeight) {
|
|
12310
|
+
const padding = this.getFitPadding(viewWidth, viewHeight);
|
|
12311
|
+
const availableWidth = Math.max(viewWidth - padding * 2, 1);
|
|
12312
|
+
const availableHeight = Math.max(viewHeight - padding * 2, 1);
|
|
12313
|
+
const nextZoom = Math.min(availableWidth / svgWidth, availableHeight / svgHeight, 1);
|
|
12314
|
+
return clamp(nextZoom || 1, this.getZoomMin(), this.getZoomMax());
|
|
12315
|
+
}
|
|
12316
|
+
getFitPadding(viewWidth, viewHeight) {
|
|
12317
|
+
if (typeof this.options.fitPadding === "number") {
|
|
12318
|
+
return Math.max(0, this.options.fitPadding);
|
|
12319
|
+
}
|
|
12320
|
+
return Math.max(16, Math.min(40, Math.round(Math.min(viewWidth, viewHeight) * 0.08)));
|
|
12321
|
+
}
|
|
12322
|
+
getZoomMin() {
|
|
12323
|
+
return this.options.zoomMin ?? 0.08;
|
|
12324
|
+
}
|
|
12325
|
+
getZoomMax() {
|
|
12326
|
+
return this.options.zoomMax ?? 4;
|
|
12327
|
+
}
|
|
12328
|
+
zoomAroundViewportCenter(factor) {
|
|
12329
|
+
if (!this.instance?.svg)
|
|
12330
|
+
return;
|
|
12331
|
+
const pivotX = this.viewport.clientWidth / 2;
|
|
12332
|
+
const pivotY = this.viewport.clientHeight / 2;
|
|
12333
|
+
this.zoomTo(this.zoom * factor, pivotX, pivotY);
|
|
12334
|
+
}
|
|
12335
|
+
zoomTo(nextZoom, pivotX, pivotY) {
|
|
12336
|
+
const clampedZoom = clamp(nextZoom, this.getZoomMin(), this.getZoomMax());
|
|
12337
|
+
const ratio = clampedZoom / this.zoom;
|
|
12338
|
+
if (!Number.isFinite(ratio) || ratio === 1) {
|
|
12339
|
+
this.syncViewControls();
|
|
12340
|
+
return;
|
|
12341
|
+
}
|
|
12342
|
+
this.stopMotion();
|
|
12343
|
+
this.autoFitEnabled = false;
|
|
12344
|
+
this.offsetX = pivotX - (pivotX - this.offsetX) * ratio;
|
|
12345
|
+
this.offsetY = pivotY - (pivotY - this.offsetY) * ratio;
|
|
12346
|
+
this.zoom = clampedZoom;
|
|
12347
|
+
this.applyTransform();
|
|
12348
|
+
this.syncViewControls();
|
|
12349
|
+
}
|
|
12350
|
+
getTargetBox(target, viewportRect) {
|
|
12351
|
+
if (target instanceof SVGGraphicsElement) {
|
|
12352
|
+
try {
|
|
12353
|
+
const bounds = target.getBBox();
|
|
12354
|
+
return {
|
|
12355
|
+
centerX: (bounds.x + bounds.width / 2) * this.zoom,
|
|
12356
|
+
centerY: (bounds.y + bounds.height / 2) * this.zoom,
|
|
12357
|
+
};
|
|
12358
|
+
}
|
|
12359
|
+
catch {
|
|
12360
|
+
// Ignore and fall back to layout-based bounds below.
|
|
12361
|
+
}
|
|
12362
|
+
}
|
|
12363
|
+
const currentRect = target.getBoundingClientRect();
|
|
12364
|
+
return {
|
|
12365
|
+
centerX: currentRect.left - viewportRect.left - this.offsetX + currentRect.width / 2,
|
|
12366
|
+
centerY: currentRect.top - viewportRect.top - this.offsetY + currentRect.height / 2,
|
|
12367
|
+
};
|
|
12203
12368
|
}
|
|
12204
12369
|
getFocusTarget() {
|
|
12205
12370
|
const anim = this.instance?.anim;
|