@fieldnotes/core 0.31.0 → 0.32.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
@@ -410,6 +410,8 @@ interface ElementStyle {
410
410
  declare function styleToPatch(element: CanvasElement, style: ElementStyle): Partial<CanvasElement>;
411
411
  declare function getElementStyle(element: CanvasElement): ElementStyle;
412
412
 
413
+ type AlignEdge = 'left' | 'center-x' | 'right' | 'top' | 'middle' | 'bottom';
414
+ type DistributeAxis = 'horizontal' | 'vertical';
413
415
  interface GridInfo {
414
416
  gridType: 'square' | 'hex';
415
417
  hexOrientation: 'pointy' | 'flat';
@@ -516,6 +518,10 @@ declare class Viewport {
516
518
  onSelectionChange(listener: () => void): () => void;
517
519
  getSelectionStyle(): ElementStyle | null;
518
520
  applyStyleToSelection(style: ElementStyle): void;
521
+ alignSelection(edge: AlignEdge): void;
522
+ distributeSelection(axis: DistributeAxis): void;
523
+ private boundedSelection;
524
+ private isMovable;
519
525
  getRenderStats(): RenderStatsSnapshot;
520
526
  logPerformance(intervalMs?: number): () => void;
521
527
  destroy(): void;
@@ -711,6 +717,7 @@ declare class PencilTool implements Tool {
711
717
  }
712
718
 
713
719
  interface EraserToolOptions {
720
+ /** Eraser radius in SCREEN pixels (matches the cursor circle; converted to world units per zoom). */
714
721
  radius?: number;
715
722
  mode?: 'partial' | 'stroke';
716
723
  }
@@ -972,6 +979,6 @@ declare class TemplateTool implements Tool {
972
979
  private notifyOptionsChange;
973
980
  }
974
981
 
975
- declare const VERSION = "0.31.0";
982
+ declare const VERSION = "0.32.0";
976
983
 
977
- export { type ActiveFormats, 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, 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 };
984
+ 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
@@ -410,6 +410,8 @@ interface ElementStyle {
410
410
  declare function styleToPatch(element: CanvasElement, style: ElementStyle): Partial<CanvasElement>;
411
411
  declare function getElementStyle(element: CanvasElement): ElementStyle;
412
412
 
413
+ type AlignEdge = 'left' | 'center-x' | 'right' | 'top' | 'middle' | 'bottom';
414
+ type DistributeAxis = 'horizontal' | 'vertical';
413
415
  interface GridInfo {
414
416
  gridType: 'square' | 'hex';
415
417
  hexOrientation: 'pointy' | 'flat';
@@ -516,6 +518,10 @@ declare class Viewport {
516
518
  onSelectionChange(listener: () => void): () => void;
517
519
  getSelectionStyle(): ElementStyle | null;
518
520
  applyStyleToSelection(style: ElementStyle): void;
521
+ alignSelection(edge: AlignEdge): void;
522
+ distributeSelection(axis: DistributeAxis): void;
523
+ private boundedSelection;
524
+ private isMovable;
519
525
  getRenderStats(): RenderStatsSnapshot;
520
526
  logPerformance(intervalMs?: number): () => void;
521
527
  destroy(): void;
@@ -711,6 +717,7 @@ declare class PencilTool implements Tool {
711
717
  }
712
718
 
713
719
  interface EraserToolOptions {
720
+ /** Eraser radius in SCREEN pixels (matches the cursor circle; converted to world units per zoom). */
714
721
  radius?: number;
715
722
  mode?: 'partial' | 'stroke';
716
723
  }
@@ -972,6 +979,6 @@ declare class TemplateTool implements Tool {
972
979
  private notifyOptionsChange;
973
980
  }
974
981
 
975
- declare const VERSION = "0.31.0";
982
+ declare const VERSION = "0.32.0";
976
983
 
977
- export { type ActiveFormats, 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, 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 };
984
+ 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
@@ -2270,6 +2270,23 @@ function distToBounds(point, bounds) {
2270
2270
  function findBoundArrows(elementId, store) {
2271
2271
  return store.getElementsByType("arrow").filter((a) => a.fromBinding?.elementId === elementId || a.toBinding?.elementId === elementId);
2272
2272
  }
2273
+ function updateArrowsBoundToElements(movedIds, store) {
2274
+ const movedNonArrowIds = /* @__PURE__ */ new Set();
2275
+ for (const id of movedIds) {
2276
+ const el = store.getById(id);
2277
+ if (el && el.type !== "arrow") movedNonArrowIds.add(id);
2278
+ }
2279
+ if (movedNonArrowIds.size === 0) return;
2280
+ const updated = /* @__PURE__ */ new Set();
2281
+ for (const id of movedNonArrowIds) {
2282
+ for (const ba of findBoundArrows(id, store)) {
2283
+ if (updated.has(ba.id)) continue;
2284
+ updated.add(ba.id);
2285
+ const updates = updateBoundArrow(ba, store);
2286
+ if (updates) store.update(ba.id, updates);
2287
+ }
2288
+ }
2289
+ }
2273
2290
  function updateBoundArrow(arrow, store) {
2274
2291
  if (!arrow.fromBinding && !arrow.toBinding) return null;
2275
2292
  const updates = {};
@@ -3697,6 +3714,19 @@ var NoteEditor = class {
3697
3714
  }
3698
3715
  };
3699
3716
 
3717
+ // src/elements/translate.ts
3718
+ function translateElementPatch(el, dx, dy) {
3719
+ const position = { x: el.position.x + dx, y: el.position.y + dy };
3720
+ if (el.type === "arrow") {
3721
+ return {
3722
+ position,
3723
+ from: { x: el.from.x + dx, y: el.from.y + dy },
3724
+ to: { x: el.to.x + dx, y: el.to.y + dy }
3725
+ };
3726
+ }
3727
+ return { position };
3728
+ }
3729
+
3700
3730
  // src/elements/arrow-label-editor.ts
3701
3731
  var ArrowLabelEditor = class {
3702
3732
  input = null;
@@ -5383,6 +5413,16 @@ function getElementStyle(element) {
5383
5413
  }
5384
5414
 
5385
5415
  // src/canvas/viewport.ts
5416
+ function unionBounds(list) {
5417
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
5418
+ for (const b of list) {
5419
+ minX = Math.min(minX, b.x);
5420
+ minY = Math.min(minY, b.y);
5421
+ maxX = Math.max(maxX, b.x + b.w);
5422
+ maxY = Math.max(maxY, b.y + b.h);
5423
+ }
5424
+ return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
5425
+ }
5386
5426
  var EMPTY_IDS = [];
5387
5427
  var ARROW_HIT_THRESHOLD = 10;
5388
5428
  function noop() {
@@ -5792,6 +5832,86 @@ var Viewport = class {
5792
5832
  }
5793
5833
  this.historyRecorder.commit();
5794
5834
  }
5835
+ alignSelection(edge) {
5836
+ const bounded = this.boundedSelection();
5837
+ if (bounded.length < 2) return;
5838
+ const B = unionBounds(bounded.map((e) => e.bounds));
5839
+ this.historyRecorder.begin();
5840
+ const moved = [];
5841
+ for (const { id, el, bounds: b } of bounded) {
5842
+ if (!this.isMovable(el)) continue;
5843
+ let dx = 0;
5844
+ let dy = 0;
5845
+ switch (edge) {
5846
+ case "left":
5847
+ dx = B.x - b.x;
5848
+ break;
5849
+ case "right":
5850
+ dx = B.x + B.w - (b.x + b.w);
5851
+ break;
5852
+ case "center-x":
5853
+ dx = B.x + B.w / 2 - (b.x + b.w / 2);
5854
+ break;
5855
+ case "top":
5856
+ dy = B.y - b.y;
5857
+ break;
5858
+ case "bottom":
5859
+ dy = B.y + B.h - (b.y + b.h);
5860
+ break;
5861
+ case "middle":
5862
+ dy = B.y + B.h / 2 - (b.y + b.h / 2);
5863
+ break;
5864
+ }
5865
+ if (dx === 0 && dy === 0) continue;
5866
+ this.store.update(id, translateElementPatch(el, dx, dy));
5867
+ moved.push(id);
5868
+ }
5869
+ updateArrowsBoundToElements(moved, this.store);
5870
+ this.historyRecorder.commit();
5871
+ this.requestRender();
5872
+ }
5873
+ distributeSelection(axis) {
5874
+ const bounded = this.boundedSelection();
5875
+ if (bounded.length < 3) return;
5876
+ const center = (b) => axis === "horizontal" ? b.x + b.w / 2 : b.y + b.h / 2;
5877
+ const sorted = [...bounded].sort((p, q) => center(p.bounds) - center(q.bounds));
5878
+ const first = sorted[0];
5879
+ const last = sorted[sorted.length - 1];
5880
+ if (!first || !last) return;
5881
+ const c0 = center(first.bounds);
5882
+ const cN = center(last.bounds);
5883
+ const n = sorted.length;
5884
+ this.historyRecorder.begin();
5885
+ const moved = [];
5886
+ for (let i = 1; i < n - 1; i++) {
5887
+ const item = sorted[i];
5888
+ if (!item || !this.isMovable(item.el)) continue;
5889
+ const target = c0 + i * (cN - c0) / (n - 1);
5890
+ const delta = target - center(item.bounds);
5891
+ if (delta === 0) continue;
5892
+ const [dx, dy] = axis === "horizontal" ? [delta, 0] : [0, delta];
5893
+ this.store.update(item.id, translateElementPatch(item.el, dx, dy));
5894
+ moved.push(item.id);
5895
+ }
5896
+ updateArrowsBoundToElements(moved, this.store);
5897
+ this.historyRecorder.commit();
5898
+ this.requestRender();
5899
+ }
5900
+ boundedSelection() {
5901
+ const out = [];
5902
+ for (const id of this.getSelectedIds()) {
5903
+ const el = this.store.getById(id);
5904
+ if (!el) continue;
5905
+ const bounds = getElementBounds(el);
5906
+ if (bounds) out.push({ id, el, bounds });
5907
+ }
5908
+ return out;
5909
+ }
5910
+ isMovable(el) {
5911
+ if (el.locked) return false;
5912
+ if (el.type === "arrow" && (el.fromBinding ?? el.toBinding)) return false;
5913
+ return true;
5914
+ }
5795
5915
  getRenderStats() {
5796
5916
  return this.renderLoop.getStats();
5797
5917
  }
@@ -6376,11 +6496,12 @@ var EraserTool = class {
6376
6496
  }
6377
6497
  eraseAt(state, ctx) {
6378
6498
  const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
6499
+ const worldRadius = this.radius / ctx.camera.zoom;
6379
6500
  const queryBounds = {
6380
- x: world.x - this.radius,
6381
- y: world.y - this.radius,
6382
- w: this.radius * 2,
6383
- h: this.radius * 2
6501
+ x: world.x - worldRadius,
6502
+ y: world.y - worldRadius,
6503
+ w: worldRadius * 2,
6504
+ h: worldRadius * 2
6384
6505
  };
6385
6506
  const candidates = ctx.store.queryRect(queryBounds);
6386
6507
  let erased = false;
@@ -6388,14 +6509,14 @@ var EraserTool = class {
6388
6509
  if (el.type !== "stroke") continue;
6389
6510
  if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
6390
6511
  if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
6391
- if (!this.strokeIntersects(el, world)) continue;
6512
+ if (!this.strokeIntersects(el, world, worldRadius)) continue;
6392
6513
  if (this.mode === "stroke") {
6393
6514
  ctx.store.remove(el.id);
6394
6515
  erased = true;
6395
6516
  continue;
6396
6517
  }
6397
6518
  const localEraser = { x: world.x - el.position.x, y: world.y - el.position.y };
6398
- const runs = erasePoints(el.points, localEraser, this.radius);
6519
+ const runs = erasePoints(el.points, localEraser, worldRadius);
6399
6520
  if (runs === null) continue;
6400
6521
  ctx.store.remove(el.id);
6401
6522
  for (const run of runs) {
@@ -6415,8 +6536,8 @@ var EraserTool = class {
6415
6536
  }
6416
6537
  if (erased) ctx.requestRender();
6417
6538
  }
6418
- strokeIntersects(stroke, point) {
6419
- return hitTestStroke(stroke, point, this.radius);
6539
+ strokeIntersects(stroke, point, worldRadius) {
6540
+ return hitTestStroke(stroke, point, worldRadius);
6420
6541
  }
6421
6542
  };
6422
6543
 
@@ -6798,40 +6919,15 @@ var SelectTool = class {
6798
6919
  }
6799
6920
  }
6800
6921
  updateArrowsBoundTo(ids, ctx) {
6801
- const movedNonArrowIds = /* @__PURE__ */ new Set();
6802
- for (const id of ids) {
6803
- const el = ctx.store.getById(id);
6804
- if (el && el.type !== "arrow") movedNonArrowIds.add(id);
6805
- }
6806
- if (movedNonArrowIds.size === 0) return;
6807
- const updatedArrows = /* @__PURE__ */ new Set();
6808
- for (const id of movedNonArrowIds) {
6809
- const boundArrows = findBoundArrows(id, ctx.store);
6810
- for (const ba of boundArrows) {
6811
- if (updatedArrows.has(ba.id)) continue;
6812
- updatedArrows.add(ba.id);
6813
- const updates = updateBoundArrow(ba, ctx.store);
6814
- if (updates) ctx.store.update(ba.id, updates);
6815
- }
6816
- }
6922
+ updateArrowsBoundToElements(ids, ctx.store);
6817
6923
  }
6818
6924
  nudgeSelection(dx, dy, ctx) {
6819
6925
  let moved = false;
6820
6926
  for (const id of this._selectedIds) {
6821
6927
  const el = ctx.store.getById(id);
6822
6928
  if (!el || el.locked) continue;
6823
- if (el.type === "arrow") {
6824
- if (el.fromBinding || el.toBinding) continue;
6825
- ctx.store.update(id, {
6826
- position: { x: el.position.x + dx, y: el.position.y + dy },
6827
- from: { x: el.from.x + dx, y: el.from.y + dy },
6828
- to: { x: el.to.x + dx, y: el.to.y + dy }
6829
- });
6830
- } else {
6831
- ctx.store.update(id, {
6832
- position: { x: el.position.x + dx, y: el.position.y + dy }
6833
- });
6834
- }
6929
+ if (el.type === "arrow" && (el.fromBinding || el.toBinding)) continue;
6930
+ ctx.store.update(id, translateElementPatch(el, dx, dy));
6835
6931
  moved = true;
6836
6932
  }
6837
6933
  if (moved) {
@@ -8023,7 +8119,7 @@ var TemplateTool = class {
8023
8119
  };
8024
8120
 
8025
8121
  // src/index.ts
8026
- var VERSION = "0.31.0";
8122
+ var VERSION = "0.32.0";
8027
8123
  export {
8028
8124
  ArrowTool,
8029
8125
  AutoSave,