@fieldnotes/core 0.32.0 → 0.33.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.d.cts CHANGED
@@ -200,6 +200,8 @@ interface ToolContext {
200
200
  activeLayerId?: string;
201
201
  isLayerVisible?: (layerId: string) => boolean;
202
202
  isLayerLocked?: (layerId: string) => boolean;
203
+ smartGuides?: boolean;
204
+ getVisibleRect?: () => Bounds;
203
205
  }
204
206
  interface PointerState {
205
207
  x: number;
@@ -460,6 +462,7 @@ declare class Viewport {
460
462
  private readonly marginViewport;
461
463
  private resizeObserver;
462
464
  private _snapToGrid;
465
+ private _smartGuides;
463
466
  private readonly _gridSize;
464
467
  private readonly renderLoop;
465
468
  private readonly domNodeManager;
@@ -474,6 +477,8 @@ declare class Viewport {
474
477
  get ctx(): CanvasRenderingContext2D | null;
475
478
  get snapToGrid(): boolean;
476
479
  setSnapToGrid(enabled: boolean): void;
480
+ get smartGuides(): boolean;
481
+ setSmartGuides(enabled: boolean): void;
477
482
  fitToContent(padding?: number): void;
478
483
  requestRender(): void;
479
484
  exportState(): CanvasState;
@@ -749,6 +754,9 @@ declare class SelectTool implements Tool {
749
754
  private ctx;
750
755
  private pendingSingleSelectId;
751
756
  private hasDragged;
757
+ private activeGuides;
758
+ private dragSnapTargets;
759
+ private dragVisibleRect;
752
760
  private resizeAspectRatio;
753
761
  private hoveredId;
754
762
  get selectedIds(): string[];
@@ -764,6 +772,7 @@ declare class SelectTool implements Tool {
764
772
  onPointerUp(_state: PointerState, ctx: ToolContext): void;
765
773
  onHover(state: PointerState, ctx: ToolContext): void;
766
774
  renderOverlay(canvasCtx: CanvasRenderingContext2D): void;
775
+ private renderGuideLines;
767
776
  private updateArrowsBoundTo;
768
777
  nudgeSelection(dx: number, dy: number, ctx: ToolContext): boolean;
769
778
  private updateHoverCursor;
@@ -979,6 +988,6 @@ declare class TemplateTool implements Tool {
979
988
  private notifyOptionsChange;
980
989
  }
981
990
 
982
- declare const VERSION = "0.32.0";
991
+ declare const VERSION = "0.33.0";
983
992
 
984
993
  export { type ActiveFormats, type AlignEdge, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DistributeAxis, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportImageOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
package/dist/index.d.ts CHANGED
@@ -200,6 +200,8 @@ interface ToolContext {
200
200
  activeLayerId?: string;
201
201
  isLayerVisible?: (layerId: string) => boolean;
202
202
  isLayerLocked?: (layerId: string) => boolean;
203
+ smartGuides?: boolean;
204
+ getVisibleRect?: () => Bounds;
203
205
  }
204
206
  interface PointerState {
205
207
  x: number;
@@ -460,6 +462,7 @@ declare class Viewport {
460
462
  private readonly marginViewport;
461
463
  private resizeObserver;
462
464
  private _snapToGrid;
465
+ private _smartGuides;
463
466
  private readonly _gridSize;
464
467
  private readonly renderLoop;
465
468
  private readonly domNodeManager;
@@ -474,6 +477,8 @@ declare class Viewport {
474
477
  get ctx(): CanvasRenderingContext2D | null;
475
478
  get snapToGrid(): boolean;
476
479
  setSnapToGrid(enabled: boolean): void;
480
+ get smartGuides(): boolean;
481
+ setSmartGuides(enabled: boolean): void;
477
482
  fitToContent(padding?: number): void;
478
483
  requestRender(): void;
479
484
  exportState(): CanvasState;
@@ -749,6 +754,9 @@ declare class SelectTool implements Tool {
749
754
  private ctx;
750
755
  private pendingSingleSelectId;
751
756
  private hasDragged;
757
+ private activeGuides;
758
+ private dragSnapTargets;
759
+ private dragVisibleRect;
752
760
  private resizeAspectRatio;
753
761
  private hoveredId;
754
762
  get selectedIds(): string[];
@@ -764,6 +772,7 @@ declare class SelectTool implements Tool {
764
772
  onPointerUp(_state: PointerState, ctx: ToolContext): void;
765
773
  onHover(state: PointerState, ctx: ToolContext): void;
766
774
  renderOverlay(canvasCtx: CanvasRenderingContext2D): void;
775
+ private renderGuideLines;
767
776
  private updateArrowsBoundTo;
768
777
  nudgeSelection(dx: number, dy: number, ctx: ToolContext): boolean;
769
778
  private updateHoverCursor;
@@ -979,6 +988,6 @@ declare class TemplateTool implements Tool {
979
988
  private notifyOptionsChange;
980
989
  }
981
990
 
982
- declare const VERSION = "0.32.0";
991
+ declare const VERSION = "0.33.0";
983
992
 
984
993
  export { type ActiveFormats, type AlignEdge, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DistributeAxis, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportImageOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
package/dist/index.js CHANGED
@@ -5495,7 +5495,9 @@ var Viewport = class {
5495
5495
  gridSize: this._gridSize,
5496
5496
  activeLayerId: this.layerManager.activeLayerId,
5497
5497
  isLayerVisible: (id) => this.layerManager.isLayerVisible(id),
5498
- isLayerLocked: (id) => this.layerManager.isLayerLocked(id)
5498
+ isLayerLocked: (id) => this.layerManager.isLayerLocked(id),
5499
+ smartGuides: false,
5500
+ getVisibleRect: () => this.camera.getVisibleRect(this.canvasEl.clientWidth, this.canvasEl.clientHeight)
5499
5501
  };
5500
5502
  this.inputHandler = new InputHandler(this.wrapper, this.camera, {
5501
5503
  toolManager: this.toolManager,
@@ -5599,6 +5601,7 @@ var Viewport = class {
5599
5601
  marginViewport;
5600
5602
  resizeObserver = null;
5601
5603
  _snapToGrid = false;
5604
+ _smartGuides = false;
5602
5605
  _gridSize;
5603
5606
  renderLoop;
5604
5607
  domNodeManager;
@@ -5619,6 +5622,13 @@ var Viewport = class {
5619
5622
  this._snapToGrid = enabled;
5620
5623
  this.toolContext.snapToGrid = enabled;
5621
5624
  }
5625
+ get smartGuides() {
5626
+ return this._smartGuides;
5627
+ }
5628
+ setSmartGuides(enabled) {
5629
+ this._smartGuides = enabled;
5630
+ this.toolContext.smartGuides = enabled;
5631
+ }
5622
5632
  fitToContent(padding = 40) {
5623
5633
  if (this.wrapper.clientWidth === 0 || this.wrapper.clientHeight === 0) return;
5624
5634
  const visibleElements = this.store.getAll().filter((el) => this.layerManager.isLayerVisible(el.layerId));
@@ -6656,8 +6666,47 @@ function renderArrowHandles(canvasCtx, arrow, zoom) {
6656
6666
  }
6657
6667
  }
6658
6668
 
6669
+ // src/elements/snap-guides.ts
6670
+ function xAnchors(b) {
6671
+ return { lo: b.x, mid: b.x + b.w / 2, hi: b.x + b.w };
6672
+ }
6673
+ function yAnchors(b) {
6674
+ return { lo: b.y, mid: b.y + b.h / 2, hi: b.y + b.h };
6675
+ }
6676
+ function bestAxisSnap(moving, targets, anchorsFn, threshold) {
6677
+ let best = null;
6678
+ for (const t of targets) {
6679
+ const ta = anchorsFn(t);
6680
+ const pairs = [
6681
+ // colinear alignment: same-type edges/centers line up
6682
+ [ta.lo - moving.lo, ta.lo],
6683
+ [ta.mid - moving.mid, ta.mid],
6684
+ [ta.hi - moving.hi, ta.hi],
6685
+ // abutment: the moving box sits flush against the target's opposite edge
6686
+ [ta.lo - moving.hi, ta.lo],
6687
+ [ta.hi - moving.lo, ta.hi]
6688
+ ];
6689
+ for (const [delta, position] of pairs) {
6690
+ const abs = Math.abs(delta);
6691
+ if (abs <= threshold && (best === null || abs < Math.abs(best.delta))) {
6692
+ best = { delta, position };
6693
+ }
6694
+ }
6695
+ }
6696
+ return best;
6697
+ }
6698
+ function computeSnapGuides(moving, targets, threshold) {
6699
+ const xSnap = bestAxisSnap(xAnchors(moving), targets, xAnchors, threshold);
6700
+ const ySnap = bestAxisSnap(yAnchors(moving), targets, yAnchors, threshold);
6701
+ const guides = [];
6702
+ if (xSnap) guides.push({ axis: "x", position: xSnap.position });
6703
+ if (ySnap) guides.push({ axis: "y", position: ySnap.position });
6704
+ return { dx: xSnap?.delta ?? 0, dy: ySnap?.delta ?? 0, guides };
6705
+ }
6706
+
6659
6707
  // src/tools/select-tool.ts
6660
6708
  var HANDLE_SIZE = 8;
6709
+ var SNAP_PX = 6;
6661
6710
  var HANDLE_HIT_PADDING2 = 4;
6662
6711
  var SELECTION_PAD = 4;
6663
6712
  var MIN_ELEMENT_SIZE = 20;
@@ -6677,6 +6726,9 @@ var SelectTool = class {
6677
6726
  ctx = null;
6678
6727
  pendingSingleSelectId = null;
6679
6728
  hasDragged = false;
6729
+ activeGuides = [];
6730
+ dragSnapTargets = null;
6731
+ dragVisibleRect = null;
6680
6732
  resizeAspectRatio = 0;
6681
6733
  hoveredId = null;
6682
6734
  get selectedIds() {
@@ -6708,6 +6760,9 @@ var SelectTool = class {
6708
6760
  this.setSelectedIds([]);
6709
6761
  this.mode = { type: "idle" };
6710
6762
  this.hoveredId = null;
6763
+ this.activeGuides = [];
6764
+ this.dragSnapTargets = null;
6765
+ this.dragVisibleRect = null;
6711
6766
  ctx.setCursor?.("default");
6712
6767
  }
6713
6768
  snap(point, ctx) {
@@ -6716,6 +6771,8 @@ var SelectTool = class {
6716
6771
  onPointerDown(state, ctx) {
6717
6772
  this.ctx = ctx;
6718
6773
  this.setHovered(null, ctx);
6774
+ this.dragSnapTargets = null;
6775
+ this.dragVisibleRect = null;
6719
6776
  const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
6720
6777
  this.lastWorld = this.snap(world, ctx);
6721
6778
  this.currentWorld = world;
@@ -6816,6 +6873,31 @@ var SelectTool = class {
6816
6873
  const dx = snapped.x - this.lastWorld.x;
6817
6874
  const dy = snapped.y - this.lastWorld.y;
6818
6875
  this.lastWorld = snapped;
6876
+ let adjDx = dx;
6877
+ let adjDy = dy;
6878
+ this.activeGuides = [];
6879
+ if (ctx.smartGuides) {
6880
+ if (this.dragSnapTargets === null) {
6881
+ const selSet = new Set(this._selectedIds);
6882
+ this.dragVisibleRect = ctx.getVisibleRect?.() ?? null;
6883
+ const candidates = (this.dragVisibleRect ? ctx.store.queryRect(this.dragVisibleRect) : ctx.store.getAll()).filter((el) => !selSet.has(el.id) && el.type !== "grid");
6884
+ const targets = [];
6885
+ for (const el of candidates) {
6886
+ const b = getElementBounds(el);
6887
+ if (b) targets.push(b);
6888
+ }
6889
+ this.dragSnapTargets = targets;
6890
+ }
6891
+ const selectedEls = this._selectedIds.map((id) => ctx.store.getById(id)).filter((el) => !!el && !el.locked);
6892
+ const base = getElementsBoundingBox(selectedEls);
6893
+ if (base) {
6894
+ const moving = { x: base.x + dx, y: base.y + dy, w: base.w, h: base.h };
6895
+ const res = computeSnapGuides(moving, this.dragSnapTargets, SNAP_PX / ctx.camera.zoom);
6896
+ adjDx = dx + res.dx;
6897
+ adjDy = dy + res.dy;
6898
+ this.activeGuides = res.guides;
6899
+ }
6900
+ }
6819
6901
  for (const id of this._selectedIds) {
6820
6902
  const el = ctx.store.getById(id);
6821
6903
  if (!el || el.locked) continue;
@@ -6824,13 +6906,13 @@ var SelectTool = class {
6824
6906
  continue;
6825
6907
  }
6826
6908
  ctx.store.update(id, {
6827
- position: { x: el.position.x + dx, y: el.position.y + dy },
6828
- from: { x: el.from.x + dx, y: el.from.y + dy },
6829
- to: { x: el.to.x + dx, y: el.to.y + dy }
6909
+ position: { x: el.position.x + adjDx, y: el.position.y + adjDy },
6910
+ from: { x: el.from.x + adjDx, y: el.from.y + adjDy },
6911
+ to: { x: el.to.x + adjDx, y: el.to.y + adjDy }
6830
6912
  });
6831
- } else if (ctx.gridType && "size" in el) {
6832
- const centerX = el.position.x + el.size.w / 2 + dx;
6833
- const centerY = el.position.y + el.size.h / 2 + dy;
6913
+ } else if (!ctx.smartGuides && ctx.gridType && "size" in el) {
6914
+ const centerX = el.position.x + el.size.w / 2 + adjDx;
6915
+ const centerY = el.position.y + el.size.h / 2 + adjDy;
6834
6916
  const snappedCenter = this.snap({ x: centerX, y: centerY }, ctx);
6835
6917
  ctx.store.update(id, {
6836
6918
  position: {
@@ -6840,7 +6922,7 @@ var SelectTool = class {
6840
6922
  });
6841
6923
  } else {
6842
6924
  ctx.store.update(id, {
6843
- position: { x: el.position.x + dx, y: el.position.y + dy }
6925
+ position: { x: el.position.x + adjDx, y: el.position.y + adjDy }
6844
6926
  });
6845
6927
  }
6846
6928
  }
@@ -6870,6 +6952,10 @@ var SelectTool = class {
6870
6952
  this.hasDragged = false;
6871
6953
  const resizedNoteId = this.mode.type === "resizing" ? this.mode.elementId : null;
6872
6954
  this.mode = { type: "idle" };
6955
+ this.activeGuides = [];
6956
+ this.dragSnapTargets = null;
6957
+ this.dragVisibleRect = null;
6958
+ ctx.requestRender();
6873
6959
  ctx.setCursor?.("default");
6874
6960
  if (resizedNoteId !== null) {
6875
6961
  const el = ctx.store.getById(resizedNoteId);
@@ -6917,6 +7003,32 @@ var SelectTool = class {
6917
7003
  }
6918
7004
  }
6919
7005
  }
7006
+ this.renderGuideLines(canvasCtx);
7007
+ }
7008
+ renderGuideLines(canvasCtx) {
7009
+ if (this.mode.type !== "dragging" || !this.ctx || this.activeGuides.length === 0) return;
7010
+ const zoom = this.ctx.camera.zoom;
7011
+ const rect = this.dragVisibleRect;
7012
+ canvasCtx.save();
7013
+ canvasCtx.strokeStyle = "#FF4081";
7014
+ canvasCtx.lineWidth = 1 / zoom;
7015
+ canvasCtx.setLineDash([]);
7016
+ for (const g of this.activeGuides) {
7017
+ canvasCtx.beginPath();
7018
+ if (g.axis === "x") {
7019
+ const y0 = rect ? rect.y : this.currentWorld.y - 1e5;
7020
+ const y1 = rect ? rect.y + rect.h : this.currentWorld.y + 1e5;
7021
+ canvasCtx.moveTo(g.position, y0);
7022
+ canvasCtx.lineTo(g.position, y1);
7023
+ } else {
7024
+ const x0 = rect ? rect.x : this.currentWorld.x - 1e5;
7025
+ const x1 = rect ? rect.x + rect.w : this.currentWorld.x + 1e5;
7026
+ canvasCtx.moveTo(x0, g.position);
7027
+ canvasCtx.lineTo(x1, g.position);
7028
+ }
7029
+ canvasCtx.stroke();
7030
+ }
7031
+ canvasCtx.restore();
6920
7032
  }
6921
7033
  updateArrowsBoundTo(ids, ctx) {
6922
7034
  updateArrowsBoundToElements(ids, ctx.store);
@@ -8119,7 +8231,7 @@ var TemplateTool = class {
8119
8231
  };
8120
8232
 
8121
8233
  // src/index.ts
8122
- var VERSION = "0.32.0";
8234
+ var VERSION = "0.33.0";
8123
8235
  export {
8124
8236
  ArrowTool,
8125
8237
  AutoSave,