@fieldnotes/core 0.29.0 → 0.31.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;
@@ -752,6 +763,7 @@ declare class SelectTool implements Tool {
752
763
  private setHovered;
753
764
  private handleResize;
754
765
  private hitTestResizeHandle;
766
+ private hitTestLineHandles;
755
767
  private getHandlePositions;
756
768
  private renderMarquee;
757
769
  private renderSelectionBoxes;
@@ -960,6 +972,6 @@ declare class TemplateTool implements Tool {
960
972
  private notifyOptionsChange;
961
973
  }
962
974
 
963
- declare const VERSION = "0.29.0";
975
+ declare const VERSION = "0.31.0";
964
976
 
965
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 };
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;
@@ -752,6 +763,7 @@ declare class SelectTool implements Tool {
752
763
  private setHovered;
753
764
  private handleResize;
754
765
  private hitTestResizeHandle;
766
+ private hitTestLineHandles;
755
767
  private getHandlePositions;
756
768
  private renderMarquee;
757
769
  private renderSelectionBoxes;
@@ -960,6 +972,6 @@ declare class TemplateTool implements Tool {
960
972
  private notifyOptionsChange;
961
973
  }
962
974
 
963
- declare const VERSION = "0.29.0";
975
+ declare const VERSION = "0.31.0";
964
976
 
965
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 };
package/dist/index.js CHANGED
@@ -2195,6 +2195,26 @@ function getArrowRenderGeometry(arrow) {
2195
2195
  return geometry;
2196
2196
  }
2197
2197
 
2198
+ // src/elements/shape-geometry.ts
2199
+ function lineFromEndpoints(a, b) {
2200
+ return {
2201
+ position: { x: Math.min(a.x, b.x), y: Math.min(a.y, b.y) },
2202
+ size: { w: Math.abs(b.x - a.x), h: Math.abs(b.y - a.y) },
2203
+ flip: b.x > a.x !== b.y > a.y
2204
+ };
2205
+ }
2206
+ function lineEndpoints(shape) {
2207
+ const { x, y } = shape.position;
2208
+ const { w, h } = shape.size;
2209
+ return shape.flip ? [
2210
+ { x, y: y + h },
2211
+ { x: x + w, y }
2212
+ ] : [
2213
+ { x, y },
2214
+ { x: x + w, y: y + h }
2215
+ ];
2216
+ }
2217
+
2198
2218
  // src/elements/arrow-binding.ts
2199
2219
  var BINDABLE_TYPES = /* @__PURE__ */ new Set(["note", "text", "image", "html", "shape"]);
2200
2220
  function isBindable(element) {
@@ -2719,6 +2739,7 @@ var ElementRenderer = class {
2719
2739
  renderStroke(ctx, stroke) {
2720
2740
  if (stroke.points.length < 2) return;
2721
2741
  ctx.save();
2742
+ if (stroke.blendMode) ctx.globalCompositeOperation = stroke.blendMode;
2722
2743
  ctx.translate(stroke.position.x, stroke.position.y);
2723
2744
  ctx.strokeStyle = stroke.color;
2724
2745
  ctx.lineCap = "round";
@@ -2841,7 +2862,7 @@ var ElementRenderer = class {
2841
2862
  }
2842
2863
  renderShape(ctx, shape) {
2843
2864
  ctx.save();
2844
- if (shape.fillColor !== "none") {
2865
+ if (shape.fillColor !== "none" && shape.shape !== "line") {
2845
2866
  ctx.fillStyle = shape.fillColor;
2846
2867
  this.fillShapePath(ctx, shape);
2847
2868
  }
@@ -2880,6 +2901,15 @@ var ElementRenderer = class {
2880
2901
  ctx.stroke();
2881
2902
  break;
2882
2903
  }
2904
+ case "line": {
2905
+ const [a, b] = lineEndpoints(shape);
2906
+ ctx.lineCap = "round";
2907
+ ctx.beginPath();
2908
+ ctx.moveTo(a.x, a.y);
2909
+ ctx.lineTo(b.x, b.y);
2910
+ ctx.stroke();
2911
+ break;
2912
+ }
2883
2913
  }
2884
2914
  }
2885
2915
  renderGrid(ctx, grid) {
@@ -3153,7 +3183,7 @@ var ElementRenderer = class {
3153
3183
  // src/elements/element-factory.ts
3154
3184
  var DEFAULT_NOTE_FONT_SIZE = 18;
3155
3185
  function createStroke(input) {
3156
- return {
3186
+ const result = {
3157
3187
  id: createId("stroke"),
3158
3188
  type: "stroke",
3159
3189
  position: input.position ?? { x: 0, y: 0 },
@@ -3165,6 +3195,8 @@ function createStroke(input) {
3165
3195
  width: input.width ?? 2,
3166
3196
  opacity: input.opacity ?? 1
3167
3197
  };
3198
+ if (input.blendMode) result.blendMode = input.blendMode;
3199
+ return result;
3168
3200
  }
3169
3201
  function createNote(input) {
3170
3202
  return {
@@ -3229,7 +3261,7 @@ function createHtmlElement(input) {
3229
3261
  return el;
3230
3262
  }
3231
3263
  function createShape(input) {
3232
- return {
3264
+ const result = {
3233
3265
  id: createId("shape"),
3234
3266
  type: "shape",
3235
3267
  position: input.position,
@@ -3242,6 +3274,8 @@ function createShape(input) {
3242
3274
  strokeWidth: input.strokeWidth ?? 2,
3243
3275
  fillColor: input.fillColor ?? "none"
3244
3276
  };
3277
+ if (input.flip) result.flip = input.flip;
3278
+ return result;
3245
3279
  }
3246
3280
  function createGrid(input) {
3247
3281
  return {
@@ -6072,7 +6106,7 @@ var DEFAULT_MIN_POINT_DISTANCE = 3;
6072
6106
  var DEFAULT_PROGRESSIVE_THRESHOLD = 200;
6073
6107
  var PROGRESSIVE_HOT_ZONE = 30;
6074
6108
  var PencilTool = class {
6075
- name = "pencil";
6109
+ name;
6076
6110
  drawing = false;
6077
6111
  points = [];
6078
6112
  color;
@@ -6081,14 +6115,19 @@ var PencilTool = class {
6081
6115
  minPointDistance;
6082
6116
  progressiveThreshold;
6083
6117
  nextSimplifyAt;
6118
+ opacity;
6119
+ blendMode;
6084
6120
  optionListeners = /* @__PURE__ */ new Set();
6085
6121
  constructor(options = {}) {
6122
+ this.name = options.name ?? "pencil";
6086
6123
  this.color = options.color ?? "#000000";
6087
6124
  this.width = options.width ?? 2;
6088
6125
  this.smoothing = options.smoothing ?? DEFAULT_SMOOTHING;
6089
6126
  this.minPointDistance = options.minPointDistance ?? DEFAULT_MIN_POINT_DISTANCE;
6090
6127
  this.progressiveThreshold = options.progressiveSimplifyThreshold ?? DEFAULT_PROGRESSIVE_THRESHOLD;
6091
6128
  this.nextSimplifyAt = this.progressiveThreshold;
6129
+ this.opacity = options.opacity ?? 1;
6130
+ this.blendMode = options.blendMode;
6092
6131
  }
6093
6132
  onActivate(ctx) {
6094
6133
  ctx.setCursor?.("crosshair");
@@ -6102,7 +6141,9 @@ var PencilTool = class {
6102
6141
  width: this.width,
6103
6142
  smoothing: this.smoothing,
6104
6143
  minPointDistance: this.minPointDistance,
6105
- progressiveSimplifyThreshold: this.progressiveThreshold
6144
+ progressiveSimplifyThreshold: this.progressiveThreshold,
6145
+ opacity: this.opacity,
6146
+ blendMode: this.blendMode
6106
6147
  };
6107
6148
  }
6108
6149
  onOptionsChange(listener) {
@@ -6116,6 +6157,8 @@ var PencilTool = class {
6116
6157
  if (options.minPointDistance !== void 0) this.minPointDistance = options.minPointDistance;
6117
6158
  if (options.progressiveSimplifyThreshold !== void 0)
6118
6159
  this.progressiveThreshold = options.progressiveSimplifyThreshold;
6160
+ if (options.opacity !== void 0) this.opacity = options.opacity;
6161
+ if (options.blendMode !== void 0) this.blendMode = options.blendMode;
6119
6162
  this.notifyOptionsChange();
6120
6163
  }
6121
6164
  onPointerDown(state, ctx) {
@@ -6157,7 +6200,9 @@ var PencilTool = class {
6157
6200
  points: simplified,
6158
6201
  color: this.color,
6159
6202
  width: this.width,
6160
- layerId: ctx.activeLayerId ?? ""
6203
+ layerId: ctx.activeLayerId ?? "",
6204
+ opacity: this.opacity,
6205
+ blendMode: this.blendMode
6161
6206
  });
6162
6207
  ctx.store.add(stroke);
6163
6208
  computeStrokeSegments(stroke);
@@ -6173,7 +6218,8 @@ var PencilTool = class {
6173
6218
  ctx.strokeStyle = this.color;
6174
6219
  ctx.lineCap = "round";
6175
6220
  ctx.lineJoin = "round";
6176
- ctx.globalAlpha = 0.8;
6221
+ ctx.globalAlpha = this.blendMode ? this.opacity : 0.8;
6222
+ if (this.blendMode) ctx.globalCompositeOperation = this.blendMode;
6177
6223
  const segments = smoothToSegments(this.points);
6178
6224
  for (const seg of segments) {
6179
6225
  const w = (pressureToWidth(seg.start.pressure, this.width) + pressureToWidth(seg.end.pressure, this.width)) / 2;
@@ -6562,6 +6608,12 @@ var SelectTool = class {
6562
6608
  ctx.requestRender();
6563
6609
  return;
6564
6610
  }
6611
+ const lineHit = this.hitTestLineHandles(world, ctx);
6612
+ if (lineHit) {
6613
+ this.mode = { type: "line-handle", elementId: lineHit.elementId, fixed: lineHit.fixed };
6614
+ ctx.requestRender();
6615
+ return;
6616
+ }
6565
6617
  const templateResizeHit = this.hitTestTemplateResizeHandle(world, ctx);
6566
6618
  if (templateResizeHit) {
6567
6619
  this.mode = { type: "resizing-template", elementId: templateResizeHit };
@@ -6617,6 +6669,15 @@ var SelectTool = class {
6617
6669
  applyArrowHandleDrag(this.mode.handle, this.mode.elementId, world, ctx);
6618
6670
  return;
6619
6671
  }
6672
+ if (this.mode.type === "line-handle") {
6673
+ ctx.setCursor?.("grabbing");
6674
+ const el = ctx.store.getById(this.mode.elementId);
6675
+ if (el && el.type === "shape") {
6676
+ ctx.store.update(el.id, lineFromEndpoints(this.mode.fixed, world));
6677
+ }
6678
+ ctx.requestRender();
6679
+ return;
6680
+ }
6620
6681
  if (this.mode.type === "resizing-template") {
6621
6682
  ctx.setCursor?.("nwse-resize");
6622
6683
  this.handleTemplateResize(world, ctx);
@@ -6785,6 +6846,10 @@ var SelectTool = class {
6785
6846
  ctx.setCursor?.(getArrowHandleCursor(arrowHit.handle, false));
6786
6847
  return null;
6787
6848
  }
6849
+ if (this.hitTestLineHandles(world, ctx)) {
6850
+ ctx.setCursor?.("grab");
6851
+ return null;
6852
+ }
6788
6853
  const templateResizeHit = this.hitTestTemplateResizeHandle(world, ctx);
6789
6854
  if (templateResizeHit) {
6790
6855
  ctx.setCursor?.("nwse-resize");
@@ -6872,6 +6937,7 @@ var SelectTool = class {
6872
6937
  for (const id of this._selectedIds) {
6873
6938
  const el = ctx.store.getById(id);
6874
6939
  if (!el || !("size" in el)) continue;
6940
+ if (el.type === "shape" && el.shape === "line") continue;
6875
6941
  const bounds = getElementBounds(el);
6876
6942
  if (!bounds) continue;
6877
6943
  const corners = this.getHandlePositions(bounds);
@@ -6883,6 +6949,20 @@ var SelectTool = class {
6883
6949
  }
6884
6950
  return null;
6885
6951
  }
6952
+ hitTestLineHandles(world, ctx) {
6953
+ if (this._selectedIds.length === 0) return null;
6954
+ const zoom = ctx.camera.zoom;
6955
+ const r = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / zoom;
6956
+ const r2 = r * r;
6957
+ for (const id of this._selectedIds) {
6958
+ const el = ctx.store.getById(id);
6959
+ if (!el || el.type !== "shape" || el.shape !== "line") continue;
6960
+ const [a, b] = lineEndpoints(el);
6961
+ if ((world.x - a.x) ** 2 + (world.y - a.y) ** 2 <= r2) return { elementId: id, fixed: b };
6962
+ if ((world.x - b.x) ** 2 + (world.y - b.y) ** 2 <= r2) return { elementId: id, fixed: a };
6963
+ }
6964
+ return null;
6965
+ }
6886
6966
  getHandlePositions(bounds) {
6887
6967
  return [
6888
6968
  ["nw", { x: bounds.x, y: bounds.y }],
@@ -6920,6 +7000,19 @@ var SelectTool = class {
6920
7000
  this.renderBindingHighlights(canvasCtx, el, zoom);
6921
7001
  continue;
6922
7002
  }
7003
+ if (el.type === "shape" && el.shape === "line") {
7004
+ canvasCtx.setLineDash([]);
7005
+ canvasCtx.fillStyle = "#ffffff";
7006
+ const r = handleWorldSize / 2;
7007
+ for (const p of lineEndpoints(el)) {
7008
+ canvasCtx.beginPath();
7009
+ canvasCtx.arc(p.x, p.y, r, 0, Math.PI * 2);
7010
+ canvasCtx.fill();
7011
+ canvasCtx.stroke();
7012
+ }
7013
+ canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
7014
+ continue;
7015
+ }
6923
7016
  const bounds = getElementBounds(el);
6924
7017
  if (!bounds) continue;
6925
7018
  const pad = SELECTION_PAD / zoom;
@@ -7063,6 +7156,11 @@ var SelectTool = class {
7063
7156
  }
7064
7157
  isInsideBounds(point, el) {
7065
7158
  if (el.type === "grid") return false;
7159
+ if (el.type === "shape" && el.shape === "line") {
7160
+ const [a, b] = lineEndpoints(el);
7161
+ const threshold = Math.max(el.strokeWidth / 2, 6);
7162
+ return distSqToSegment(point, a, b) <= threshold * threshold;
7163
+ }
7066
7164
  if ("size" in el) {
7067
7165
  const s = el.size;
7068
7166
  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;
@@ -7376,6 +7474,15 @@ var ImageTool = class {
7376
7474
  };
7377
7475
 
7378
7476
  // src/tools/shape-tool.ts
7477
+ function snapTo45(start, end) {
7478
+ const dx = end.x - start.x;
7479
+ const dy = end.y - start.y;
7480
+ const len = Math.hypot(dx, dy);
7481
+ if (len === 0) return { ...end };
7482
+ const step = Math.PI / 4;
7483
+ const angle = Math.round(Math.atan2(dy, dx) / step) * step;
7484
+ return { x: start.x + Math.cos(angle) * len, y: start.y + Math.sin(angle) * len };
7485
+ }
7379
7486
  var ShapeTool = class {
7380
7487
  name = "shape";
7381
7488
  drawing = false;
@@ -7433,13 +7540,17 @@ var ShapeTool = class {
7433
7540
  onPointerMove(state, ctx) {
7434
7541
  if (!this.drawing) return;
7435
7542
  this.end = this.snap(ctx.camera.screenToWorld({ x: state.x, y: state.y }), ctx);
7543
+ if (this.shape === "line" && this.shiftHeld) {
7544
+ this.end = snapTo45(this.start, this.end);
7545
+ }
7436
7546
  ctx.requestRender();
7437
7547
  }
7438
7548
  onPointerUp(_state, ctx) {
7439
7549
  if (!this.drawing) return;
7440
7550
  this.drawing = false;
7441
7551
  const { position, size } = this.computeRect();
7442
- if (size.w === 0 || size.h === 0) return;
7552
+ const isLine = this.shape === "line";
7553
+ if (isLine ? size.w === 0 && size.h === 0 : size.w === 0 || size.h === 0) return;
7443
7554
  const shape = createShape({
7444
7555
  position,
7445
7556
  size,
@@ -7447,6 +7558,7 @@ var ShapeTool = class {
7447
7558
  strokeColor: this.strokeColor,
7448
7559
  strokeWidth: this.strokeWidth,
7449
7560
  fillColor: this.fillColor,
7561
+ ...isLine ? { flip: this.end.x > this.start.x !== this.end.y > this.start.y } : {},
7450
7562
  layerId: ctx.activeLayerId ?? ""
7451
7563
  });
7452
7564
  ctx.store.add(shape);
@@ -7480,6 +7592,13 @@ var ShapeTool = class {
7480
7592
  ctx.stroke();
7481
7593
  break;
7482
7594
  }
7595
+ case "line":
7596
+ ctx.lineCap = "round";
7597
+ ctx.beginPath();
7598
+ ctx.moveTo(this.start.x, this.start.y);
7599
+ ctx.lineTo(this.end.x, this.end.y);
7600
+ ctx.stroke();
7601
+ break;
7483
7602
  }
7484
7603
  ctx.restore();
7485
7604
  }
@@ -7488,7 +7607,7 @@ var ShapeTool = class {
7488
7607
  let y = Math.min(this.start.y, this.end.y);
7489
7608
  let w = Math.abs(this.end.x - this.start.x);
7490
7609
  let h = Math.abs(this.end.y - this.start.y);
7491
- if (this.shiftHeld) {
7610
+ if (this.shiftHeld && this.shape !== "line") {
7492
7611
  const side = Math.max(w, h);
7493
7612
  w = side;
7494
7613
  h = side;
@@ -7904,7 +8023,7 @@ var TemplateTool = class {
7904
8023
  };
7905
8024
 
7906
8025
  // src/index.ts
7907
- var VERSION = "0.29.0";
8026
+ var VERSION = "0.31.0";
7908
8027
  export {
7909
8028
  ArrowTool,
7910
8029
  AutoSave,