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.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);
|
|
@@ -10049,7 +10070,7 @@ class AnimationController {
|
|
|
10049
10070
|
if (!nodeGuidePathEl(nodeEl) && !nodeEl.querySelector("path")?.style.strokeDasharray) {
|
|
10050
10071
|
prepareNodeForDraw(nodeEl);
|
|
10051
10072
|
}
|
|
10052
|
-
animateNodeDraw(nodeEl, step.duration ?? ANIMATION.nodeStrokeDur);
|
|
10073
|
+
animateNodeDraw(nodeEl, step.duration ?? ANIMATION.nodeStrokeDur, step.duration ?? ANIMATION.textRevealMs);
|
|
10053
10074
|
}
|
|
10054
10075
|
}
|
|
10055
10076
|
// ── erase ─────────────────────────────────────────────────
|
|
@@ -11850,6 +11871,7 @@ const EMBED_CSS = `
|
|
|
11850
11871
|
|
|
11851
11872
|
.skm-embed__controls {
|
|
11852
11873
|
display: flex;
|
|
11874
|
+
flex-wrap: wrap;
|
|
11853
11875
|
align-items: center;
|
|
11854
11876
|
gap: 8px;
|
|
11855
11877
|
padding: 10px 12px;
|
|
@@ -11867,6 +11889,13 @@ const EMBED_CSS = `
|
|
|
11867
11889
|
display: none;
|
|
11868
11890
|
}
|
|
11869
11891
|
|
|
11892
|
+
.skm-embed__controls-group {
|
|
11893
|
+
display: flex;
|
|
11894
|
+
align-items: center;
|
|
11895
|
+
gap: 8px;
|
|
11896
|
+
flex-wrap: wrap;
|
|
11897
|
+
}
|
|
11898
|
+
|
|
11870
11899
|
.skm-embed__button {
|
|
11871
11900
|
border: 1px solid #caba98;
|
|
11872
11901
|
background: #f5eedd;
|
|
@@ -11902,6 +11931,17 @@ const EMBED_CSS = `
|
|
|
11902
11931
|
cursor: default;
|
|
11903
11932
|
}
|
|
11904
11933
|
|
|
11934
|
+
.skm-embed__zoom {
|
|
11935
|
+
min-width: 48px;
|
|
11936
|
+
text-align: center;
|
|
11937
|
+
color: #8a6040;
|
|
11938
|
+
font-size: 11px;
|
|
11939
|
+
}
|
|
11940
|
+
|
|
11941
|
+
.skm-embed--dark .skm-embed__zoom {
|
|
11942
|
+
color: #d0b176;
|
|
11943
|
+
}
|
|
11944
|
+
|
|
11905
11945
|
.skm-embed__step {
|
|
11906
11946
|
margin-left: auto;
|
|
11907
11947
|
min-width: 96px;
|
|
@@ -11920,8 +11960,10 @@ class SketchmarkEmbed {
|
|
|
11920
11960
|
this.emitter = new EventEmitter();
|
|
11921
11961
|
this.animUnsub = null;
|
|
11922
11962
|
this.playInFlight = false;
|
|
11963
|
+
this.zoom = 1;
|
|
11923
11964
|
this.offsetX = 0;
|
|
11924
11965
|
this.offsetY = 0;
|
|
11966
|
+
this.autoFitEnabled = true;
|
|
11925
11967
|
this.motionFrame = null;
|
|
11926
11968
|
this.resizeObserver = null;
|
|
11927
11969
|
this.options = options;
|
|
@@ -11942,10 +11984,18 @@ class SketchmarkEmbed {
|
|
|
11942
11984
|
</div>
|
|
11943
11985
|
<div class="skm-embed__error"></div>
|
|
11944
11986
|
<div class="skm-embed__controls">
|
|
11945
|
-
<
|
|
11946
|
-
|
|
11947
|
-
|
|
11948
|
-
|
|
11987
|
+
<div class="skm-embed__controls-group">
|
|
11988
|
+
<button type="button" class="skm-embed__button" data-action="zoom-out">-</button>
|
|
11989
|
+
<span class="skm-embed__zoom">100%</span>
|
|
11990
|
+
<button type="button" class="skm-embed__button" data-action="zoom-in">+</button>
|
|
11991
|
+
<button type="button" class="skm-embed__button" data-action="fit">Reset</button>
|
|
11992
|
+
</div>
|
|
11993
|
+
<div class="skm-embed__controls-group">
|
|
11994
|
+
<button type="button" class="skm-embed__button" data-action="restart">Restart</button>
|
|
11995
|
+
<button type="button" class="skm-embed__button" data-action="prev">Prev</button>
|
|
11996
|
+
<button type="button" class="skm-embed__button" data-action="next">Next</button>
|
|
11997
|
+
<button type="button" class="skm-embed__button" data-action="play">Play</button>
|
|
11998
|
+
</div>
|
|
11949
11999
|
<span class="skm-embed__step">No steps</span>
|
|
11950
12000
|
</div>
|
|
11951
12001
|
`;
|
|
@@ -11955,12 +12005,19 @@ class SketchmarkEmbed {
|
|
|
11955
12005
|
this.errorElement = this.root.querySelector(".skm-embed__error");
|
|
11956
12006
|
this.controlsElement = this.root.querySelector(".skm-embed__controls");
|
|
11957
12007
|
this.stepInfoElement = this.root.querySelector(".skm-embed__step");
|
|
11958
|
-
this.
|
|
12008
|
+
this.zoomInfoElement = this.root.querySelector(".skm-embed__zoom");
|
|
12009
|
+
this.btnFit = this.root.querySelector('[data-action="fit"]');
|
|
12010
|
+
this.btnZoomIn = this.root.querySelector('[data-action="zoom-in"]');
|
|
12011
|
+
this.btnZoomOut = this.root.querySelector('[data-action="zoom-out"]');
|
|
12012
|
+
this.btnRestart = this.root.querySelector('[data-action="restart"]');
|
|
11959
12013
|
this.btnPrev = this.root.querySelector('[data-action="prev"]');
|
|
11960
12014
|
this.btnNext = this.root.querySelector('[data-action="next"]');
|
|
11961
12015
|
this.btnPlay = this.root.querySelector('[data-action="play"]');
|
|
11962
12016
|
this.controlsElement.classList.toggle("is-hidden", options.showControls === false);
|
|
11963
|
-
this.
|
|
12017
|
+
this.btnFit.addEventListener("click", () => this.resetView());
|
|
12018
|
+
this.btnZoomIn.addEventListener("click", () => this.zoomIn());
|
|
12019
|
+
this.btnZoomOut.addEventListener("click", () => this.zoomOut());
|
|
12020
|
+
this.btnRestart.addEventListener("click", () => this.resetAnimation());
|
|
11964
12021
|
this.btnPrev.addEventListener("click", () => this.prevStep());
|
|
11965
12022
|
this.btnNext.addEventListener("click", () => this.nextStep());
|
|
11966
12023
|
this.btnPlay.addEventListener("click", () => {
|
|
@@ -12006,7 +12063,12 @@ class SketchmarkEmbed {
|
|
|
12006
12063
|
this.animUnsub = null;
|
|
12007
12064
|
this.instance?.anim?.destroy();
|
|
12008
12065
|
this.instance = null;
|
|
12066
|
+
this.autoFitEnabled = true;
|
|
12067
|
+
this.zoom = 1;
|
|
12068
|
+
this.offsetX = 0;
|
|
12069
|
+
this.offsetY = 0;
|
|
12009
12070
|
this.diagramWrap.innerHTML = "";
|
|
12071
|
+
this.applyTransform();
|
|
12010
12072
|
try {
|
|
12011
12073
|
const instance = render({
|
|
12012
12074
|
container: this.diagramWrap,
|
|
@@ -12087,6 +12149,21 @@ class SketchmarkEmbed {
|
|
|
12087
12149
|
this.syncControls();
|
|
12088
12150
|
this.positionViewport(true);
|
|
12089
12151
|
}
|
|
12152
|
+
fitToViewport(animated = false) {
|
|
12153
|
+
if (!this.instance?.svg)
|
|
12154
|
+
return;
|
|
12155
|
+
this.autoFitEnabled = true;
|
|
12156
|
+
this.positionViewport(animated);
|
|
12157
|
+
}
|
|
12158
|
+
resetView(animated = false) {
|
|
12159
|
+
this.fitToViewport(animated);
|
|
12160
|
+
}
|
|
12161
|
+
zoomIn() {
|
|
12162
|
+
this.zoomAroundViewportCenter(1.2);
|
|
12163
|
+
}
|
|
12164
|
+
zoomOut() {
|
|
12165
|
+
this.zoomAroundViewportCenter(0.8);
|
|
12166
|
+
}
|
|
12090
12167
|
exportSVG(filename) {
|
|
12091
12168
|
this.instance?.exportSVG(filename);
|
|
12092
12169
|
}
|
|
@@ -12109,10 +12186,14 @@ class SketchmarkEmbed {
|
|
|
12109
12186
|
return typeof value === "number" ? `${value}px` : value;
|
|
12110
12187
|
}
|
|
12111
12188
|
syncControls() {
|
|
12189
|
+
this.syncAnimationControls();
|
|
12190
|
+
this.syncViewControls();
|
|
12191
|
+
}
|
|
12192
|
+
syncAnimationControls() {
|
|
12112
12193
|
const anim = this.instance?.anim;
|
|
12113
12194
|
if (!anim || !anim.total) {
|
|
12114
12195
|
this.stepInfoElement.textContent = "No steps";
|
|
12115
|
-
this.
|
|
12196
|
+
this.btnRestart.disabled = true;
|
|
12116
12197
|
this.btnPrev.disabled = true;
|
|
12117
12198
|
this.btnNext.disabled = true;
|
|
12118
12199
|
this.btnPlay.disabled = true;
|
|
@@ -12120,56 +12201,69 @@ class SketchmarkEmbed {
|
|
|
12120
12201
|
}
|
|
12121
12202
|
this.stepInfoElement.textContent =
|
|
12122
12203
|
anim.currentStep < 0 ? `${anim.total} steps` : `${anim.currentStep + 1} / ${anim.total}`;
|
|
12123
|
-
this.
|
|
12204
|
+
this.btnRestart.disabled = false;
|
|
12124
12205
|
this.btnPrev.disabled = !anim.canPrev;
|
|
12125
12206
|
this.btnNext.disabled = !anim.canNext;
|
|
12126
12207
|
this.btnPlay.disabled = this.playInFlight || !anim.canNext;
|
|
12127
12208
|
}
|
|
12209
|
+
syncViewControls() {
|
|
12210
|
+
const hasView = !!this.instance?.svg;
|
|
12211
|
+
const zoomMin = this.getZoomMin();
|
|
12212
|
+
const zoomMax = this.getZoomMax();
|
|
12213
|
+
this.zoomInfoElement.textContent = `${Math.round(this.zoom * 100)}%`;
|
|
12214
|
+
this.btnFit.disabled = !hasView;
|
|
12215
|
+
this.btnZoomOut.disabled = !hasView || this.zoom <= zoomMin + 0.001;
|
|
12216
|
+
this.btnZoomIn.disabled = !hasView || this.zoom >= zoomMax - 0.001;
|
|
12217
|
+
}
|
|
12128
12218
|
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)
|
|
12219
|
+
const size = this.getContentSize();
|
|
12220
|
+
if (!size)
|
|
12135
12221
|
return;
|
|
12222
|
+
const { width: svgWidth, height: svgHeight } = size;
|
|
12136
12223
|
const viewportRect = this.viewport.getBoundingClientRect();
|
|
12137
12224
|
const viewWidth = viewportRect.width || this.viewport.clientWidth;
|
|
12138
12225
|
const viewHeight = viewportRect.height || this.viewport.clientHeight;
|
|
12139
12226
|
if (!viewWidth || !viewHeight)
|
|
12140
12227
|
return;
|
|
12141
|
-
|
|
12228
|
+
if (this.autoFitEnabled) {
|
|
12229
|
+
this.zoom = this.getFitZoom(svgWidth, svgHeight, viewWidth, viewHeight);
|
|
12230
|
+
}
|
|
12231
|
+
this.syncViewControls();
|
|
12232
|
+
const scaledWidth = svgWidth * this.zoom;
|
|
12233
|
+
const scaledHeight = svgHeight * this.zoom;
|
|
12234
|
+
const focusTarget = this.getFocusTarget();
|
|
12235
|
+
const sceneIsLarge = scaledWidth > viewWidth || scaledHeight > viewHeight;
|
|
12142
12236
|
const shouldFocus = sceneIsLarge &&
|
|
12143
12237
|
this.options.autoFocus !== false &&
|
|
12144
|
-
!!
|
|
12238
|
+
!!focusTarget;
|
|
12145
12239
|
if (!shouldFocus) {
|
|
12146
|
-
this.animateTo(
|
|
12240
|
+
this.animateTo(scaledWidth <= viewWidth ? (viewWidth - scaledWidth) / 2 : 0, scaledHeight <= viewHeight ? (viewHeight - scaledHeight) / 2 : 0, animated);
|
|
12147
12241
|
return;
|
|
12148
12242
|
}
|
|
12149
|
-
const target = this.findTargetElement(
|
|
12243
|
+
const target = this.findTargetElement(focusTarget);
|
|
12150
12244
|
if (!target) {
|
|
12151
12245
|
this.animateTo(0, 0, animated);
|
|
12152
12246
|
return;
|
|
12153
12247
|
}
|
|
12154
|
-
const
|
|
12155
|
-
|
|
12156
|
-
|
|
12157
|
-
|
|
12158
|
-
|
|
12159
|
-
let nextX = viewWidth / 2 -
|
|
12160
|
-
let nextY = viewHeight / 2 -
|
|
12248
|
+
const targetBox = this.getTargetBox(target, viewportRect);
|
|
12249
|
+
if (!targetBox) {
|
|
12250
|
+
this.animateTo(0, 0, animated);
|
|
12251
|
+
return;
|
|
12252
|
+
}
|
|
12253
|
+
let nextX = viewWidth / 2 - targetBox.centerX;
|
|
12254
|
+
let nextY = viewHeight / 2 - targetBox.centerY;
|
|
12161
12255
|
const padding = this.options.focusPadding ?? 24;
|
|
12162
|
-
if (
|
|
12163
|
-
nextX = (viewWidth -
|
|
12256
|
+
if (scaledWidth <= viewWidth) {
|
|
12257
|
+
nextX = (viewWidth - scaledWidth) / 2;
|
|
12164
12258
|
}
|
|
12165
12259
|
else {
|
|
12166
|
-
nextX = clamp(nextX, viewWidth -
|
|
12260
|
+
nextX = clamp(nextX, viewWidth - scaledWidth - padding, padding);
|
|
12167
12261
|
}
|
|
12168
|
-
if (
|
|
12169
|
-
nextY = (viewHeight -
|
|
12262
|
+
if (scaledHeight <= viewHeight) {
|
|
12263
|
+
nextY = (viewHeight - scaledHeight) / 2;
|
|
12170
12264
|
}
|
|
12171
12265
|
else {
|
|
12172
|
-
nextY = clamp(nextY, viewHeight -
|
|
12266
|
+
nextY = clamp(nextY, viewHeight - scaledHeight - padding, padding);
|
|
12173
12267
|
}
|
|
12174
12268
|
this.animateTo(nextX, nextY, animated);
|
|
12175
12269
|
}
|
|
@@ -12201,7 +12295,78 @@ class SketchmarkEmbed {
|
|
|
12201
12295
|
this.motionFrame = requestAnimationFrame(frame);
|
|
12202
12296
|
}
|
|
12203
12297
|
applyTransform() {
|
|
12204
|
-
this.world.style.transform = `translate(${this.offsetX}px, ${this.offsetY}px)`;
|
|
12298
|
+
this.world.style.transform = `translate(${this.offsetX}px, ${this.offsetY}px) scale(${this.zoom})`;
|
|
12299
|
+
this.zoomInfoElement.textContent = `${Math.round(this.zoom * 100)}%`;
|
|
12300
|
+
}
|
|
12301
|
+
getContentSize() {
|
|
12302
|
+
if (!this.instance?.svg)
|
|
12303
|
+
return null;
|
|
12304
|
+
const svg = this.instance.svg;
|
|
12305
|
+
const width = parseFloat(svg.getAttribute("width") || "0");
|
|
12306
|
+
const height = parseFloat(svg.getAttribute("height") || "0");
|
|
12307
|
+
if (!width || !height)
|
|
12308
|
+
return null;
|
|
12309
|
+
return { width, height };
|
|
12310
|
+
}
|
|
12311
|
+
getFitZoom(svgWidth, svgHeight, viewWidth, viewHeight) {
|
|
12312
|
+
const padding = this.getFitPadding(viewWidth, viewHeight);
|
|
12313
|
+
const availableWidth = Math.max(viewWidth - padding * 2, 1);
|
|
12314
|
+
const availableHeight = Math.max(viewHeight - padding * 2, 1);
|
|
12315
|
+
const nextZoom = Math.min(availableWidth / svgWidth, availableHeight / svgHeight, 1);
|
|
12316
|
+
return clamp(nextZoom || 1, this.getZoomMin(), this.getZoomMax());
|
|
12317
|
+
}
|
|
12318
|
+
getFitPadding(viewWidth, viewHeight) {
|
|
12319
|
+
if (typeof this.options.fitPadding === "number") {
|
|
12320
|
+
return Math.max(0, this.options.fitPadding);
|
|
12321
|
+
}
|
|
12322
|
+
return Math.max(16, Math.min(40, Math.round(Math.min(viewWidth, viewHeight) * 0.08)));
|
|
12323
|
+
}
|
|
12324
|
+
getZoomMin() {
|
|
12325
|
+
return this.options.zoomMin ?? 0.08;
|
|
12326
|
+
}
|
|
12327
|
+
getZoomMax() {
|
|
12328
|
+
return this.options.zoomMax ?? 4;
|
|
12329
|
+
}
|
|
12330
|
+
zoomAroundViewportCenter(factor) {
|
|
12331
|
+
if (!this.instance?.svg)
|
|
12332
|
+
return;
|
|
12333
|
+
const pivotX = this.viewport.clientWidth / 2;
|
|
12334
|
+
const pivotY = this.viewport.clientHeight / 2;
|
|
12335
|
+
this.zoomTo(this.zoom * factor, pivotX, pivotY);
|
|
12336
|
+
}
|
|
12337
|
+
zoomTo(nextZoom, pivotX, pivotY) {
|
|
12338
|
+
const clampedZoom = clamp(nextZoom, this.getZoomMin(), this.getZoomMax());
|
|
12339
|
+
const ratio = clampedZoom / this.zoom;
|
|
12340
|
+
if (!Number.isFinite(ratio) || ratio === 1) {
|
|
12341
|
+
this.syncViewControls();
|
|
12342
|
+
return;
|
|
12343
|
+
}
|
|
12344
|
+
this.stopMotion();
|
|
12345
|
+
this.autoFitEnabled = false;
|
|
12346
|
+
this.offsetX = pivotX - (pivotX - this.offsetX) * ratio;
|
|
12347
|
+
this.offsetY = pivotY - (pivotY - this.offsetY) * ratio;
|
|
12348
|
+
this.zoom = clampedZoom;
|
|
12349
|
+
this.applyTransform();
|
|
12350
|
+
this.syncViewControls();
|
|
12351
|
+
}
|
|
12352
|
+
getTargetBox(target, viewportRect) {
|
|
12353
|
+
if (target instanceof SVGGraphicsElement) {
|
|
12354
|
+
try {
|
|
12355
|
+
const bounds = target.getBBox();
|
|
12356
|
+
return {
|
|
12357
|
+
centerX: (bounds.x + bounds.width / 2) * this.zoom,
|
|
12358
|
+
centerY: (bounds.y + bounds.height / 2) * this.zoom,
|
|
12359
|
+
};
|
|
12360
|
+
}
|
|
12361
|
+
catch {
|
|
12362
|
+
// Ignore and fall back to layout-based bounds below.
|
|
12363
|
+
}
|
|
12364
|
+
}
|
|
12365
|
+
const currentRect = target.getBoundingClientRect();
|
|
12366
|
+
return {
|
|
12367
|
+
centerX: currentRect.left - viewportRect.left - this.offsetX + currentRect.width / 2,
|
|
12368
|
+
centerY: currentRect.top - viewportRect.top - this.offsetY + currentRect.height / 2,
|
|
12369
|
+
};
|
|
12205
12370
|
}
|
|
12206
12371
|
getFocusTarget() {
|
|
12207
12372
|
const anim = this.instance?.anim;
|