@fieldnotes/core 0.4.1 → 0.5.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
@@ -69,6 +69,7 @@ __export(index_exports, {
69
69
  isBindable: () => isBindable,
70
70
  isNearBezier: () => isNearBezier,
71
71
  parseState: () => parseState,
72
+ snapPoint: () => snapPoint,
72
73
  unbindArrow: () => unbindArrow,
73
74
  updateBoundArrow: () => updateBoundArrow
74
75
  });
@@ -195,6 +196,14 @@ function migrateElement(obj) {
195
196
  }
196
197
  }
197
198
 
199
+ // src/core/snap.ts
200
+ function snapPoint(point, gridSize) {
201
+ return {
202
+ x: Math.round(point.x / gridSize) * gridSize || 0,
203
+ y: Math.round(point.y / gridSize) * gridSize || 0
204
+ };
205
+ }
206
+
198
207
  // src/core/auto-save.ts
199
208
  var DEFAULT_KEY = "fieldnotes-autosave";
200
209
  var DEFAULT_DEBOUNCE_MS = 1e3;
@@ -1577,6 +1586,7 @@ var Viewport = class {
1577
1586
  this.container = container;
1578
1587
  this.camera = new Camera(options.camera);
1579
1588
  this.background = new Background(options.background);
1589
+ this._gridSize = options.background?.spacing ?? 24;
1580
1590
  this.store = new ElementStore();
1581
1591
  this.toolManager = new ToolManager();
1582
1592
  this.renderer = new ElementRenderer();
@@ -1599,7 +1609,9 @@ var Viewport = class {
1599
1609
  editElement: (id) => this.startEditingElement(id),
1600
1610
  setCursor: (cursor) => {
1601
1611
  this.wrapper.style.cursor = cursor;
1602
- }
1612
+ },
1613
+ snapToGrid: false,
1614
+ gridSize: this._gridSize
1603
1615
  };
1604
1616
  this.inputHandler = new InputHandler(this.wrapper, this.camera, {
1605
1617
  toolManager: this.toolManager,
@@ -1644,6 +1656,8 @@ var Viewport = class {
1644
1656
  toolContext;
1645
1657
  resizeObserver = null;
1646
1658
  animFrameId = 0;
1659
+ _snapToGrid = false;
1660
+ _gridSize;
1647
1661
  needsRender = true;
1648
1662
  domNodes = /* @__PURE__ */ new Map();
1649
1663
  htmlContent = /* @__PURE__ */ new Map();
@@ -1651,6 +1665,13 @@ var Viewport = class {
1651
1665
  get ctx() {
1652
1666
  return this.canvasEl.getContext("2d");
1653
1667
  }
1668
+ get snapToGrid() {
1669
+ return this._snapToGrid;
1670
+ }
1671
+ setSnapToGrid(enabled) {
1672
+ this._snapToGrid = enabled;
1673
+ this.toolContext.snapToGrid = enabled;
1674
+ }
1654
1675
  requestRender() {
1655
1676
  this.needsRender = true;
1656
1677
  }
@@ -2399,10 +2420,13 @@ var SelectTool = class {
2399
2420
  this.mode = { type: "idle" };
2400
2421
  ctx.setCursor?.("default");
2401
2422
  }
2423
+ snap(point, ctx) {
2424
+ return ctx.snapToGrid && ctx.gridSize ? snapPoint(point, ctx.gridSize) : point;
2425
+ }
2402
2426
  onPointerDown(state, ctx) {
2403
2427
  this.ctx = ctx;
2404
2428
  const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
2405
- this.lastWorld = world;
2429
+ this.lastWorld = this.snap(world, ctx);
2406
2430
  this.currentWorld = world;
2407
2431
  const arrowHit = hitTestArrowHandles(world, this._selectedIds, ctx);
2408
2432
  if (arrowHit) {
@@ -2455,9 +2479,10 @@ var SelectTool = class {
2455
2479
  }
2456
2480
  if (this.mode.type === "dragging" && this._selectedIds.length > 0) {
2457
2481
  ctx.setCursor?.("move");
2458
- const dx = world.x - this.lastWorld.x;
2459
- const dy = world.y - this.lastWorld.y;
2460
- this.lastWorld = world;
2482
+ const snapped = this.snap(world, ctx);
2483
+ const dx = snapped.x - this.lastWorld.x;
2484
+ const dy = snapped.y - this.lastWorld.y;
2485
+ this.lastWorld = snapped;
2461
2486
  for (const id of this._selectedIds) {
2462
2487
  const el = ctx.store.getById(id);
2463
2488
  if (!el || el.locked) continue;
@@ -2806,7 +2831,7 @@ var ArrowTool = class {
2806
2831
  this.fromBinding = { elementId: target.id };
2807
2832
  this.fromTarget = target;
2808
2833
  } else {
2809
- this.start = world;
2834
+ this.start = ctx.snapToGrid && ctx.gridSize ? snapPoint(world, ctx.gridSize) : world;
2810
2835
  this.fromBinding = void 0;
2811
2836
  this.fromTarget = null;
2812
2837
  }
@@ -2823,7 +2848,7 @@ var ArrowTool = class {
2823
2848
  this.end = getElementCenter(target);
2824
2849
  this.toTarget = target;
2825
2850
  } else {
2826
- this.end = world;
2851
+ this.end = ctx.snapToGrid && ctx.gridSize ? snapPoint(world, ctx.gridSize) : world;
2827
2852
  this.toTarget = null;
2828
2853
  }
2829
2854
  ctx.requestRender();
@@ -2918,7 +2943,10 @@ var NoteTool = class {
2918
2943
  onPointerMove(_state, _ctx) {
2919
2944
  }
2920
2945
  onPointerUp(state, ctx) {
2921
- const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
2946
+ let world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
2947
+ if (ctx.snapToGrid && ctx.gridSize) {
2948
+ world = snapPoint(world, ctx.gridSize);
2949
+ }
2922
2950
  const note = createNote({
2923
2951
  position: world,
2924
2952
  size: { ...this.size },
@@ -2959,7 +2987,10 @@ var TextTool = class {
2959
2987
  onPointerMove(_state, _ctx) {
2960
2988
  }
2961
2989
  onPointerUp(state, ctx) {
2962
- const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
2990
+ let world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
2991
+ if (ctx.snapToGrid && ctx.gridSize) {
2992
+ world = snapPoint(world, ctx.gridSize);
2993
+ }
2963
2994
  const textEl = createText({
2964
2995
  position: world,
2965
2996
  fontSize: this.fontSize,
@@ -3041,12 +3072,12 @@ var ShapeTool = class {
3041
3072
  }
3042
3073
  onPointerDown(state, ctx) {
3043
3074
  this.drawing = true;
3044
- this.start = ctx.camera.screenToWorld({ x: state.x, y: state.y });
3075
+ this.start = this.snap(ctx.camera.screenToWorld({ x: state.x, y: state.y }), ctx);
3045
3076
  this.end = { ...this.start };
3046
3077
  }
3047
3078
  onPointerMove(state, ctx) {
3048
3079
  if (!this.drawing) return;
3049
- this.end = ctx.camera.screenToWorld({ x: state.x, y: state.y });
3080
+ this.end = this.snap(ctx.camera.screenToWorld({ x: state.x, y: state.y }), ctx);
3050
3081
  ctx.requestRender();
3051
3082
  }
3052
3083
  onPointerUp(_state, ctx) {
@@ -3110,6 +3141,9 @@ var ShapeTool = class {
3110
3141
  }
3111
3142
  return { position: { x, y }, size: { w, h } };
3112
3143
  }
3144
+ snap(point, ctx) {
3145
+ return ctx.snapToGrid && ctx.gridSize ? snapPoint(point, ctx.gridSize) : point;
3146
+ }
3113
3147
  onKeyDown = (e) => {
3114
3148
  if (e.key === "Shift") this.shiftHeld = true;
3115
3149
  };
@@ -3119,7 +3153,7 @@ var ShapeTool = class {
3119
3153
  };
3120
3154
 
3121
3155
  // src/index.ts
3122
- var VERSION = "0.4.1";
3156
+ var VERSION = "0.5.0";
3123
3157
  // Annotate the CommonJS export names for ESM import in node:
3124
3158
  0 && (module.exports = {
3125
3159
  AddElementCommand,
@@ -3171,6 +3205,7 @@ var VERSION = "0.4.1";
3171
3205
  isBindable,
3172
3206
  isNearBezier,
3173
3207
  parseState,
3208
+ snapPoint,
3174
3209
  unbindArrow,
3175
3210
  updateBoundArrow
3176
3211
  });