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/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
- // Make text visible but clipped to zero width
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
- // We need to wait for text to be visible before we can measure it
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
- const bbox = textEl.getBBox?.();
8996
- if (!bbox || bbox.width === 0) {
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
- <button type="button" class="skm-embed__button" data-action="reset">Reset</button>
11946
- <button type="button" class="skm-embed__button" data-action="prev">Prev</button>
11947
- <button type="button" class="skm-embed__button" data-action="next">Next</button>
11948
- <button type="button" class="skm-embed__button" data-action="play">Play</button>
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.btnReset = this.root.querySelector('[data-action="reset"]');
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.btnReset.addEventListener("click", () => this.resetAnimation());
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.btnReset.disabled = true;
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.btnReset.disabled = false;
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
- if (!this.instance?.svg)
12130
- return;
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
- const sceneIsLarge = svgWidth > viewWidth || svgHeight > viewHeight;
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
- !!this.getFocusTarget();
12238
+ !!focusTarget;
12145
12239
  if (!shouldFocus) {
12146
- this.animateTo(svgWidth <= viewWidth ? (viewWidth - svgWidth) / 2 : 0, svgHeight <= viewHeight ? (viewHeight - svgHeight) / 2 : 0, animated);
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(this.getFocusTarget());
12243
+ const target = this.findTargetElement(focusTarget);
12150
12244
  if (!target) {
12151
12245
  this.animateTo(0, 0, animated);
12152
12246
  return;
12153
12247
  }
12154
- const currentRect = target.getBoundingClientRect();
12155
- const sceneX = currentRect.left - viewportRect.left - this.offsetX;
12156
- const sceneY = currentRect.top - viewportRect.top - this.offsetY;
12157
- const targetCenterX = sceneX + currentRect.width / 2;
12158
- const targetCenterY = sceneY + currentRect.height / 2;
12159
- let nextX = viewWidth / 2 - targetCenterX;
12160
- let nextY = viewHeight / 2 - targetCenterY;
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 (svgWidth <= viewWidth) {
12163
- nextX = (viewWidth - svgWidth) / 2;
12256
+ if (scaledWidth <= viewWidth) {
12257
+ nextX = (viewWidth - scaledWidth) / 2;
12164
12258
  }
12165
12259
  else {
12166
- nextX = clamp(nextX, viewWidth - svgWidth - padding, padding);
12260
+ nextX = clamp(nextX, viewWidth - scaledWidth - padding, padding);
12167
12261
  }
12168
- if (svgHeight <= viewHeight) {
12169
- nextY = (viewHeight - svgHeight) / 2;
12262
+ if (scaledHeight <= viewHeight) {
12263
+ nextY = (viewHeight - scaledHeight) / 2;
12170
12264
  }
12171
12265
  else {
12172
- nextY = clamp(nextY, viewHeight - svgHeight - padding, padding);
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;