@fieldnotes/core 0.2.0 → 0.2.2

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
@@ -13,6 +13,11 @@ interface Point {
13
13
  x: number;
14
14
  y: number;
15
15
  }
16
+ interface StrokePoint {
17
+ x: number;
18
+ y: number;
19
+ pressure: number;
20
+ }
16
21
  interface Size {
17
22
  w: number;
18
23
  h: number;
@@ -28,7 +33,7 @@ interface BaseElement {
28
33
  }
29
34
  interface StrokeElement extends BaseElement {
30
35
  type: 'stroke';
31
- points: Point[];
36
+ points: StrokePoint[];
32
37
  color: string;
33
38
  width: number;
34
39
  opacity: number;
@@ -200,6 +205,7 @@ declare class ToolManager {
200
205
  get activeTool(): Tool | null;
201
206
  get toolNames(): string[];
202
207
  register(tool: Tool): void;
208
+ getTool<T extends Tool = Tool>(name: string): T | undefined;
203
209
  setTool(name: string, ctx: ToolContext): void;
204
210
  handlePointerDown(state: PointerState, ctx: ToolContext): void;
205
211
  handlePointerMove(state: PointerState, ctx: ToolContext): void;
@@ -280,7 +286,6 @@ declare class InputHandler {
280
286
  destroy(): void;
281
287
  private bind;
282
288
  private onWheel;
283
- private isInteractiveHtmlContent;
284
289
  private onPointerDown;
285
290
  private onPointerMove;
286
291
  private onPointerUp;
@@ -328,6 +333,7 @@ declare class Viewport {
328
333
  private needsRender;
329
334
  private domNodes;
330
335
  private htmlContent;
336
+ private interactingElementId;
331
337
  constructor(container: HTMLElement, options?: ViewportOptions);
332
338
  get ctx(): CanvasRenderingContext2D | null;
333
339
  requestRender(): void;
@@ -343,7 +349,7 @@ declare class Viewport {
343
349
  }, size?: {
344
350
  w: number;
345
351
  h: number;
346
- }): void;
352
+ }): string;
347
353
  addHtmlElement(dom: HTMLElement, position: {
348
354
  x: number;
349
355
  y: number;
@@ -356,6 +362,12 @@ declare class Viewport {
356
362
  private render;
357
363
  private startEditingNote;
358
364
  private onDblClick;
365
+ private hitTestWorld;
366
+ private startInteracting;
367
+ stopInteracting(): void;
368
+ private onInteractNodePointerDown;
369
+ private onInteractKeyDown;
370
+ private onInteractPointerDown;
359
371
  private onDragOver;
360
372
  private onDrop;
361
373
  private syncDomNode;
@@ -401,7 +413,7 @@ interface BaseDefaults {
401
413
  locked?: boolean;
402
414
  }
403
415
  interface StrokeInput extends BaseDefaults {
404
- points: Point[];
416
+ points: StrokePoint[];
405
417
  color?: string;
406
418
  width?: number;
407
419
  opacity?: number;
@@ -488,6 +500,7 @@ declare class HandTool implements Tool {
488
500
  interface PencilToolOptions {
489
501
  color?: string;
490
502
  width?: number;
503
+ smoothing?: number;
491
504
  }
492
505
  declare class PencilTool implements Tool {
493
506
  readonly name = "pencil";
@@ -495,6 +508,7 @@ declare class PencilTool implements Tool {
495
508
  private points;
496
509
  private color;
497
510
  private width;
511
+ private smoothing;
498
512
  constructor(options?: PencilToolOptions);
499
513
  onActivate(ctx: ToolContext): void;
500
514
  onDeactivate(ctx: ToolContext): void;
@@ -565,6 +579,7 @@ declare class ArrowTool implements Tool {
565
579
  private color;
566
580
  private width;
567
581
  constructor(options?: ArrowToolOptions);
582
+ setOptions(options: ArrowToolOptions): void;
568
583
  onPointerDown(state: PointerState, ctx: ToolContext): void;
569
584
  onPointerMove(state: PointerState, ctx: ToolContext): void;
570
585
  onPointerUp(_state: PointerState, ctx: ToolContext): void;
@@ -580,6 +595,7 @@ declare class NoteTool implements Tool {
580
595
  private backgroundColor;
581
596
  private size;
582
597
  constructor(options?: NoteToolOptions);
598
+ setOptions(options: NoteToolOptions): void;
583
599
  onPointerDown(_state: PointerState, _ctx: ToolContext): void;
584
600
  onPointerMove(_state: PointerState, _ctx: ToolContext): void;
585
601
  onPointerUp(state: PointerState, ctx: ToolContext): void;
@@ -599,6 +615,6 @@ declare class ImageTool implements Tool {
599
615
  onPointerUp(state: PointerState, ctx: ToolContext): void;
600
616
  }
601
617
 
602
- declare const VERSION = "0.1.2";
618
+ declare const VERSION = "0.2.2";
603
619
 
604
- export { AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Bounds, Camera, type CameraOptions, type CanvasElement, type CanvasState, type Command, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, HandTool, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputHandler, NoteEditor, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, RemoveElementCommand, SelectTool, type Size, type StrokeElement, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, VERSION, Viewport, type ViewportOptions, createArrow, createHtmlElement, createId, createImage, createNote, createStroke, exportState, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, isNearBezier, parseState };
620
+ export { AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Bounds, Camera, type CameraOptions, type CanvasElement, type CanvasState, type Command, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, HandTool, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputHandler, NoteEditor, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, RemoveElementCommand, SelectTool, type Size, type StrokeElement, type StrokePoint, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, VERSION, Viewport, type ViewportOptions, createArrow, createHtmlElement, createId, createImage, createNote, createStroke, exportState, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, isNearBezier, parseState };
package/dist/index.d.ts CHANGED
@@ -13,6 +13,11 @@ interface Point {
13
13
  x: number;
14
14
  y: number;
15
15
  }
16
+ interface StrokePoint {
17
+ x: number;
18
+ y: number;
19
+ pressure: number;
20
+ }
16
21
  interface Size {
17
22
  w: number;
18
23
  h: number;
@@ -28,7 +33,7 @@ interface BaseElement {
28
33
  }
29
34
  interface StrokeElement extends BaseElement {
30
35
  type: 'stroke';
31
- points: Point[];
36
+ points: StrokePoint[];
32
37
  color: string;
33
38
  width: number;
34
39
  opacity: number;
@@ -200,6 +205,7 @@ declare class ToolManager {
200
205
  get activeTool(): Tool | null;
201
206
  get toolNames(): string[];
202
207
  register(tool: Tool): void;
208
+ getTool<T extends Tool = Tool>(name: string): T | undefined;
203
209
  setTool(name: string, ctx: ToolContext): void;
204
210
  handlePointerDown(state: PointerState, ctx: ToolContext): void;
205
211
  handlePointerMove(state: PointerState, ctx: ToolContext): void;
@@ -280,7 +286,6 @@ declare class InputHandler {
280
286
  destroy(): void;
281
287
  private bind;
282
288
  private onWheel;
283
- private isInteractiveHtmlContent;
284
289
  private onPointerDown;
285
290
  private onPointerMove;
286
291
  private onPointerUp;
@@ -328,6 +333,7 @@ declare class Viewport {
328
333
  private needsRender;
329
334
  private domNodes;
330
335
  private htmlContent;
336
+ private interactingElementId;
331
337
  constructor(container: HTMLElement, options?: ViewportOptions);
332
338
  get ctx(): CanvasRenderingContext2D | null;
333
339
  requestRender(): void;
@@ -343,7 +349,7 @@ declare class Viewport {
343
349
  }, size?: {
344
350
  w: number;
345
351
  h: number;
346
- }): void;
352
+ }): string;
347
353
  addHtmlElement(dom: HTMLElement, position: {
348
354
  x: number;
349
355
  y: number;
@@ -356,6 +362,12 @@ declare class Viewport {
356
362
  private render;
357
363
  private startEditingNote;
358
364
  private onDblClick;
365
+ private hitTestWorld;
366
+ private startInteracting;
367
+ stopInteracting(): void;
368
+ private onInteractNodePointerDown;
369
+ private onInteractKeyDown;
370
+ private onInteractPointerDown;
359
371
  private onDragOver;
360
372
  private onDrop;
361
373
  private syncDomNode;
@@ -401,7 +413,7 @@ interface BaseDefaults {
401
413
  locked?: boolean;
402
414
  }
403
415
  interface StrokeInput extends BaseDefaults {
404
- points: Point[];
416
+ points: StrokePoint[];
405
417
  color?: string;
406
418
  width?: number;
407
419
  opacity?: number;
@@ -488,6 +500,7 @@ declare class HandTool implements Tool {
488
500
  interface PencilToolOptions {
489
501
  color?: string;
490
502
  width?: number;
503
+ smoothing?: number;
491
504
  }
492
505
  declare class PencilTool implements Tool {
493
506
  readonly name = "pencil";
@@ -495,6 +508,7 @@ declare class PencilTool implements Tool {
495
508
  private points;
496
509
  private color;
497
510
  private width;
511
+ private smoothing;
498
512
  constructor(options?: PencilToolOptions);
499
513
  onActivate(ctx: ToolContext): void;
500
514
  onDeactivate(ctx: ToolContext): void;
@@ -565,6 +579,7 @@ declare class ArrowTool implements Tool {
565
579
  private color;
566
580
  private width;
567
581
  constructor(options?: ArrowToolOptions);
582
+ setOptions(options: ArrowToolOptions): void;
568
583
  onPointerDown(state: PointerState, ctx: ToolContext): void;
569
584
  onPointerMove(state: PointerState, ctx: ToolContext): void;
570
585
  onPointerUp(_state: PointerState, ctx: ToolContext): void;
@@ -580,6 +595,7 @@ declare class NoteTool implements Tool {
580
595
  private backgroundColor;
581
596
  private size;
582
597
  constructor(options?: NoteToolOptions);
598
+ setOptions(options: NoteToolOptions): void;
583
599
  onPointerDown(_state: PointerState, _ctx: ToolContext): void;
584
600
  onPointerMove(_state: PointerState, _ctx: ToolContext): void;
585
601
  onPointerUp(state: PointerState, ctx: ToolContext): void;
@@ -599,6 +615,6 @@ declare class ImageTool implements Tool {
599
615
  onPointerUp(state: PointerState, ctx: ToolContext): void;
600
616
  }
601
617
 
602
- declare const VERSION = "0.1.2";
618
+ declare const VERSION = "0.2.2";
603
619
 
604
- export { AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Bounds, Camera, type CameraOptions, type CanvasElement, type CanvasState, type Command, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, HandTool, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputHandler, NoteEditor, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, RemoveElementCommand, SelectTool, type Size, type StrokeElement, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, VERSION, Viewport, type ViewportOptions, createArrow, createHtmlElement, createId, createImage, createNote, createStroke, exportState, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, isNearBezier, parseState };
620
+ export { AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Bounds, Camera, type CameraOptions, type CanvasElement, type CanvasState, type Command, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, HandTool, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputHandler, NoteEditor, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, RemoveElementCommand, SelectTool, type Size, type StrokeElement, type StrokePoint, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, VERSION, Viewport, type ViewportOptions, createArrow, createHtmlElement, createId, createImage, createNote, createStroke, exportState, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, isNearBezier, parseState };
package/dist/index.js CHANGED
@@ -89,6 +89,13 @@ function migrateElement(obj) {
89
89
  if (obj["type"] === "arrow" && typeof obj["bend"] !== "number") {
90
90
  obj["bend"] = 0;
91
91
  }
92
+ if (obj["type"] === "stroke" && Array.isArray(obj["points"])) {
93
+ for (const pt of obj["points"]) {
94
+ if (typeof pt["pressure"] !== "number") {
95
+ pt["pressure"] = 0.5;
96
+ }
97
+ }
98
+ }
92
99
  }
93
100
 
94
101
  // src/core/auto-save.ts
@@ -344,24 +351,8 @@ var InputHandler = class {
344
351
  y: e.clientY - rect.top
345
352
  });
346
353
  };
347
- isInteractiveHtmlContent(e) {
348
- const target = e.target;
349
- if (!target) return false;
350
- const node = target.closest("[data-element-id]");
351
- if (!node) return false;
352
- const elementId = node.dataset["elementId"];
353
- if (!elementId) return false;
354
- const store = this.toolContext?.store;
355
- if (!store) return false;
356
- const element = store.getById(elementId);
357
- if (!element || element.type !== "html") return false;
358
- return true;
359
- }
360
354
  onPointerDown = (e) => {
361
355
  this.activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
362
- if (this.isInteractiveHtmlContent(e)) {
363
- return;
364
- }
365
356
  this.element.setPointerCapture?.(e.pointerId);
366
357
  if (this.activePointers.size === 2) {
367
358
  this.startPinch();
@@ -687,6 +678,79 @@ function isNearLine(point, a, b, threshold) {
687
678
  return Math.hypot(point.x - projX, point.y - projY) <= threshold;
688
679
  }
689
680
 
681
+ // src/elements/stroke-smoothing.ts
682
+ var MIN_PRESSURE_SCALE = 0.2;
683
+ function pressureToWidth(pressure, baseWidth) {
684
+ return baseWidth * (MIN_PRESSURE_SCALE + (1 - MIN_PRESSURE_SCALE) * pressure);
685
+ }
686
+ function simplifyPoints(points, tolerance) {
687
+ if (points.length <= 2) return points.slice();
688
+ return rdp(points, 0, points.length - 1, tolerance);
689
+ }
690
+ function rdp(points, start, end, tolerance) {
691
+ const first = points[start];
692
+ const last = points[end];
693
+ if (!first || !last) return [];
694
+ if (end - start <= 1) return [first, last];
695
+ let maxDist = 0;
696
+ let maxIndex = start;
697
+ for (let i = start + 1; i < end; i++) {
698
+ const pt = points[i];
699
+ if (!pt) continue;
700
+ const dist = perpendicularDistance(pt, first, last);
701
+ if (dist > maxDist) {
702
+ maxDist = dist;
703
+ maxIndex = i;
704
+ }
705
+ }
706
+ if (maxDist <= tolerance) return [first, last];
707
+ const left = rdp(points, start, maxIndex, tolerance);
708
+ const right = rdp(points, maxIndex, end, tolerance);
709
+ return left.concat(right.slice(1));
710
+ }
711
+ function perpendicularDistance(pt, lineStart, lineEnd) {
712
+ const dx = lineEnd.x - lineStart.x;
713
+ const dy = lineEnd.y - lineStart.y;
714
+ const lenSq = dx * dx + dy * dy;
715
+ if (lenSq === 0) {
716
+ const ex = pt.x - lineStart.x;
717
+ const ey = pt.y - lineStart.y;
718
+ return Math.sqrt(ex * ex + ey * ey);
719
+ }
720
+ const num = Math.abs(dy * pt.x - dx * pt.y + lineEnd.x * lineStart.y - lineEnd.y * lineStart.x);
721
+ return num / Math.sqrt(lenSq);
722
+ }
723
+ function smoothToSegments(points) {
724
+ if (points.length < 2) return [];
725
+ if (points.length === 2) {
726
+ const p0 = points[0];
727
+ const p1 = points[1];
728
+ if (!p0 || !p1) return [];
729
+ const mx = (p0.x + p1.x) / 2;
730
+ const my = (p0.y + p1.y) / 2;
731
+ return [{ start: p0, cp1: { x: mx, y: my }, cp2: { x: mx, y: my }, end: p1 }];
732
+ }
733
+ const segments = [];
734
+ const n = points.length;
735
+ for (let i = 0; i < n - 1; i++) {
736
+ const p0 = points[Math.max(0, i - 1)];
737
+ const p1 = points[i];
738
+ const p2 = points[i + 1];
739
+ const p3 = points[Math.min(n - 1, i + 2)];
740
+ if (!p0 || !p1 || !p2 || !p3) continue;
741
+ const cp1 = {
742
+ x: p1.x + (p2.x - p0.x) / 6,
743
+ y: p1.y + (p2.y - p0.y) / 6
744
+ };
745
+ const cp2 = {
746
+ x: p2.x - (p3.x - p1.x) / 6,
747
+ y: p2.y - (p3.y - p1.y) / 6
748
+ };
749
+ segments.push({ start: p1, cp1, cp2, end: p2 });
750
+ }
751
+ return segments;
752
+ }
753
+
690
754
  // src/elements/element-renderer.ts
691
755
  var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "image", "html"]);
692
756
  var ARROWHEAD_LENGTH = 12;
@@ -710,22 +774,18 @@ var ElementRenderer = class {
710
774
  ctx.save();
711
775
  ctx.translate(stroke.position.x, stroke.position.y);
712
776
  ctx.strokeStyle = stroke.color;
713
- ctx.lineWidth = stroke.width;
714
777
  ctx.lineCap = "round";
715
778
  ctx.lineJoin = "round";
716
779
  ctx.globalAlpha = stroke.opacity;
717
- ctx.beginPath();
718
- const first = stroke.points[0];
719
- if (first) {
720
- ctx.moveTo(first.x, first.y);
721
- }
722
- for (let i = 1; i < stroke.points.length; i++) {
723
- const pt = stroke.points[i];
724
- if (pt) {
725
- ctx.lineTo(pt.x, pt.y);
726
- }
780
+ const segments = smoothToSegments(stroke.points);
781
+ for (const seg of segments) {
782
+ const w = (pressureToWidth(seg.start.pressure, stroke.width) + pressureToWidth(seg.end.pressure, stroke.width)) / 2;
783
+ ctx.lineWidth = w;
784
+ ctx.beginPath();
785
+ ctx.moveTo(seg.start.x, seg.start.y);
786
+ ctx.bezierCurveTo(seg.cp1.x, seg.cp1.y, seg.cp2.x, seg.cp2.y, seg.end.x, seg.end.y);
787
+ ctx.stroke();
727
788
  }
728
- ctx.stroke();
729
789
  ctx.restore();
730
790
  }
731
791
  renderArrow(ctx, arrow) {
@@ -868,6 +928,9 @@ var ToolManager = class {
868
928
  register(tool) {
869
929
  this.tools.set(tool.name, tool);
870
930
  }
931
+ getTool(name) {
932
+ return this.tools.get(name);
933
+ }
871
934
  setTool(name, ctx) {
872
935
  const tool = this.tools.get(name);
873
936
  if (!tool) return;
@@ -1227,6 +1290,7 @@ var Viewport = class {
1227
1290
  needsRender = true;
1228
1291
  domNodes = /* @__PURE__ */ new Map();
1229
1292
  htmlContent = /* @__PURE__ */ new Map();
1293
+ interactingElementId = null;
1230
1294
  get ctx() {
1231
1295
  return this.canvasEl.getContext("2d");
1232
1296
  }
@@ -1272,6 +1336,7 @@ var Viewport = class {
1272
1336
  this.store.add(image);
1273
1337
  this.historyRecorder.commit();
1274
1338
  this.requestRender();
1339
+ return image.id;
1275
1340
  }
1276
1341
  addHtmlElement(dom, position, size = { w: 200, h: 150 }) {
1277
1342
  const el = createHtmlElement({ position, size });
@@ -1284,6 +1349,7 @@ var Viewport = class {
1284
1349
  }
1285
1350
  destroy() {
1286
1351
  cancelAnimationFrame(this.animFrameId);
1352
+ this.stopInteracting();
1287
1353
  this.noteEditor.destroy(this.store);
1288
1354
  this.historyRecorder.destroy();
1289
1355
  this.wrapper.removeEventListener("dblclick", this.onDblClick);
@@ -1341,11 +1407,74 @@ var Viewport = class {
1341
1407
  }
1342
1408
  onDblClick = (e) => {
1343
1409
  const el = document.elementFromPoint(e.clientX, e.clientY);
1344
- if (!el) return;
1345
- const nodeEl = el.closest("[data-element-id]");
1346
- if (!nodeEl) return;
1347
- const elementId = nodeEl.dataset["elementId"];
1348
- if (elementId) this.startEditingNote(elementId);
1410
+ const nodeEl = el?.closest("[data-element-id]");
1411
+ if (nodeEl) {
1412
+ const elementId = nodeEl.dataset["elementId"];
1413
+ if (elementId) {
1414
+ const element = this.store.getById(elementId);
1415
+ if (element?.type === "note") {
1416
+ this.startEditingNote(elementId);
1417
+ return;
1418
+ }
1419
+ }
1420
+ }
1421
+ const rect = this.wrapper.getBoundingClientRect();
1422
+ const screen = { x: e.clientX - rect.left, y: e.clientY - rect.top };
1423
+ const world = this.camera.screenToWorld(screen);
1424
+ const hit = this.hitTestWorld(world);
1425
+ if (hit?.type === "html") {
1426
+ this.startInteracting(hit.id);
1427
+ }
1428
+ };
1429
+ hitTestWorld(world) {
1430
+ const elements = this.store.getAll().reverse();
1431
+ for (const el of elements) {
1432
+ if (!("size" in el)) continue;
1433
+ const { x, y } = el.position;
1434
+ const { w, h } = el.size;
1435
+ if (world.x >= x && world.x <= x + w && world.y >= y && world.y <= y + h) {
1436
+ return el;
1437
+ }
1438
+ }
1439
+ return null;
1440
+ }
1441
+ startInteracting(id) {
1442
+ this.stopInteracting();
1443
+ const node = this.domNodes.get(id);
1444
+ if (!node) return;
1445
+ this.interactingElementId = id;
1446
+ node.style.pointerEvents = "auto";
1447
+ node.addEventListener("pointerdown", this.onInteractNodePointerDown);
1448
+ window.addEventListener("keydown", this.onInteractKeyDown);
1449
+ window.addEventListener("pointerdown", this.onInteractPointerDown);
1450
+ }
1451
+ stopInteracting() {
1452
+ if (!this.interactingElementId) return;
1453
+ const node = this.domNodes.get(this.interactingElementId);
1454
+ if (node) {
1455
+ node.style.pointerEvents = "none";
1456
+ node.removeEventListener("pointerdown", this.onInteractNodePointerDown);
1457
+ }
1458
+ this.interactingElementId = null;
1459
+ window.removeEventListener("keydown", this.onInteractKeyDown);
1460
+ window.removeEventListener("pointerdown", this.onInteractPointerDown);
1461
+ }
1462
+ onInteractNodePointerDown = (e) => {
1463
+ e.stopPropagation();
1464
+ };
1465
+ onInteractKeyDown = (e) => {
1466
+ if (e.key === "Escape") {
1467
+ this.stopInteracting();
1468
+ }
1469
+ };
1470
+ onInteractPointerDown = (e) => {
1471
+ if (!this.interactingElementId) return;
1472
+ const target = e.target;
1473
+ if (!target) return;
1474
+ const node = this.domNodes.get(this.interactingElementId);
1475
+ if (node && !node.contains(target)) {
1476
+ this.stopInteracting();
1477
+ }
1349
1478
  };
1350
1479
  onDragOver = (e) => {
1351
1480
  e.preventDefault();
@@ -1443,7 +1572,8 @@ var Viewport = class {
1443
1572
  if (content) {
1444
1573
  node.dataset["initialized"] = "true";
1445
1574
  Object.assign(node.style, {
1446
- overflow: "hidden"
1575
+ overflow: "hidden",
1576
+ pointerEvents: "none"
1447
1577
  });
1448
1578
  node.appendChild(content);
1449
1579
  }
@@ -1546,15 +1676,19 @@ var HandTool = class {
1546
1676
 
1547
1677
  // src/tools/pencil-tool.ts
1548
1678
  var MIN_POINTS_FOR_STROKE = 2;
1679
+ var DEFAULT_SMOOTHING = 1.5;
1680
+ var DEFAULT_PRESSURE = 0.5;
1549
1681
  var PencilTool = class {
1550
1682
  name = "pencil";
1551
1683
  drawing = false;
1552
1684
  points = [];
1553
1685
  color;
1554
1686
  width;
1687
+ smoothing;
1555
1688
  constructor(options = {}) {
1556
1689
  this.color = options.color ?? "#000000";
1557
1690
  this.width = options.width ?? 2;
1691
+ this.smoothing = options.smoothing ?? DEFAULT_SMOOTHING;
1558
1692
  }
1559
1693
  onActivate(ctx) {
1560
1694
  ctx.setCursor?.("crosshair");
@@ -1565,16 +1699,19 @@ var PencilTool = class {
1565
1699
  setOptions(options) {
1566
1700
  if (options.color !== void 0) this.color = options.color;
1567
1701
  if (options.width !== void 0) this.width = options.width;
1702
+ if (options.smoothing !== void 0) this.smoothing = options.smoothing;
1568
1703
  }
1569
1704
  onPointerDown(state, ctx) {
1570
1705
  this.drawing = true;
1571
1706
  const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
1572
- this.points = [world];
1707
+ const pressure = state.pressure === 0 ? DEFAULT_PRESSURE : state.pressure;
1708
+ this.points = [{ x: world.x, y: world.y, pressure }];
1573
1709
  }
1574
1710
  onPointerMove(state, ctx) {
1575
1711
  if (!this.drawing) return;
1576
1712
  const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
1577
- this.points.push(world);
1713
+ const pressure = state.pressure === 0 ? DEFAULT_PRESSURE : state.pressure;
1714
+ this.points.push({ x: world.x, y: world.y, pressure });
1578
1715
  ctx.requestRender();
1579
1716
  }
1580
1717
  onPointerUp(_state, ctx) {
@@ -1584,8 +1721,9 @@ var PencilTool = class {
1584
1721
  this.points = [];
1585
1722
  return;
1586
1723
  }
1724
+ const simplified = simplifyPoints(this.points, this.smoothing);
1587
1725
  const stroke = createStroke({
1588
- points: this.points,
1726
+ points: simplified,
1589
1727
  color: this.color,
1590
1728
  width: this.width
1591
1729
  });
@@ -1597,19 +1735,18 @@ var PencilTool = class {
1597
1735
  if (!this.drawing || this.points.length < 2) return;
1598
1736
  ctx.save();
1599
1737
  ctx.strokeStyle = this.color;
1600
- ctx.lineWidth = this.width;
1601
1738
  ctx.lineCap = "round";
1602
1739
  ctx.lineJoin = "round";
1603
1740
  ctx.globalAlpha = 0.8;
1604
- ctx.beginPath();
1605
- const first = this.points[0];
1606
- if (!first) return;
1607
- ctx.moveTo(first.x, first.y);
1608
- for (let i = 1; i < this.points.length; i++) {
1609
- const p = this.points[i];
1610
- if (p) ctx.lineTo(p.x, p.y);
1741
+ const segments = smoothToSegments(this.points);
1742
+ for (const seg of segments) {
1743
+ const w = (pressureToWidth(seg.start.pressure, this.width) + pressureToWidth(seg.end.pressure, this.width)) / 2;
1744
+ ctx.lineWidth = w;
1745
+ ctx.beginPath();
1746
+ ctx.moveTo(seg.start.x, seg.start.y);
1747
+ ctx.bezierCurveTo(seg.cp1.x, seg.cp1.y, seg.cp2.x, seg.cp2.y, seg.end.x, seg.end.y);
1748
+ ctx.stroke();
1611
1749
  }
1612
- ctx.stroke();
1613
1750
  ctx.restore();
1614
1751
  }
1615
1752
  };
@@ -1897,7 +2034,7 @@ var SelectTool = class {
1897
2034
  handleResize(world, ctx) {
1898
2035
  if (this.mode.type !== "resizing") return;
1899
2036
  const el = ctx.store.getById(this.mode.elementId);
1900
- if (!el || !("size" in el)) return;
2037
+ if (!el || !("size" in el) || el.locked) return;
1901
2038
  const { handle } = this.mode;
1902
2039
  const dx = world.x - this.lastWorld.x;
1903
2040
  const dy = world.y - this.lastWorld.y;
@@ -2104,6 +2241,10 @@ var ArrowTool = class {
2104
2241
  this.color = options.color ?? "#000000";
2105
2242
  this.width = options.width ?? 2;
2106
2243
  }
2244
+ setOptions(options) {
2245
+ if (options.color !== void 0) this.color = options.color;
2246
+ if (options.width !== void 0) this.width = options.width;
2247
+ }
2107
2248
  onPointerDown(state, ctx) {
2108
2249
  this.drawing = true;
2109
2250
  this.start = ctx.camera.screenToWorld({ x: state.x, y: state.y });
@@ -2168,6 +2309,10 @@ var NoteTool = class {
2168
2309
  this.backgroundColor = options.backgroundColor ?? "#ffeb3b";
2169
2310
  this.size = options.size ?? { w: 200, h: 100 };
2170
2311
  }
2312
+ setOptions(options) {
2313
+ if (options.backgroundColor !== void 0) this.backgroundColor = options.backgroundColor;
2314
+ if (options.size !== void 0) this.size = options.size;
2315
+ }
2171
2316
  onPointerDown(_state, _ctx) {
2172
2317
  }
2173
2318
  onPointerMove(_state, _ctx) {
@@ -2217,7 +2362,7 @@ var ImageTool = class {
2217
2362
  };
2218
2363
 
2219
2364
  // src/index.ts
2220
- var VERSION = "0.1.2";
2365
+ var VERSION = "0.2.2";
2221
2366
  export {
2222
2367
  AddElementCommand,
2223
2368
  ArrowTool,