@tsdraw/core 0.8.3 → 0.8.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.
package/dist/index.cjs CHANGED
@@ -249,8 +249,13 @@ function setViewport(viewport, updater) {
249
249
  function panViewport(viewport, dx, dy) {
250
250
  return { ...viewport, x: viewport.x + dx, y: viewport.y + dy };
251
251
  }
252
- function zoomViewport(viewport, factor, centerX, centerY) {
253
- const zoom = Math.max(0.1, Math.min(4, viewport.zoom * factor));
252
+ var DEFAULT_ZOOM_RANGE = { min: 0.1, max: 4 };
253
+ function clampZoom(zoom, range) {
254
+ const { min, max } = range ?? DEFAULT_ZOOM_RANGE;
255
+ return Math.max(min, Math.min(max, zoom));
256
+ }
257
+ function zoomViewport(viewport, factor, centerX, centerY, zoomRange) {
258
+ const zoom = clampZoom(viewport.zoom * factor, zoomRange);
254
259
  if (centerX == null || centerY == null) {
255
260
  return { ...viewport, zoom };
256
261
  }
@@ -260,6 +265,90 @@ function zoomViewport(viewport, factor, centerX, centerY) {
260
265
  return { x, y, zoom };
261
266
  }
262
267
 
268
+ // src/canvas/cameraPan.ts
269
+ var VELOCITY_LERP_FACTOR = 0.5;
270
+ var VELOCITY_ZERO_THRESHOLD = 0.01;
271
+ function beginCameraPan(viewport, screenX, screenY) {
272
+ return {
273
+ initialViewportX: viewport.x,
274
+ initialViewportY: viewport.y,
275
+ originScreenX: screenX,
276
+ originScreenY: screenY,
277
+ velocityX: 0,
278
+ velocityY: 0,
279
+ previousScreenX: screenX,
280
+ previousScreenY: screenY,
281
+ lastMoveTime: performance.now()
282
+ };
283
+ }
284
+ function moveCameraPan(session, currentScreenX, currentScreenY) {
285
+ const now = performance.now();
286
+ const elapsed = now - session.lastMoveTime;
287
+ if (elapsed > 0) {
288
+ const moveDx = currentScreenX - session.previousScreenX;
289
+ const moveDy = currentScreenY - session.previousScreenY;
290
+ const moveLen = Math.hypot(moveDx, moveDy);
291
+ if (moveLen > 0) {
292
+ const dirX = moveDx / moveLen;
293
+ const dirY = moveDy / moveLen;
294
+ const speed = moveLen / elapsed;
295
+ session.velocityX += (dirX * speed - session.velocityX) * VELOCITY_LERP_FACTOR;
296
+ session.velocityY += (dirY * speed - session.velocityY) * VELOCITY_LERP_FACTOR;
297
+ }
298
+ if (Math.abs(session.velocityX) < VELOCITY_ZERO_THRESHOLD) session.velocityX = 0;
299
+ if (Math.abs(session.velocityY) < VELOCITY_ZERO_THRESHOLD) session.velocityY = 0;
300
+ }
301
+ session.previousScreenX = currentScreenX;
302
+ session.previousScreenY = currentScreenY;
303
+ session.lastMoveTime = now;
304
+ return {
305
+ x: session.initialViewportX + (currentScreenX - session.originScreenX),
306
+ y: session.initialViewportY + (currentScreenY - session.originScreenY)
307
+ };
308
+ }
309
+ var SLIDE_FRICTION = 0.92;
310
+ var SLIDE_MIN_SPEED = 0.01;
311
+ var SLIDE_MAX_SPEED = 2;
312
+ var SLIDE_MIN_VELOCITY_TO_START = 0.1;
313
+ function startCameraSlide(session, applyPan, onFrame, slideOptions) {
314
+ const friction = slideOptions?.friction ?? SLIDE_FRICTION;
315
+ const timeSinceLastMove = performance.now() - session.lastMoveTime;
316
+ const FRAME_DURATION = 16;
317
+ const decayFactor = Math.pow(1 - VELOCITY_LERP_FACTOR, timeSinceLastMove / FRAME_DURATION);
318
+ const effectiveVx = session.velocityX * decayFactor;
319
+ const effectiveVy = session.velocityY * decayFactor;
320
+ const speed = Math.hypot(effectiveVx, effectiveVy);
321
+ const clampedSpeed = Math.min(speed, SLIDE_MAX_SPEED);
322
+ if (clampedSpeed < SLIDE_MIN_VELOCITY_TO_START) return null;
323
+ const dirX = effectiveVx / speed;
324
+ const dirY = effectiveVy / speed;
325
+ let currentSpeed = clampedSpeed;
326
+ let lastTime = performance.now();
327
+ let rafId = 0;
328
+ const tick = () => {
329
+ const now = performance.now();
330
+ const elapsed = now - lastTime;
331
+ lastTime = now;
332
+ applyPan(dirX * currentSpeed * elapsed, dirY * currentSpeed * elapsed);
333
+ onFrame();
334
+ currentSpeed *= friction;
335
+ if (currentSpeed < SLIDE_MIN_SPEED) {
336
+ rafId = 0;
337
+ return;
338
+ }
339
+ rafId = requestAnimationFrame(tick);
340
+ };
341
+ rafId = requestAnimationFrame(tick);
342
+ return {
343
+ stop() {
344
+ if (rafId !== 0) {
345
+ cancelAnimationFrame(rafId);
346
+ rafId = 0;
347
+ }
348
+ }
349
+ };
350
+ }
351
+
263
352
  // src/utils/colors.ts
264
353
  var DARK_COLORS = {
265
354
  black: "#f0f0f0",
@@ -1492,16 +1581,29 @@ var HandIdleState = class extends StateNode {
1492
1581
  // src/tools/hand/states/HandDraggingState.ts
1493
1582
  var HandDraggingState = class extends StateNode {
1494
1583
  static id = "hand_dragging";
1584
+ panSession = null;
1585
+ onEnter(info) {
1586
+ const downInfo = info;
1587
+ const screenX = downInfo?.screenX ?? 0;
1588
+ const screenY = downInfo?.screenY ?? 0;
1589
+ this.panSession = beginCameraPan(this.editor.viewport, screenX, screenY);
1590
+ }
1495
1591
  onPointerMove(info) {
1496
- const move = info ?? {};
1497
- const dx = move.screenDeltaX ?? 0;
1498
- const dy = move.screenDeltaY ?? 0;
1499
- if (dx === 0 && dy === 0) return;
1500
- this.editor.panBy(dx, dy);
1592
+ if (!this.panSession) return;
1593
+ const screenX = info?.screenX ?? 0;
1594
+ const screenY = info?.screenY ?? 0;
1595
+ const target = moveCameraPan(this.panSession, screenX, screenY);
1596
+ this.editor.setViewport({ x: target.x, y: target.y });
1597
+ }
1598
+ getPanSession() {
1599
+ return this.panSession;
1501
1600
  }
1502
1601
  onPointerUp() {
1503
1602
  this.ctx.transition("hand_idle");
1504
1603
  }
1604
+ onExit() {
1605
+ this.panSession = null;
1606
+ }
1505
1607
  onCancel() {
1506
1608
  this.ctx.transition("hand_idle");
1507
1609
  }
@@ -1615,7 +1717,7 @@ var Editor = class {
1615
1717
  historyBatchChanged = false;
1616
1718
  // Creates a new editor instance with the given options (with defaults if not provided)
1617
1719
  constructor(opts = {}) {
1618
- this.options = { dragDistanceSquared: opts.dragDistanceSquared ?? DRAG_DISTANCE_SQUARED };
1720
+ this.options = { dragDistanceSquared: opts.dragDistanceSquared ?? DRAG_DISTANCE_SQUARED, zoomRange: opts.zoomRange };
1619
1721
  this.lastDocumentSnapshot = this.getDocumentSnapshot();
1620
1722
  this.store.listen(() => {
1621
1723
  this.captureDocumentHistory();
@@ -1729,7 +1831,7 @@ var Editor = class {
1729
1831
  this.viewport = {
1730
1832
  x: partial.x ?? this.viewport.x,
1731
1833
  y: partial.y ?? this.viewport.y,
1732
- zoom: Math.max(0.1, Math.min(4, rawZoom))
1834
+ zoom: clampZoom(rawZoom, this.options.zoomRange)
1733
1835
  };
1734
1836
  this.emitChange();
1735
1837
  }
@@ -1740,7 +1842,7 @@ var Editor = class {
1740
1842
  });
1741
1843
  }
1742
1844
  zoomAt(factor, screenX, screenY) {
1743
- this.viewport = zoomViewport(this.viewport, factor, screenX, screenY);
1845
+ this.viewport = zoomViewport(this.viewport, factor, screenX, screenY, this.options.zoomRange);
1744
1846
  this.emitChange();
1745
1847
  }
1746
1848
  deleteShapes(ids) {
@@ -2164,6 +2266,7 @@ exports.CanvasRenderer = CanvasRenderer;
2164
2266
  exports.CircleDrawingState = CircleDrawingState;
2165
2267
  exports.CircleIdleState = CircleIdleState;
2166
2268
  exports.DEFAULT_COLORS = DEFAULT_COLORS;
2269
+ exports.DEFAULT_ZOOM_RANGE = DEFAULT_ZOOM_RANGE;
2167
2270
  exports.DRAG_DISTANCE_SQUARED = DRAG_DISTANCE_SQUARED;
2168
2271
  exports.DocumentStore = DocumentStore;
2169
2272
  exports.ERASER_MARGIN = ERASER_MARGIN;
@@ -2186,11 +2289,13 @@ exports.ToolManager = ToolManager;
2186
2289
  exports.applyMove = applyMove;
2187
2290
  exports.applyResize = applyResize;
2188
2291
  exports.applyRotation = applyRotation;
2292
+ exports.beginCameraPan = beginCameraPan;
2189
2293
  exports.boundsContainPoint = boundsContainPoint;
2190
2294
  exports.boundsIntersect = boundsIntersect;
2191
2295
  exports.boundsOf = boundsOf;
2192
2296
  exports.buildStartPositions = buildStartPositions;
2193
2297
  exports.buildTransformSnapshots = buildTransformSnapshots;
2298
+ exports.clampZoom = clampZoom;
2194
2299
  exports.closestOnSegment = closestOnSegment;
2195
2300
  exports.createViewport = createViewport;
2196
2301
  exports.decodeFirstPoint = decodeFirstPoint;
@@ -2206,6 +2311,7 @@ exports.getShapesInBounds = getShapesInBounds;
2206
2311
  exports.getTopShapeAtPoint = getTopShapeAtPoint;
2207
2312
  exports.isSelectTool = isSelectTool;
2208
2313
  exports.minDistanceToPolyline = minDistanceToPolyline;
2314
+ exports.moveCameraPan = moveCameraPan;
2209
2315
  exports.normalizeSelectionBounds = normalizeSelectionBounds;
2210
2316
  exports.padBounds = padBounds;
2211
2317
  exports.pageToScreen = pageToScreen;
@@ -2220,6 +2326,7 @@ exports.segmentTouchesPolyline = segmentTouchesPolyline;
2220
2326
  exports.setViewport = setViewport;
2221
2327
  exports.shapePagePoints = shapePagePoints;
2222
2328
  exports.sqDistance = sqDistance;
2329
+ exports.startCameraSlide = startCameraSlide;
2223
2330
  exports.zoomViewport = zoomViewport;
2224
2331
  //# sourceMappingURL=index.cjs.map
2225
2332
  //# sourceMappingURL=index.cjs.map