@fieldnotes/core 0.28.0 → 0.30.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
@@ -27,6 +27,8 @@ interface StrokeElement extends BaseElement {
27
27
  color: string;
28
28
  width: number;
29
29
  opacity: number;
30
+ /** Optional canvas blend mode (e.g. highlighter uses 'multiply'). */
31
+ blendMode?: 'multiply';
30
32
  }
31
33
  interface NoteElement extends BaseElement {
32
34
  type: 'note';
@@ -72,7 +74,7 @@ interface TextElement extends BaseElement {
72
74
  color: string;
73
75
  textAlign: 'left' | 'center' | 'right';
74
76
  }
75
- type ShapeKind = 'rectangle' | 'ellipse';
77
+ type ShapeKind = 'rectangle' | 'ellipse' | 'line';
76
78
  interface ShapeElement extends BaseElement {
77
79
  type: 'shape';
78
80
  shape: ShapeKind;
@@ -80,6 +82,8 @@ interface ShapeElement extends BaseElement {
80
82
  strokeColor: string;
81
83
  strokeWidth: number;
82
84
  fillColor: string;
85
+ /** Line-only: which bbox diagonal the segment runs along. Absent/false = main diagonal. */
86
+ flip?: boolean;
83
87
  }
84
88
  type HexOrientation = 'pointy' | 'flat';
85
89
  interface GridElement extends BaseElement {
@@ -562,6 +566,7 @@ interface StrokeInput extends BaseDefaults {
562
566
  color?: string;
563
567
  width?: number;
564
568
  opacity?: number;
569
+ blendMode?: 'multiply';
565
570
  }
566
571
  interface NoteInput extends BaseDefaults {
567
572
  position: Point;
@@ -612,6 +617,7 @@ interface ShapeInput extends BaseDefaults {
612
617
  strokeColor?: string;
613
618
  strokeWidth?: number;
614
619
  fillColor?: string;
620
+ flip?: boolean;
615
621
  }
616
622
  declare function createShape(input: ShapeInput): ShapeElement;
617
623
  interface GridInput extends BaseDefaults {
@@ -669,14 +675,17 @@ declare class HandTool implements Tool {
669
675
  }
670
676
 
671
677
  interface PencilToolOptions {
678
+ name?: string;
672
679
  color?: string;
673
680
  width?: number;
674
681
  smoothing?: number;
675
682
  minPointDistance?: number;
676
683
  progressiveSimplifyThreshold?: number;
684
+ opacity?: number;
685
+ blendMode?: 'multiply';
677
686
  }
678
687
  declare class PencilTool implements Tool {
679
- readonly name = "pencil";
688
+ readonly name: string;
680
689
  private drawing;
681
690
  private points;
682
691
  private color;
@@ -685,6 +694,8 @@ declare class PencilTool implements Tool {
685
694
  private minPointDistance;
686
695
  private progressiveThreshold;
687
696
  private nextSimplifyAt;
697
+ private opacity;
698
+ private blendMode;
688
699
  private optionListeners;
689
700
  constructor(options?: PencilToolOptions);
690
701
  onActivate(ctx: ToolContext): void;
@@ -701,14 +712,17 @@ declare class PencilTool implements Tool {
701
712
 
702
713
  interface EraserToolOptions {
703
714
  radius?: number;
715
+ mode?: 'partial' | 'stroke';
704
716
  }
705
717
  declare class EraserTool implements Tool {
706
718
  readonly name = "eraser";
707
719
  private erasing;
708
- private readonly radius;
709
- private readonly cursor;
720
+ private radius;
721
+ private cursor;
722
+ private mode;
710
723
  constructor(options?: EraserToolOptions);
711
724
  getOptions(): EraserToolOptions;
725
+ setOptions(options: EraserToolOptions): void;
712
726
  onActivate(ctx: ToolContext): void;
713
727
  onDeactivate(ctx: ToolContext): void;
714
728
  onPointerDown(state: PointerState, ctx: ToolContext): void;
@@ -957,6 +971,6 @@ declare class TemplateTool implements Tool {
957
971
  private notifyOptionsChange;
958
972
  }
959
973
 
960
- declare const VERSION = "0.28.0";
974
+ declare const VERSION = "0.30.0";
961
975
 
962
976
  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 };
package/dist/index.d.ts CHANGED
@@ -27,6 +27,8 @@ interface StrokeElement extends BaseElement {
27
27
  color: string;
28
28
  width: number;
29
29
  opacity: number;
30
+ /** Optional canvas blend mode (e.g. highlighter uses 'multiply'). */
31
+ blendMode?: 'multiply';
30
32
  }
31
33
  interface NoteElement extends BaseElement {
32
34
  type: 'note';
@@ -72,7 +74,7 @@ interface TextElement extends BaseElement {
72
74
  color: string;
73
75
  textAlign: 'left' | 'center' | 'right';
74
76
  }
75
- type ShapeKind = 'rectangle' | 'ellipse';
77
+ type ShapeKind = 'rectangle' | 'ellipse' | 'line';
76
78
  interface ShapeElement extends BaseElement {
77
79
  type: 'shape';
78
80
  shape: ShapeKind;
@@ -80,6 +82,8 @@ interface ShapeElement extends BaseElement {
80
82
  strokeColor: string;
81
83
  strokeWidth: number;
82
84
  fillColor: string;
85
+ /** Line-only: which bbox diagonal the segment runs along. Absent/false = main diagonal. */
86
+ flip?: boolean;
83
87
  }
84
88
  type HexOrientation = 'pointy' | 'flat';
85
89
  interface GridElement extends BaseElement {
@@ -562,6 +566,7 @@ interface StrokeInput extends BaseDefaults {
562
566
  color?: string;
563
567
  width?: number;
564
568
  opacity?: number;
569
+ blendMode?: 'multiply';
565
570
  }
566
571
  interface NoteInput extends BaseDefaults {
567
572
  position: Point;
@@ -612,6 +617,7 @@ interface ShapeInput extends BaseDefaults {
612
617
  strokeColor?: string;
613
618
  strokeWidth?: number;
614
619
  fillColor?: string;
620
+ flip?: boolean;
615
621
  }
616
622
  declare function createShape(input: ShapeInput): ShapeElement;
617
623
  interface GridInput extends BaseDefaults {
@@ -669,14 +675,17 @@ declare class HandTool implements Tool {
669
675
  }
670
676
 
671
677
  interface PencilToolOptions {
678
+ name?: string;
672
679
  color?: string;
673
680
  width?: number;
674
681
  smoothing?: number;
675
682
  minPointDistance?: number;
676
683
  progressiveSimplifyThreshold?: number;
684
+ opacity?: number;
685
+ blendMode?: 'multiply';
677
686
  }
678
687
  declare class PencilTool implements Tool {
679
- readonly name = "pencil";
688
+ readonly name: string;
680
689
  private drawing;
681
690
  private points;
682
691
  private color;
@@ -685,6 +694,8 @@ declare class PencilTool implements Tool {
685
694
  private minPointDistance;
686
695
  private progressiveThreshold;
687
696
  private nextSimplifyAt;
697
+ private opacity;
698
+ private blendMode;
688
699
  private optionListeners;
689
700
  constructor(options?: PencilToolOptions);
690
701
  onActivate(ctx: ToolContext): void;
@@ -701,14 +712,17 @@ declare class PencilTool implements Tool {
701
712
 
702
713
  interface EraserToolOptions {
703
714
  radius?: number;
715
+ mode?: 'partial' | 'stroke';
704
716
  }
705
717
  declare class EraserTool implements Tool {
706
718
  readonly name = "eraser";
707
719
  private erasing;
708
- private readonly radius;
709
- private readonly cursor;
720
+ private radius;
721
+ private cursor;
722
+ private mode;
710
723
  constructor(options?: EraserToolOptions);
711
724
  getOptions(): EraserToolOptions;
725
+ setOptions(options: EraserToolOptions): void;
712
726
  onActivate(ctx: ToolContext): void;
713
727
  onDeactivate(ctx: ToolContext): void;
714
728
  onPointerDown(state: PointerState, ctx: ToolContext): void;
@@ -957,6 +971,6 @@ declare class TemplateTool implements Tool {
957
971
  private notifyOptionsChange;
958
972
  }
959
973
 
960
- declare const VERSION = "0.28.0";
974
+ declare const VERSION = "0.30.0";
961
975
 
962
976
  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 };
package/dist/index.js CHANGED
@@ -2195,6 +2195,19 @@ function getArrowRenderGeometry(arrow) {
2195
2195
  return geometry;
2196
2196
  }
2197
2197
 
2198
+ // src/elements/shape-geometry.ts
2199
+ function lineEndpoints(shape) {
2200
+ const { x, y } = shape.position;
2201
+ const { w, h } = shape.size;
2202
+ return shape.flip ? [
2203
+ { x, y: y + h },
2204
+ { x: x + w, y }
2205
+ ] : [
2206
+ { x, y },
2207
+ { x: x + w, y: y + h }
2208
+ ];
2209
+ }
2210
+
2198
2211
  // src/elements/arrow-binding.ts
2199
2212
  var BINDABLE_TYPES = /* @__PURE__ */ new Set(["note", "text", "image", "html", "shape"]);
2200
2213
  function isBindable(element) {
@@ -2719,6 +2732,7 @@ var ElementRenderer = class {
2719
2732
  renderStroke(ctx, stroke) {
2720
2733
  if (stroke.points.length < 2) return;
2721
2734
  ctx.save();
2735
+ if (stroke.blendMode) ctx.globalCompositeOperation = stroke.blendMode;
2722
2736
  ctx.translate(stroke.position.x, stroke.position.y);
2723
2737
  ctx.strokeStyle = stroke.color;
2724
2738
  ctx.lineCap = "round";
@@ -2841,7 +2855,7 @@ var ElementRenderer = class {
2841
2855
  }
2842
2856
  renderShape(ctx, shape) {
2843
2857
  ctx.save();
2844
- if (shape.fillColor !== "none") {
2858
+ if (shape.fillColor !== "none" && shape.shape !== "line") {
2845
2859
  ctx.fillStyle = shape.fillColor;
2846
2860
  this.fillShapePath(ctx, shape);
2847
2861
  }
@@ -2880,6 +2894,15 @@ var ElementRenderer = class {
2880
2894
  ctx.stroke();
2881
2895
  break;
2882
2896
  }
2897
+ case "line": {
2898
+ const [a, b] = lineEndpoints(shape);
2899
+ ctx.lineCap = "round";
2900
+ ctx.beginPath();
2901
+ ctx.moveTo(a.x, a.y);
2902
+ ctx.lineTo(b.x, b.y);
2903
+ ctx.stroke();
2904
+ break;
2905
+ }
2883
2906
  }
2884
2907
  }
2885
2908
  renderGrid(ctx, grid) {
@@ -3153,7 +3176,7 @@ var ElementRenderer = class {
3153
3176
  // src/elements/element-factory.ts
3154
3177
  var DEFAULT_NOTE_FONT_SIZE = 18;
3155
3178
  function createStroke(input) {
3156
- return {
3179
+ const result = {
3157
3180
  id: createId("stroke"),
3158
3181
  type: "stroke",
3159
3182
  position: input.position ?? { x: 0, y: 0 },
@@ -3165,6 +3188,8 @@ function createStroke(input) {
3165
3188
  width: input.width ?? 2,
3166
3189
  opacity: input.opacity ?? 1
3167
3190
  };
3191
+ if (input.blendMode) result.blendMode = input.blendMode;
3192
+ return result;
3168
3193
  }
3169
3194
  function createNote(input) {
3170
3195
  return {
@@ -3229,7 +3254,7 @@ function createHtmlElement(input) {
3229
3254
  return el;
3230
3255
  }
3231
3256
  function createShape(input) {
3232
- return {
3257
+ const result = {
3233
3258
  id: createId("shape"),
3234
3259
  type: "shape",
3235
3260
  position: input.position,
@@ -3242,6 +3267,8 @@ function createShape(input) {
3242
3267
  strokeWidth: input.strokeWidth ?? 2,
3243
3268
  fillColor: input.fillColor ?? "none"
3244
3269
  };
3270
+ if (input.flip) result.flip = input.flip;
3271
+ return result;
3245
3272
  }
3246
3273
  function createGrid(input) {
3247
3274
  return {
@@ -6072,7 +6099,7 @@ var DEFAULT_MIN_POINT_DISTANCE = 3;
6072
6099
  var DEFAULT_PROGRESSIVE_THRESHOLD = 200;
6073
6100
  var PROGRESSIVE_HOT_ZONE = 30;
6074
6101
  var PencilTool = class {
6075
- name = "pencil";
6102
+ name;
6076
6103
  drawing = false;
6077
6104
  points = [];
6078
6105
  color;
@@ -6081,14 +6108,19 @@ var PencilTool = class {
6081
6108
  minPointDistance;
6082
6109
  progressiveThreshold;
6083
6110
  nextSimplifyAt;
6111
+ opacity;
6112
+ blendMode;
6084
6113
  optionListeners = /* @__PURE__ */ new Set();
6085
6114
  constructor(options = {}) {
6115
+ this.name = options.name ?? "pencil";
6086
6116
  this.color = options.color ?? "#000000";
6087
6117
  this.width = options.width ?? 2;
6088
6118
  this.smoothing = options.smoothing ?? DEFAULT_SMOOTHING;
6089
6119
  this.minPointDistance = options.minPointDistance ?? DEFAULT_MIN_POINT_DISTANCE;
6090
6120
  this.progressiveThreshold = options.progressiveSimplifyThreshold ?? DEFAULT_PROGRESSIVE_THRESHOLD;
6091
6121
  this.nextSimplifyAt = this.progressiveThreshold;
6122
+ this.opacity = options.opacity ?? 1;
6123
+ this.blendMode = options.blendMode;
6092
6124
  }
6093
6125
  onActivate(ctx) {
6094
6126
  ctx.setCursor?.("crosshair");
@@ -6102,7 +6134,9 @@ var PencilTool = class {
6102
6134
  width: this.width,
6103
6135
  smoothing: this.smoothing,
6104
6136
  minPointDistance: this.minPointDistance,
6105
- progressiveSimplifyThreshold: this.progressiveThreshold
6137
+ progressiveSimplifyThreshold: this.progressiveThreshold,
6138
+ opacity: this.opacity,
6139
+ blendMode: this.blendMode
6106
6140
  };
6107
6141
  }
6108
6142
  onOptionsChange(listener) {
@@ -6116,6 +6150,8 @@ var PencilTool = class {
6116
6150
  if (options.minPointDistance !== void 0) this.minPointDistance = options.minPointDistance;
6117
6151
  if (options.progressiveSimplifyThreshold !== void 0)
6118
6152
  this.progressiveThreshold = options.progressiveSimplifyThreshold;
6153
+ if (options.opacity !== void 0) this.opacity = options.opacity;
6154
+ if (options.blendMode !== void 0) this.blendMode = options.blendMode;
6119
6155
  this.notifyOptionsChange();
6120
6156
  }
6121
6157
  onPointerDown(state, ctx) {
@@ -6157,7 +6193,9 @@ var PencilTool = class {
6157
6193
  points: simplified,
6158
6194
  color: this.color,
6159
6195
  width: this.width,
6160
- layerId: ctx.activeLayerId ?? ""
6196
+ layerId: ctx.activeLayerId ?? "",
6197
+ opacity: this.opacity,
6198
+ blendMode: this.blendMode
6161
6199
  });
6162
6200
  ctx.store.add(stroke);
6163
6201
  computeStrokeSegments(stroke);
@@ -6173,7 +6211,8 @@ var PencilTool = class {
6173
6211
  ctx.strokeStyle = this.color;
6174
6212
  ctx.lineCap = "round";
6175
6213
  ctx.lineJoin = "round";
6176
- ctx.globalAlpha = 0.8;
6214
+ ctx.globalAlpha = this.blendMode ? this.opacity : 0.8;
6215
+ if (this.blendMode) ctx.globalCompositeOperation = this.blendMode;
6177
6216
  const segments = smoothToSegments(this.points);
6178
6217
  for (const seg of segments) {
6179
6218
  const w = (pressureToWidth(seg.start.pressure, this.width) + pressureToWidth(seg.end.pressure, this.width)) / 2;
@@ -6210,6 +6249,79 @@ function hitTestStroke(stroke, point, radius) {
6210
6249
  return false;
6211
6250
  }
6212
6251
 
6252
+ // src/elements/stroke-erase.ts
6253
+ function lerp(a, b, t) {
6254
+ return {
6255
+ x: a.x + (b.x - a.x) * t,
6256
+ y: a.y + (b.y - a.y) * t,
6257
+ pressure: a.pressure + (b.pressure - a.pressure) * t
6258
+ };
6259
+ }
6260
+ function erasePoints(points, eraser, radius) {
6261
+ const r2 = radius * radius;
6262
+ if (points.length < 2) {
6263
+ const p = points[0];
6264
+ if (p && (p.x - eraser.x) ** 2 + (p.y - eraser.y) ** 2 <= r2) return [];
6265
+ return null;
6266
+ }
6267
+ const runs = [];
6268
+ let current = [];
6269
+ let erased = false;
6270
+ const flush = () => {
6271
+ if (current.length >= 2) runs.push(current);
6272
+ current = [];
6273
+ };
6274
+ for (let i = 0; i < points.length - 1; i++) {
6275
+ const a = points[i];
6276
+ const b = points[i + 1];
6277
+ if (!a || !b) continue;
6278
+ const dx = b.x - a.x;
6279
+ const dy = b.y - a.y;
6280
+ const fx = a.x - eraser.x;
6281
+ const fy = a.y - eraser.y;
6282
+ const A = dx * dx + dy * dy;
6283
+ const B = 2 * (fx * dx + fy * dy);
6284
+ const C = fx * fx + fy * fy - r2;
6285
+ let tLo = 1;
6286
+ let tHi = 0;
6287
+ if (A === 0) {
6288
+ if (C <= 0) {
6289
+ tLo = 0;
6290
+ tHi = 1;
6291
+ }
6292
+ } else {
6293
+ const disc = B * B - 4 * A * C;
6294
+ if (disc >= 0) {
6295
+ const sq = Math.sqrt(disc);
6296
+ const lo = Math.max(0, (-B - sq) / (2 * A));
6297
+ const hi = Math.min(1, (-B + sq) / (2 * A));
6298
+ if (lo < hi) {
6299
+ tLo = lo;
6300
+ tHi = hi;
6301
+ }
6302
+ }
6303
+ }
6304
+ if (tLo > tHi) {
6305
+ if (current.length === 0) current.push(a);
6306
+ current.push(b);
6307
+ continue;
6308
+ }
6309
+ erased = true;
6310
+ if (tLo > 0) {
6311
+ if (current.length === 0) current.push(a);
6312
+ current.push(lerp(a, b, tLo));
6313
+ flush();
6314
+ } else {
6315
+ flush();
6316
+ }
6317
+ if (tHi < 1) {
6318
+ current = [lerp(a, b, tHi), b];
6319
+ }
6320
+ }
6321
+ flush();
6322
+ return erased ? runs : null;
6323
+ }
6324
+
6213
6325
  // src/tools/eraser-tool.ts
6214
6326
  var DEFAULT_RADIUS = 20;
6215
6327
  function makeEraserCursor(radius) {
@@ -6222,12 +6334,21 @@ var EraserTool = class {
6222
6334
  erasing = false;
6223
6335
  radius;
6224
6336
  cursor;
6337
+ mode;
6225
6338
  constructor(options = {}) {
6226
6339
  this.radius = options.radius ?? DEFAULT_RADIUS;
6227
6340
  this.cursor = makeEraserCursor(this.radius);
6341
+ this.mode = options.mode ?? "partial";
6228
6342
  }
6229
6343
  getOptions() {
6230
- return { radius: this.radius };
6344
+ return { radius: this.radius, mode: this.mode };
6345
+ }
6346
+ setOptions(options) {
6347
+ if (options.mode !== void 0) this.mode = options.mode;
6348
+ if (options.radius !== void 0) {
6349
+ this.radius = options.radius;
6350
+ this.cursor = makeEraserCursor(this.radius);
6351
+ }
6231
6352
  }
6232
6353
  onActivate(ctx) {
6233
6354
  ctx.setCursor?.(this.cursor);
@@ -6260,10 +6381,30 @@ var EraserTool = class {
6260
6381
  if (el.type !== "stroke") continue;
6261
6382
  if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
6262
6383
  if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
6263
- if (this.strokeIntersects(el, world)) {
6384
+ if (!this.strokeIntersects(el, world)) continue;
6385
+ if (this.mode === "stroke") {
6264
6386
  ctx.store.remove(el.id);
6265
6387
  erased = true;
6388
+ continue;
6266
6389
  }
6390
+ const localEraser = { x: world.x - el.position.x, y: world.y - el.position.y };
6391
+ const runs = erasePoints(el.points, localEraser, this.radius);
6392
+ if (runs === null) continue;
6393
+ ctx.store.remove(el.id);
6394
+ for (const run of runs) {
6395
+ ctx.store.add(
6396
+ createStroke({
6397
+ points: run,
6398
+ color: el.color,
6399
+ width: el.width,
6400
+ opacity: el.opacity,
6401
+ layerId: el.layerId,
6402
+ zIndex: el.zIndex,
6403
+ position: el.position
6404
+ })
6405
+ );
6406
+ }
6407
+ erased = true;
6267
6408
  }
6268
6409
  if (erased) ctx.requestRender();
6269
6410
  }
@@ -6961,6 +7102,11 @@ var SelectTool = class {
6961
7102
  }
6962
7103
  isInsideBounds(point, el) {
6963
7104
  if (el.type === "grid") return false;
7105
+ if (el.type === "shape" && el.shape === "line") {
7106
+ const [a, b] = lineEndpoints(el);
7107
+ const threshold = Math.max(el.strokeWidth / 2, 6);
7108
+ return distSqToSegment(point, a, b) <= threshold * threshold;
7109
+ }
6964
7110
  if ("size" in el) {
6965
7111
  const s = el.size;
6966
7112
  return point.x >= el.position.x && point.x <= el.position.x + s.w && point.y >= el.position.y && point.y <= el.position.y + s.h;
@@ -7274,6 +7420,15 @@ var ImageTool = class {
7274
7420
  };
7275
7421
 
7276
7422
  // src/tools/shape-tool.ts
7423
+ function snapTo45(start, end) {
7424
+ const dx = end.x - start.x;
7425
+ const dy = end.y - start.y;
7426
+ const len = Math.hypot(dx, dy);
7427
+ if (len === 0) return { ...end };
7428
+ const step = Math.PI / 4;
7429
+ const angle = Math.round(Math.atan2(dy, dx) / step) * step;
7430
+ return { x: start.x + Math.cos(angle) * len, y: start.y + Math.sin(angle) * len };
7431
+ }
7277
7432
  var ShapeTool = class {
7278
7433
  name = "shape";
7279
7434
  drawing = false;
@@ -7331,13 +7486,17 @@ var ShapeTool = class {
7331
7486
  onPointerMove(state, ctx) {
7332
7487
  if (!this.drawing) return;
7333
7488
  this.end = this.snap(ctx.camera.screenToWorld({ x: state.x, y: state.y }), ctx);
7489
+ if (this.shape === "line" && this.shiftHeld) {
7490
+ this.end = snapTo45(this.start, this.end);
7491
+ }
7334
7492
  ctx.requestRender();
7335
7493
  }
7336
7494
  onPointerUp(_state, ctx) {
7337
7495
  if (!this.drawing) return;
7338
7496
  this.drawing = false;
7339
7497
  const { position, size } = this.computeRect();
7340
- if (size.w === 0 || size.h === 0) return;
7498
+ const isLine = this.shape === "line";
7499
+ if (isLine ? size.w === 0 && size.h === 0 : size.w === 0 || size.h === 0) return;
7341
7500
  const shape = createShape({
7342
7501
  position,
7343
7502
  size,
@@ -7345,6 +7504,7 @@ var ShapeTool = class {
7345
7504
  strokeColor: this.strokeColor,
7346
7505
  strokeWidth: this.strokeWidth,
7347
7506
  fillColor: this.fillColor,
7507
+ ...isLine ? { flip: this.end.x > this.start.x !== this.end.y > this.start.y } : {},
7348
7508
  layerId: ctx.activeLayerId ?? ""
7349
7509
  });
7350
7510
  ctx.store.add(shape);
@@ -7378,6 +7538,13 @@ var ShapeTool = class {
7378
7538
  ctx.stroke();
7379
7539
  break;
7380
7540
  }
7541
+ case "line":
7542
+ ctx.lineCap = "round";
7543
+ ctx.beginPath();
7544
+ ctx.moveTo(this.start.x, this.start.y);
7545
+ ctx.lineTo(this.end.x, this.end.y);
7546
+ ctx.stroke();
7547
+ break;
7381
7548
  }
7382
7549
  ctx.restore();
7383
7550
  }
@@ -7386,7 +7553,7 @@ var ShapeTool = class {
7386
7553
  let y = Math.min(this.start.y, this.end.y);
7387
7554
  let w = Math.abs(this.end.x - this.start.x);
7388
7555
  let h = Math.abs(this.end.y - this.start.y);
7389
- if (this.shiftHeld) {
7556
+ if (this.shiftHeld && this.shape !== "line") {
7390
7557
  const side = Math.max(w, h);
7391
7558
  w = side;
7392
7559
  h = side;
@@ -7802,7 +7969,7 @@ var TemplateTool = class {
7802
7969
  };
7803
7970
 
7804
7971
  // src/index.ts
7805
- var VERSION = "0.28.0";
7972
+ var VERSION = "0.30.0";
7806
7973
  export {
7807
7974
  ArrowTool,
7808
7975
  AutoSave,