sketchmark 1.3.0 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -149,7 +149,7 @@ const embed = new SketchmarkEmbed({
149
149
  });
150
150
  ```
151
151
 
152
- Use `SketchmarkCanvas` for the full playground-style surface, and `SketchmarkEmbed` for fixed-size embeds that clip overflow, auto-fit large diagrams, and expose built-in zoom, playback, caption, and TTS controls.
152
+ Use `SketchmarkCanvas` for the full playground-style surface, and `SketchmarkEmbed` for fixed-size embeds that clip overflow, auto-fit large diagrams, support drag-to-pan plus wheel/trackpad zoom, and expose built-in zoom, playback, caption, and TTS controls.
153
153
 
154
154
  ---
155
155
 
package/dist/index.cjs CHANGED
@@ -12656,6 +12656,12 @@ const EMBED_CSS = `
12656
12656
  overflow: hidden;
12657
12657
  min-height: 0;
12658
12658
  background: inherit;
12659
+ cursor: grab;
12660
+ touch-action: none;
12661
+ }
12662
+
12663
+ .skm-embed__viewport.is-panning {
12664
+ cursor: grabbing;
12659
12665
  }
12660
12666
 
12661
12667
  .skm-embed__world {
@@ -12786,6 +12792,83 @@ class SketchmarkEmbed {
12786
12792
  this.autoFitEnabled = true;
12787
12793
  this.motionFrame = null;
12788
12794
  this.resizeObserver = null;
12795
+ this.isPanning = false;
12796
+ this.panMoved = false;
12797
+ this.activePointerId = null;
12798
+ this.lastPointerX = 0;
12799
+ this.lastPointerY = 0;
12800
+ this.suppressClickUntil = 0;
12801
+ this.onPointerDown = (event) => {
12802
+ if (event.button !== 0 && event.button !== 1)
12803
+ return;
12804
+ this.isPanning = true;
12805
+ this.panMoved = false;
12806
+ this.activePointerId = event.pointerId;
12807
+ this.lastPointerX = event.clientX;
12808
+ this.lastPointerY = event.clientY;
12809
+ try {
12810
+ this.viewport.setPointerCapture(event.pointerId);
12811
+ }
12812
+ catch {
12813
+ // Ignore pointer capture failures.
12814
+ }
12815
+ };
12816
+ this.onPointerMove = (event) => {
12817
+ if (!this.isPanning)
12818
+ return;
12819
+ if (this.activePointerId !== null && event.pointerId !== this.activePointerId)
12820
+ return;
12821
+ const dx = event.clientX - this.lastPointerX;
12822
+ const dy = event.clientY - this.lastPointerY;
12823
+ if (!this.panMoved && Math.abs(dx) + Math.abs(dy) > 4) {
12824
+ this.panMoved = true;
12825
+ this.viewport.classList.add("is-panning");
12826
+ }
12827
+ if (this.panMoved) {
12828
+ this.stopMotion();
12829
+ this.autoFitEnabled = false;
12830
+ this.offsetX += dx;
12831
+ this.offsetY += dy;
12832
+ this.applyTransform();
12833
+ this.syncViewControls();
12834
+ }
12835
+ this.lastPointerX = event.clientX;
12836
+ this.lastPointerY = event.clientY;
12837
+ };
12838
+ this.onStopPanning = (event) => {
12839
+ if (this.activePointerId !== null && event?.pointerId != null && event.pointerId !== this.activePointerId)
12840
+ return;
12841
+ if (this.panMoved)
12842
+ this.suppressClickUntil = performance.now() + 180;
12843
+ if (this.activePointerId !== null && this.viewport.hasPointerCapture?.(this.activePointerId)) {
12844
+ try {
12845
+ this.viewport.releasePointerCapture(this.activePointerId);
12846
+ }
12847
+ catch {
12848
+ // Ignore pointer capture release failures.
12849
+ }
12850
+ }
12851
+ this.activePointerId = null;
12852
+ this.isPanning = false;
12853
+ this.panMoved = false;
12854
+ this.viewport.classList.remove("is-panning");
12855
+ };
12856
+ this.onViewportClick = (event) => {
12857
+ if (performance.now() <= this.suppressClickUntil) {
12858
+ event.preventDefault();
12859
+ event.stopPropagation();
12860
+ }
12861
+ };
12862
+ this.onWheel = (event) => {
12863
+ if (!this.instance?.svg)
12864
+ return;
12865
+ event.preventDefault();
12866
+ const rect = this.viewport.getBoundingClientRect();
12867
+ const pivotX = event.clientX - rect.left;
12868
+ const pivotY = event.clientY - rect.top;
12869
+ const factor = Math.exp(-event.deltaY * 0.0015);
12870
+ this.zoomTo(this.zoom * factor, pivotX, pivotY);
12871
+ };
12789
12872
  this.options = options;
12790
12873
  this.dsl = normalizeNewlines(options.dsl);
12791
12874
  this.theme = options.theme ?? "light";
@@ -12853,6 +12936,13 @@ class SketchmarkEmbed {
12853
12936
  });
12854
12937
  this.btnCaption.addEventListener("click", () => this.setCaptionVisible(!this.showCaption));
12855
12938
  this.btnTts.addEventListener("click", () => this.setTtsEnabled(!this.getTtsEnabled()));
12939
+ this.viewport.addEventListener("pointerdown", this.onPointerDown);
12940
+ this.viewport.addEventListener("pointermove", this.onPointerMove);
12941
+ this.viewport.addEventListener("pointerup", this.onStopPanning);
12942
+ this.viewport.addEventListener("pointercancel", this.onStopPanning);
12943
+ this.viewport.addEventListener("lostpointercapture", this.onStopPanning);
12944
+ this.viewport.addEventListener("click", this.onViewportClick);
12945
+ this.viewport.addEventListener("wheel", this.onWheel, { passive: false });
12856
12946
  if (typeof ResizeObserver !== "undefined") {
12857
12947
  this.resizeObserver = new ResizeObserver(() => {
12858
12948
  this.positionViewport(false);
@@ -12930,9 +13020,9 @@ class SketchmarkEmbed {
12930
13020
  this.animUnsub = instance.anim.on((event) => {
12931
13021
  this.syncControls();
12932
13022
  if (event.type === "step-change") {
12933
- if (this.options.autoFocusOnStep !== false) {
13023
+ if (this.options.autoFocus !== false && this.options.autoFocusOnStep !== false) {
12934
13024
  requestAnimationFrame(() => {
12935
- window.setTimeout(() => this.positionViewport(true), 40);
13025
+ window.setTimeout(() => this.focusCurrentStep(true), 40);
12936
13026
  });
12937
13027
  }
12938
13028
  this.emitter.emit("stepchange", {
@@ -12976,21 +13066,24 @@ class SketchmarkEmbed {
12976
13066
  return;
12977
13067
  this.instance.anim.next();
12978
13068
  this.syncControls();
12979
- this.positionViewport(true);
13069
+ if (this.options.autoFocus !== false && this.options.autoFocusOnStep !== false) {
13070
+ this.focusCurrentStep(true);
13071
+ }
12980
13072
  }
12981
13073
  prevStep() {
12982
13074
  if (!this.instance)
12983
13075
  return;
12984
13076
  this.instance.anim.prev();
12985
13077
  this.syncControls();
12986
- this.positionViewport(true);
13078
+ if (this.options.autoFocus !== false && this.options.autoFocusOnStep !== false) {
13079
+ this.focusCurrentStep(true);
13080
+ }
12987
13081
  }
12988
13082
  resetAnimation() {
12989
13083
  if (!this.instance)
12990
13084
  return;
12991
13085
  this.instance.anim.reset();
12992
13086
  this.syncControls();
12993
- this.positionViewport(true);
12994
13087
  }
12995
13088
  fitToViewport(animated = false) {
12996
13089
  if (!this.instance?.svg)
@@ -13019,6 +13112,13 @@ class SketchmarkEmbed {
13019
13112
  this.instance?.anim?.destroy();
13020
13113
  this.instance = null;
13021
13114
  this.resizeObserver?.disconnect();
13115
+ this.viewport.removeEventListener("pointerdown", this.onPointerDown);
13116
+ this.viewport.removeEventListener("pointermove", this.onPointerMove);
13117
+ this.viewport.removeEventListener("pointerup", this.onStopPanning);
13118
+ this.viewport.removeEventListener("pointercancel", this.onStopPanning);
13119
+ this.viewport.removeEventListener("lostpointercapture", this.onStopPanning);
13120
+ this.viewport.removeEventListener("click", this.onViewportClick);
13121
+ this.viewport.removeEventListener("wheel", this.onWheel);
13022
13122
  this.root.remove();
13023
13123
  }
13024
13124
  applySize(width, height) {
@@ -13081,6 +13181,10 @@ class SketchmarkEmbed {
13081
13181
  this.options.autoFocus !== false &&
13082
13182
  !!focusTarget;
13083
13183
  if (!shouldFocus) {
13184
+ if (!this.autoFitEnabled) {
13185
+ this.applyTransform();
13186
+ return;
13187
+ }
13084
13188
  this.animateTo(scaledWidth <= viewWidth ? (viewWidth - scaledWidth) / 2 : 0, scaledHeight <= viewHeight ? (viewHeight - scaledHeight) / 2 : 0, animated);
13085
13189
  return;
13086
13190
  }
@@ -13193,6 +13297,56 @@ class SketchmarkEmbed {
13193
13297
  this.applyTransform();
13194
13298
  this.syncViewControls();
13195
13299
  }
13300
+ focusCurrentStep(animated) {
13301
+ const anim = this.instance?.anim;
13302
+ if (!anim || anim.currentStep < 0 || anim.currentStep >= anim.total)
13303
+ return;
13304
+ const targetId = this.getStepTarget(anim.steps[anim.currentStep]);
13305
+ if (targetId)
13306
+ this.focusTarget(targetId, animated);
13307
+ }
13308
+ focusTarget(targetId, animated) {
13309
+ const size = this.getContentSize();
13310
+ if (!size)
13311
+ return;
13312
+ const viewportRect = this.viewport.getBoundingClientRect();
13313
+ const viewWidth = viewportRect.width || this.viewport.clientWidth;
13314
+ const viewHeight = viewportRect.height || this.viewport.clientHeight;
13315
+ if (!viewWidth || !viewHeight)
13316
+ return;
13317
+ const target = this.findTargetElement(targetId);
13318
+ if (!target)
13319
+ return;
13320
+ const targetBox = this.getTargetBox(target, viewportRect);
13321
+ if (!targetBox)
13322
+ return;
13323
+ const centerX = this.offsetX + targetBox.centerX;
13324
+ const centerY = this.offsetY + targetBox.centerY;
13325
+ const padding = this.options.focusPadding ?? 24;
13326
+ if (centerX >= padding &&
13327
+ centerX <= viewWidth - padding &&
13328
+ centerY >= padding &&
13329
+ centerY <= viewHeight - padding) {
13330
+ return;
13331
+ }
13332
+ const scaledWidth = size.width * this.zoom;
13333
+ const scaledHeight = size.height * this.zoom;
13334
+ let nextX = viewWidth / 2 - targetBox.centerX;
13335
+ let nextY = viewHeight / 2 - targetBox.centerY;
13336
+ if (scaledWidth <= viewWidth) {
13337
+ nextX = (viewWidth - scaledWidth) / 2;
13338
+ }
13339
+ else {
13340
+ nextX = clamp(nextX, viewWidth - scaledWidth - padding, padding);
13341
+ }
13342
+ if (scaledHeight <= viewHeight) {
13343
+ nextY = (viewHeight - scaledHeight) / 2;
13344
+ }
13345
+ else {
13346
+ nextY = clamp(nextY, viewHeight - scaledHeight - padding, padding);
13347
+ }
13348
+ this.animateTo(nextX, nextY, animated);
13349
+ }
13196
13350
  applyCaptionVisibility(instance) {
13197
13351
  const caption = instance?.anim.captionElement;
13198
13352
  if (!caption)