@fieldnotes/core 0.40.4 → 0.42.0

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
@@ -1554,6 +1554,81 @@ var KeyboardHandler = class {
1554
1554
  }
1555
1555
  };
1556
1556
 
1557
+ // src/canvas/pan-inertia.ts
1558
+ var VELOCITY_WINDOW_MS = 60;
1559
+ var FRICTION = 0.92;
1560
+ var MIN_START_SPEED = 2;
1561
+ var MIN_STOP_SPEED = 0.3;
1562
+ var PanInertia = class {
1563
+ constructor(deps) {
1564
+ this.deps = deps;
1565
+ }
1566
+ samples = [];
1567
+ vx = 0;
1568
+ vy = 0;
1569
+ rafId = null;
1570
+ sample(dx, dy) {
1571
+ const t = this.deps.now();
1572
+ this.samples.push({ dx, dy, t });
1573
+ this.prune(t);
1574
+ }
1575
+ release() {
1576
+ if (!this.deps.enabled()) {
1577
+ this.reset();
1578
+ return;
1579
+ }
1580
+ const t = this.deps.now();
1581
+ this.prune(t);
1582
+ const recent = this.samples;
1583
+ if (recent.length === 0) {
1584
+ this.reset();
1585
+ return;
1586
+ }
1587
+ let sx = 0;
1588
+ let sy = 0;
1589
+ for (const s of recent) {
1590
+ sx += s.dx;
1591
+ sy += s.dy;
1592
+ }
1593
+ this.vx = sx / recent.length;
1594
+ this.vy = sy / recent.length;
1595
+ this.samples = [];
1596
+ if (Math.hypot(this.vx, this.vy) < MIN_START_SPEED) {
1597
+ this.vx = 0;
1598
+ this.vy = 0;
1599
+ return;
1600
+ }
1601
+ this.rafId = this.deps.requestFrame(this.step);
1602
+ }
1603
+ cancel() {
1604
+ this.reset();
1605
+ }
1606
+ step = () => {
1607
+ if (this.rafId === null) return;
1608
+ this.deps.pan(this.vx, this.vy);
1609
+ this.vx *= FRICTION;
1610
+ this.vy *= FRICTION;
1611
+ if (Math.hypot(this.vx, this.vy) >= MIN_STOP_SPEED) {
1612
+ this.rafId = this.deps.requestFrame(this.step);
1613
+ } else {
1614
+ this.reset();
1615
+ }
1616
+ };
1617
+ prune(now) {
1618
+ const cutoff = now - VELOCITY_WINDOW_MS;
1619
+ this.samples = this.samples.filter((s) => s.t >= cutoff);
1620
+ }
1621
+ reset() {
1622
+ if (this.rafId !== null) {
1623
+ this.deps.cancelFrame(this.rafId);
1624
+ this.rafId = null;
1625
+ }
1626
+ this.samples = [];
1627
+ this.vx = 0;
1628
+ this.vy = 0;
1629
+ }
1630
+ };
1631
+
1557
1632
  // src/canvas/input-handler.ts
1558
1633
  var ZOOM_SENSITIVITY = 1e-3;
1559
1634
  var MIDDLE_BUTTON = 1;
@@ -1579,6 +1654,16 @@ var InputHandler = class {
1579
1654
  getLastPointerWorld: () => this.lastPointerWorld()
1580
1655
  });
1581
1656
  this.openContextMenu = options.openContextMenu;
1657
+ this.panInertiaEnabled = options.panInertia ?? true;
1658
+ this.panInertia = new PanInertia({
1659
+ pan: (dx, dy) => this.camera.pan(dx, dy),
1660
+ now: () => typeof performance !== "undefined" ? performance.now() : Date.now(),
1661
+ requestFrame: (cb) => typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame(cb) : 0,
1662
+ cancelFrame: (id) => {
1663
+ if (typeof cancelAnimationFrame !== "undefined") cancelAnimationFrame(id);
1664
+ },
1665
+ enabled: () => this.panInertiaEnabled
1666
+ });
1582
1667
  this.scope = options.shortcuts?.scope ?? "focus";
1583
1668
  this.keyboard = new KeyboardHandler({
1584
1669
  element: this.element,
@@ -1628,6 +1713,8 @@ var InputHandler = class {
1628
1713
  keyboard;
1629
1714
  scope;
1630
1715
  openContextMenu;
1716
+ panInertia;
1717
+ panInertiaEnabled;
1631
1718
  setToolManager(toolManager, toolContext) {
1632
1719
  this.toolManager = toolManager;
1633
1720
  this.toolContext = toolContext;
@@ -1639,6 +1726,7 @@ var InputHandler = class {
1639
1726
  return this.keyboard.shortcuts;
1640
1727
  }
1641
1728
  destroy() {
1729
+ this.panInertia.cancel();
1642
1730
  this.actions.dispose();
1643
1731
  this.abortController.abort();
1644
1732
  this.inputFilter.reset();
@@ -1662,6 +1750,7 @@ var InputHandler = class {
1662
1750
  }
1663
1751
  onWheel = (e) => {
1664
1752
  e.preventDefault();
1753
+ this.panInertia.cancel();
1665
1754
  const rect = this.element.getBoundingClientRect();
1666
1755
  const zoomFactor = 1 - e.deltaY * ZOOM_SENSITIVITY;
1667
1756
  const newZoom = this.camera.zoom * zoomFactor;
@@ -1671,6 +1760,7 @@ var InputHandler = class {
1671
1760
  });
1672
1761
  };
1673
1762
  onPointerDown = (e) => {
1763
+ this.panInertia.cancel();
1674
1764
  this.focusSelf();
1675
1765
  this.activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
1676
1766
  this.element.setPointerCapture?.(e.pointerId);
@@ -1711,6 +1801,7 @@ var InputHandler = class {
1711
1801
  const dy = e.clientY - this.lastPointer.y;
1712
1802
  this.lastPointer = { x: e.clientX, y: e.clientY };
1713
1803
  this.camera.pan(dx, dy);
1804
+ this.panInertia.sample(dx, dy);
1714
1805
  return;
1715
1806
  }
1716
1807
  if (e.pointerType === "pen" && !this.activePointers.has(e.pointerId)) {
@@ -1741,6 +1832,7 @@ var InputHandler = class {
1741
1832
  }
1742
1833
  if (this.isPanning && this.activePointers.size === 0) {
1743
1834
  this.isPanning = false;
1835
+ this.panInertia.release();
1744
1836
  }
1745
1837
  const upResult = this.inputFilter.filterUp(e);
1746
1838
  if (this.isToolActive) {
@@ -1761,6 +1853,7 @@ var InputHandler = class {
1761
1853
  return this.actions.hasClipboard();
1762
1854
  }
1763
1855
  startPinch() {
1856
+ this.panInertia.cancel();
1764
1857
  this.cancelLongPress();
1765
1858
  this.inputFilter.reset();
1766
1859
  this.deferredDown = null;
@@ -1782,6 +1875,7 @@ var InputHandler = class {
1782
1875
  const dx = center2.x - this.lastPointer.x;
1783
1876
  const dy = center2.y - this.lastPointer.y;
1784
1877
  this.camera.pan(dx, dy);
1878
+ this.panInertia.sample(dx, dy);
1785
1879
  this.lastPinchDistance = dist;
1786
1880
  this.lastPinchCenter = center2;
1787
1881
  this.lastPointer = { ...center2 };
@@ -2753,6 +2847,16 @@ function updateBoundArrow(arrow, store) {
2753
2847
  var ARROWHEAD_LENGTH = 12;
2754
2848
  var ARROWHEAD_ANGLE = Math.PI / 6;
2755
2849
  var ARROW_LABEL_FONT_SIZE = 14;
2850
+ function getArrowDashPattern(strokeStyle) {
2851
+ switch (strokeStyle) {
2852
+ case "dashed":
2853
+ return [8, 4];
2854
+ case "dotted":
2855
+ return [2, 4];
2856
+ default:
2857
+ return [];
2858
+ }
2859
+ }
2756
2860
  function renderArrow(ctx, arrow, store, labelEditingId) {
2757
2861
  const geometry = getArrowRenderGeometry(arrow);
2758
2862
  const { visualFrom, visualTo } = getVisualEndpoints(arrow, geometry, store);
@@ -2760,9 +2864,8 @@ function renderArrow(ctx, arrow, store, labelEditingId) {
2760
2864
  ctx.strokeStyle = arrow.color;
2761
2865
  ctx.lineWidth = arrow.width;
2762
2866
  ctx.lineCap = "round";
2763
- if (arrow.fromBinding || arrow.toBinding) {
2764
- ctx.setLineDash([8, 4]);
2765
- }
2867
+ const dash = getArrowDashPattern(arrow.strokeStyle);
2868
+ if (dash.length > 0) ctx.setLineDash(dash);
2766
2869
  ctx.beginPath();
2767
2870
  ctx.moveTo(visualFrom.x, visualFrom.y);
2768
2871
  if (arrow.bend !== 0) {
@@ -3682,6 +3785,7 @@ function createArrow(input) {
3682
3785
  if (input.fromBinding) result.fromBinding = input.fromBinding;
3683
3786
  if (input.toBinding) result.toBinding = input.toBinding;
3684
3787
  if (input.label !== void 0) result.label = input.label;
3788
+ if (input.strokeStyle !== void 0) result.strokeStyle = input.strokeStyle;
3685
3789
  return result;
3686
3790
  }
3687
3791
  function createImage(input) {
@@ -5085,7 +5189,8 @@ function emitArrow(arrow, store) {
5085
5189
  } else {
5086
5190
  d = `M${n(from.x)} ${n(from.y)} L${n(to.x)} ${n(to.y)}`;
5087
5191
  }
5088
- const dash = arrow.fromBinding || arrow.toBinding ? ' stroke-dasharray="8 4"' : "";
5192
+ const pattern = getArrowDashPattern(arrow.strokeStyle);
5193
+ const dash = pattern.length > 0 ? ` stroke-dasharray="${pattern.join(" ")}"` : "";
5089
5194
  let out = `<path d="${d}" fill="none" stroke="${esc(arrow.color)}" stroke-width="${n(arrow.width)}" stroke-linecap="round"${dash} />`;
5090
5195
  const angle = geometry.tangentEnd;
5091
5196
  const p1x = to.x - ARROWHEAD_LENGTH2 * Math.cos(angle - ARROWHEAD_ANGLE2);
@@ -6289,7 +6394,7 @@ function translateElementPatch(el, dx, dy) {
6289
6394
 
6290
6395
  // src/elements/element-style.ts
6291
6396
  function styleToPatch(element, style) {
6292
- const { color, fillColor, strokeWidth, opacity, fontSize } = style;
6397
+ const { color, fillColor, strokeWidth, opacity, fontSize, strokeStyle } = style;
6293
6398
  switch (element.type) {
6294
6399
  case "stroke":
6295
6400
  return {
@@ -6300,7 +6405,8 @@ function styleToPatch(element, style) {
6300
6405
  case "arrow":
6301
6406
  return {
6302
6407
  ...color !== void 0 ? { color } : {},
6303
- ...strokeWidth !== void 0 ? { width: strokeWidth } : {}
6408
+ ...strokeWidth !== void 0 ? { width: strokeWidth } : {},
6409
+ ...strokeStyle !== void 0 ? { strokeStyle } : {}
6304
6410
  };
6305
6411
  case "shape":
6306
6412
  return {
@@ -6341,7 +6447,11 @@ function getElementStyle(element) {
6341
6447
  case "stroke":
6342
6448
  return { color: element.color, strokeWidth: element.width, opacity: element.opacity };
6343
6449
  case "arrow":
6344
- return { color: element.color, strokeWidth: element.width };
6450
+ return {
6451
+ color: element.color,
6452
+ strokeWidth: element.width,
6453
+ ...element.strokeStyle !== void 0 ? { strokeStyle: element.strokeStyle } : {}
6454
+ };
6345
6455
  case "shape":
6346
6456
  return {
6347
6457
  color: element.strokeColor,
@@ -6415,6 +6525,8 @@ var SelectionOps = class {
6415
6525
  if (opacity !== void 0) result.opacity = opacity;
6416
6526
  const fontSize = sharedValue(styles.map((s) => s.fontSize));
6417
6527
  if (fontSize !== void 0) result.fontSize = fontSize;
6528
+ const strokeStyle = sharedValue(styles.map((s) => s.strokeStyle));
6529
+ if (strokeStyle !== void 0) result.strokeStyle = strokeStyle;
6418
6530
  return result;
6419
6531
  }
6420
6532
  applyStyle(style) {
@@ -6865,7 +6977,8 @@ var Viewport = class {
6865
6977
  shortcuts: options.shortcuts,
6866
6978
  addImage: (src, world) => this.addImage(src, world),
6867
6979
  getCenteredWorld: () => this.centeredPosition({ w: 300, h: 200 }),
6868
- onPaste: options.onPaste
6980
+ onPaste: options.onPaste,
6981
+ panInertia: options.panInertia
6869
6982
  });
6870
6983
  if (options.contextMenu !== false) {
6871
6984
  this.contextMenu = new ContextMenu({
@@ -8832,6 +8945,7 @@ var ArrowTool = class {
8832
8945
  end = { x: 0, y: 0 };
8833
8946
  color;
8834
8947
  width;
8948
+ strokeStyle;
8835
8949
  fromBinding;
8836
8950
  fromTarget = null;
8837
8951
  toTarget = null;
@@ -8839,9 +8953,10 @@ var ArrowTool = class {
8839
8953
  constructor(options = {}) {
8840
8954
  this.color = options.color ?? "#000000";
8841
8955
  this.width = options.width ?? 2;
8956
+ this.strokeStyle = options.strokeStyle ?? "solid";
8842
8957
  }
8843
8958
  getOptions() {
8844
- return { color: this.color, width: this.width };
8959
+ return { color: this.color, width: this.width, strokeStyle: this.strokeStyle };
8845
8960
  }
8846
8961
  onOptionsChange(listener) {
8847
8962
  this.optionListeners.add(listener);
@@ -8850,6 +8965,7 @@ var ArrowTool = class {
8850
8965
  setOptions(options) {
8851
8966
  if (options.color !== void 0) this.color = options.color;
8852
8967
  if (options.width !== void 0) this.width = options.width;
8968
+ if (options.strokeStyle !== void 0) this.strokeStyle = options.strokeStyle;
8853
8969
  this.notifyOptionsChange();
8854
8970
  }
8855
8971
  notifyOptionsChange() {
@@ -8909,6 +9025,7 @@ var ArrowTool = class {
8909
9025
  position: this.start,
8910
9026
  color: this.color,
8911
9027
  width: this.width,
9028
+ strokeStyle: this.strokeStyle,
8912
9029
  fromBinding: this.fromBinding,
8913
9030
  toBinding: this.toTarget ? { elementId: this.toTarget.id } : void 0,
8914
9031
  layerId: ctx.activeLayerId ?? ""
@@ -9776,7 +9893,7 @@ var LaserTool = class {
9776
9893
  };
9777
9894
 
9778
9895
  // src/index.ts
9779
- var VERSION = "0.40.4";
9896
+ var VERSION = "0.42.0";
9780
9897
  // Annotate the CommonJS export names for ESM import in node:
9781
9898
  0 && (module.exports = {
9782
9899
  ArrowTool,