@fieldnotes/core 0.2.0 → 0.2.1

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,11 @@ 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 onInteractKeyDown;
369
+ private onInteractPointerDown;
359
370
  private onDragOver;
360
371
  private onDrop;
361
372
  private syncDomNode;
@@ -401,7 +412,7 @@ interface BaseDefaults {
401
412
  locked?: boolean;
402
413
  }
403
414
  interface StrokeInput extends BaseDefaults {
404
- points: Point[];
415
+ points: StrokePoint[];
405
416
  color?: string;
406
417
  width?: number;
407
418
  opacity?: number;
@@ -488,6 +499,7 @@ declare class HandTool implements Tool {
488
499
  interface PencilToolOptions {
489
500
  color?: string;
490
501
  width?: number;
502
+ smoothing?: number;
491
503
  }
492
504
  declare class PencilTool implements Tool {
493
505
  readonly name = "pencil";
@@ -495,6 +507,7 @@ declare class PencilTool implements Tool {
495
507
  private points;
496
508
  private color;
497
509
  private width;
510
+ private smoothing;
498
511
  constructor(options?: PencilToolOptions);
499
512
  onActivate(ctx: ToolContext): void;
500
513
  onDeactivate(ctx: ToolContext): void;
@@ -565,6 +578,7 @@ declare class ArrowTool implements Tool {
565
578
  private color;
566
579
  private width;
567
580
  constructor(options?: ArrowToolOptions);
581
+ setOptions(options: ArrowToolOptions): void;
568
582
  onPointerDown(state: PointerState, ctx: ToolContext): void;
569
583
  onPointerMove(state: PointerState, ctx: ToolContext): void;
570
584
  onPointerUp(_state: PointerState, ctx: ToolContext): void;
@@ -580,6 +594,7 @@ declare class NoteTool implements Tool {
580
594
  private backgroundColor;
581
595
  private size;
582
596
  constructor(options?: NoteToolOptions);
597
+ setOptions(options: NoteToolOptions): void;
583
598
  onPointerDown(_state: PointerState, _ctx: ToolContext): void;
584
599
  onPointerMove(_state: PointerState, _ctx: ToolContext): void;
585
600
  onPointerUp(state: PointerState, ctx: ToolContext): void;
@@ -599,6 +614,6 @@ declare class ImageTool implements Tool {
599
614
  onPointerUp(state: PointerState, ctx: ToolContext): void;
600
615
  }
601
616
 
602
- declare const VERSION = "0.1.2";
617
+ declare const VERSION = "0.2.1";
603
618
 
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 };
619
+ 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,11 @@ 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 onInteractKeyDown;
369
+ private onInteractPointerDown;
359
370
  private onDragOver;
360
371
  private onDrop;
361
372
  private syncDomNode;
@@ -401,7 +412,7 @@ interface BaseDefaults {
401
412
  locked?: boolean;
402
413
  }
403
414
  interface StrokeInput extends BaseDefaults {
404
- points: Point[];
415
+ points: StrokePoint[];
405
416
  color?: string;
406
417
  width?: number;
407
418
  opacity?: number;
@@ -488,6 +499,7 @@ declare class HandTool implements Tool {
488
499
  interface PencilToolOptions {
489
500
  color?: string;
490
501
  width?: number;
502
+ smoothing?: number;
491
503
  }
492
504
  declare class PencilTool implements Tool {
493
505
  readonly name = "pencil";
@@ -495,6 +507,7 @@ declare class PencilTool implements Tool {
495
507
  private points;
496
508
  private color;
497
509
  private width;
510
+ private smoothing;
498
511
  constructor(options?: PencilToolOptions);
499
512
  onActivate(ctx: ToolContext): void;
500
513
  onDeactivate(ctx: ToolContext): void;
@@ -565,6 +578,7 @@ declare class ArrowTool implements Tool {
565
578
  private color;
566
579
  private width;
567
580
  constructor(options?: ArrowToolOptions);
581
+ setOptions(options: ArrowToolOptions): void;
568
582
  onPointerDown(state: PointerState, ctx: ToolContext): void;
569
583
  onPointerMove(state: PointerState, ctx: ToolContext): void;
570
584
  onPointerUp(_state: PointerState, ctx: ToolContext): void;
@@ -580,6 +594,7 @@ declare class NoteTool implements Tool {
580
594
  private backgroundColor;
581
595
  private size;
582
596
  constructor(options?: NoteToolOptions);
597
+ setOptions(options: NoteToolOptions): void;
583
598
  onPointerDown(_state: PointerState, _ctx: ToolContext): void;
584
599
  onPointerMove(_state: PointerState, _ctx: ToolContext): void;
585
600
  onPointerUp(state: PointerState, ctx: ToolContext): void;
@@ -599,6 +614,6 @@ declare class ImageTool implements Tool {
599
614
  onPointerUp(state: PointerState, ctx: ToolContext): void;
600
615
  }
601
616
 
602
- declare const VERSION = "0.1.2";
617
+ declare const VERSION = "0.2.1";
603
618
 
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 };
619
+ 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,69 @@ 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
+ window.addEventListener("keydown", this.onInteractKeyDown);
1448
+ window.addEventListener("pointerdown", this.onInteractPointerDown);
1449
+ }
1450
+ stopInteracting() {
1451
+ if (!this.interactingElementId) return;
1452
+ const node = this.domNodes.get(this.interactingElementId);
1453
+ if (node) {
1454
+ node.style.pointerEvents = "none";
1455
+ }
1456
+ this.interactingElementId = null;
1457
+ window.removeEventListener("keydown", this.onInteractKeyDown);
1458
+ window.removeEventListener("pointerdown", this.onInteractPointerDown);
1459
+ }
1460
+ onInteractKeyDown = (e) => {
1461
+ if (e.key === "Escape") {
1462
+ this.stopInteracting();
1463
+ }
1464
+ };
1465
+ onInteractPointerDown = (e) => {
1466
+ if (!this.interactingElementId) return;
1467
+ const target = e.target;
1468
+ if (!target) return;
1469
+ const node = this.domNodes.get(this.interactingElementId);
1470
+ if (node && !node.contains(target)) {
1471
+ this.stopInteracting();
1472
+ }
1349
1473
  };
1350
1474
  onDragOver = (e) => {
1351
1475
  e.preventDefault();
@@ -1443,7 +1567,8 @@ var Viewport = class {
1443
1567
  if (content) {
1444
1568
  node.dataset["initialized"] = "true";
1445
1569
  Object.assign(node.style, {
1446
- overflow: "hidden"
1570
+ overflow: "hidden",
1571
+ pointerEvents: "none"
1447
1572
  });
1448
1573
  node.appendChild(content);
1449
1574
  }
@@ -1546,15 +1671,19 @@ var HandTool = class {
1546
1671
 
1547
1672
  // src/tools/pencil-tool.ts
1548
1673
  var MIN_POINTS_FOR_STROKE = 2;
1674
+ var DEFAULT_SMOOTHING = 1.5;
1675
+ var DEFAULT_PRESSURE = 0.5;
1549
1676
  var PencilTool = class {
1550
1677
  name = "pencil";
1551
1678
  drawing = false;
1552
1679
  points = [];
1553
1680
  color;
1554
1681
  width;
1682
+ smoothing;
1555
1683
  constructor(options = {}) {
1556
1684
  this.color = options.color ?? "#000000";
1557
1685
  this.width = options.width ?? 2;
1686
+ this.smoothing = options.smoothing ?? DEFAULT_SMOOTHING;
1558
1687
  }
1559
1688
  onActivate(ctx) {
1560
1689
  ctx.setCursor?.("crosshair");
@@ -1565,16 +1694,19 @@ var PencilTool = class {
1565
1694
  setOptions(options) {
1566
1695
  if (options.color !== void 0) this.color = options.color;
1567
1696
  if (options.width !== void 0) this.width = options.width;
1697
+ if (options.smoothing !== void 0) this.smoothing = options.smoothing;
1568
1698
  }
1569
1699
  onPointerDown(state, ctx) {
1570
1700
  this.drawing = true;
1571
1701
  const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
1572
- this.points = [world];
1702
+ const pressure = state.pressure === 0 ? DEFAULT_PRESSURE : state.pressure;
1703
+ this.points = [{ x: world.x, y: world.y, pressure }];
1573
1704
  }
1574
1705
  onPointerMove(state, ctx) {
1575
1706
  if (!this.drawing) return;
1576
1707
  const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
1577
- this.points.push(world);
1708
+ const pressure = state.pressure === 0 ? DEFAULT_PRESSURE : state.pressure;
1709
+ this.points.push({ x: world.x, y: world.y, pressure });
1578
1710
  ctx.requestRender();
1579
1711
  }
1580
1712
  onPointerUp(_state, ctx) {
@@ -1584,8 +1716,9 @@ var PencilTool = class {
1584
1716
  this.points = [];
1585
1717
  return;
1586
1718
  }
1719
+ const simplified = simplifyPoints(this.points, this.smoothing);
1587
1720
  const stroke = createStroke({
1588
- points: this.points,
1721
+ points: simplified,
1589
1722
  color: this.color,
1590
1723
  width: this.width
1591
1724
  });
@@ -1597,19 +1730,18 @@ var PencilTool = class {
1597
1730
  if (!this.drawing || this.points.length < 2) return;
1598
1731
  ctx.save();
1599
1732
  ctx.strokeStyle = this.color;
1600
- ctx.lineWidth = this.width;
1601
1733
  ctx.lineCap = "round";
1602
1734
  ctx.lineJoin = "round";
1603
1735
  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);
1736
+ const segments = smoothToSegments(this.points);
1737
+ for (const seg of segments) {
1738
+ const w = (pressureToWidth(seg.start.pressure, this.width) + pressureToWidth(seg.end.pressure, this.width)) / 2;
1739
+ ctx.lineWidth = w;
1740
+ ctx.beginPath();
1741
+ ctx.moveTo(seg.start.x, seg.start.y);
1742
+ ctx.bezierCurveTo(seg.cp1.x, seg.cp1.y, seg.cp2.x, seg.cp2.y, seg.end.x, seg.end.y);
1743
+ ctx.stroke();
1611
1744
  }
1612
- ctx.stroke();
1613
1745
  ctx.restore();
1614
1746
  }
1615
1747
  };
@@ -1897,7 +2029,7 @@ var SelectTool = class {
1897
2029
  handleResize(world, ctx) {
1898
2030
  if (this.mode.type !== "resizing") return;
1899
2031
  const el = ctx.store.getById(this.mode.elementId);
1900
- if (!el || !("size" in el)) return;
2032
+ if (!el || !("size" in el) || el.locked) return;
1901
2033
  const { handle } = this.mode;
1902
2034
  const dx = world.x - this.lastWorld.x;
1903
2035
  const dy = world.y - this.lastWorld.y;
@@ -2104,6 +2236,10 @@ var ArrowTool = class {
2104
2236
  this.color = options.color ?? "#000000";
2105
2237
  this.width = options.width ?? 2;
2106
2238
  }
2239
+ setOptions(options) {
2240
+ if (options.color !== void 0) this.color = options.color;
2241
+ if (options.width !== void 0) this.width = options.width;
2242
+ }
2107
2243
  onPointerDown(state, ctx) {
2108
2244
  this.drawing = true;
2109
2245
  this.start = ctx.camera.screenToWorld({ x: state.x, y: state.y });
@@ -2168,6 +2304,10 @@ var NoteTool = class {
2168
2304
  this.backgroundColor = options.backgroundColor ?? "#ffeb3b";
2169
2305
  this.size = options.size ?? { w: 200, h: 100 };
2170
2306
  }
2307
+ setOptions(options) {
2308
+ if (options.backgroundColor !== void 0) this.backgroundColor = options.backgroundColor;
2309
+ if (options.size !== void 0) this.size = options.size;
2310
+ }
2171
2311
  onPointerDown(_state, _ctx) {
2172
2312
  }
2173
2313
  onPointerMove(_state, _ctx) {
@@ -2217,7 +2357,7 @@ var ImageTool = class {
2217
2357
  };
2218
2358
 
2219
2359
  // src/index.ts
2220
- var VERSION = "0.1.2";
2360
+ var VERSION = "0.2.1";
2221
2361
  export {
2222
2362
  AddElementCommand,
2223
2363
  ArrowTool,