@fieldnotes/core 0.23.0 → 0.25.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.js CHANGED
@@ -1,179 +1,34 @@
1
- // src/core/event-bus.ts
2
- var EventBus = class {
3
- listeners = /* @__PURE__ */ new Map();
4
- on(event, listener) {
5
- const existing = this.listeners.get(event);
6
- if (existing) {
7
- existing.add(listener);
8
- } else {
9
- const set = /* @__PURE__ */ new Set([listener]);
10
- this.listeners.set(event, set);
11
- }
12
- return () => this.off(event, listener);
13
- }
14
- off(event, listener) {
15
- this.listeners.get(event)?.delete(listener);
16
- }
17
- emit(event, data) {
18
- this.listeners.get(event)?.forEach((listener) => {
19
- try {
20
- listener(data);
21
- } catch (err) {
22
- console.error(`[fieldnotes] listener error for "${String(event)}"`, err);
23
- }
24
- });
25
- }
26
- clear() {
27
- this.listeners.clear();
28
- }
29
- };
30
-
31
- // src/core/quadtree.ts
32
- var MAX_ITEMS = 8;
33
- var MAX_DEPTH = 8;
34
- function intersects(a, b) {
35
- return a.x <= b.x + b.w && a.x + a.w >= b.x && a.y <= b.y + b.h && a.y + a.h >= b.y;
1
+ // src/core/snap.ts
2
+ function snapPoint(point, gridSize) {
3
+ return {
4
+ x: Math.round(point.x / gridSize) * gridSize || 0,
5
+ y: Math.round(point.y / gridSize) * gridSize || 0
6
+ };
36
7
  }
37
- var QuadNode = class _QuadNode {
38
- constructor(bounds, depth) {
39
- this.bounds = bounds;
40
- this.depth = depth;
41
- }
42
- items = [];
43
- children = null;
44
- insert(entry) {
45
- if (this.children) {
46
- const idx = this.getChildIndex(entry.bounds);
47
- if (idx !== -1) {
48
- const child = this.children[idx];
49
- if (child) child.insert(entry);
50
- return;
51
- }
52
- this.items.push(entry);
53
- return;
54
- }
55
- this.items.push(entry);
56
- if (this.items.length > MAX_ITEMS && this.depth < MAX_DEPTH) {
57
- this.split();
58
- }
59
- }
60
- remove(id) {
61
- const idx = this.items.findIndex((e) => e.id === id);
62
- if (idx !== -1) {
63
- this.items.splice(idx, 1);
64
- return true;
65
- }
66
- if (this.children) {
67
- for (const child of this.children) {
68
- if (child.remove(id)) {
69
- this.collapseIfEmpty();
70
- return true;
71
- }
72
- }
73
- }
74
- return false;
75
- }
76
- query(rect, result) {
77
- if (!intersects(this.bounds, rect)) return;
78
- for (const item of this.items) {
79
- if (intersects(item.bounds, rect)) {
80
- result.push(item.id);
81
- }
82
- }
83
- if (this.children) {
84
- for (const child of this.children) {
85
- child.query(rect, result);
86
- }
87
- }
88
- }
89
- getChildIndex(itemBounds) {
90
- const midX = this.bounds.x + this.bounds.w / 2;
91
- const midY = this.bounds.y + this.bounds.h / 2;
92
- const left = itemBounds.x >= this.bounds.x && itemBounds.x + itemBounds.w <= midX;
93
- const right = itemBounds.x >= midX && itemBounds.x + itemBounds.w <= this.bounds.x + this.bounds.w;
94
- const top = itemBounds.y >= this.bounds.y && itemBounds.y + itemBounds.h <= midY;
95
- const bottom = itemBounds.y >= midY && itemBounds.y + itemBounds.h <= this.bounds.y + this.bounds.h;
96
- if (left && top) return 0;
97
- if (right && top) return 1;
98
- if (left && bottom) return 2;
99
- if (right && bottom) return 3;
100
- return -1;
101
- }
102
- split() {
103
- const { x, y, w, h } = this.bounds;
104
- const halfW = w / 2;
105
- const halfH = h / 2;
106
- const d = this.depth + 1;
107
- this.children = [
108
- new _QuadNode({ x, y, w: halfW, h: halfH }, d),
109
- new _QuadNode({ x: x + halfW, y, w: halfW, h: halfH }, d),
110
- new _QuadNode({ x, y: y + halfH, w: halfW, h: halfH }, d),
111
- new _QuadNode({ x: x + halfW, y: y + halfH, w: halfW, h: halfH }, d)
112
- ];
113
- const remaining = [];
114
- for (const item of this.items) {
115
- const idx = this.getChildIndex(item.bounds);
116
- if (idx !== -1) {
117
- const target = this.children[idx];
118
- if (target) target.insert(item);
119
- } else {
120
- remaining.push(item);
121
- }
122
- }
123
- this.items = remaining;
124
- }
125
- collapseIfEmpty() {
126
- if (!this.children) return;
127
- let totalItems = this.items.length;
128
- for (const child of this.children) {
129
- if (child.children) return;
130
- totalItems += child.items.length;
131
- }
132
- if (totalItems <= MAX_ITEMS) {
133
- for (const child of this.children) {
134
- this.items.push(...child.items);
135
- }
136
- this.children = null;
137
- }
138
- }
139
- };
140
- var Quadtree = class {
141
- root;
142
- _size = 0;
143
- worldBounds;
144
- constructor(worldBounds) {
145
- this.worldBounds = worldBounds;
146
- this.root = new QuadNode(worldBounds, 0);
147
- }
148
- get size() {
149
- return this._size;
150
- }
151
- insert(id, bounds) {
152
- this.root.insert({ id, bounds });
153
- this._size++;
154
- }
155
- remove(id) {
156
- if (this.root.remove(id)) {
157
- this._size--;
158
- }
159
- }
160
- update(id, newBounds) {
161
- this.remove(id);
162
- this.insert(id, newBounds);
163
- }
164
- query(rect) {
165
- const result = [];
166
- this.root.query(rect, result);
167
- return result;
168
- }
169
- queryPoint(point) {
170
- return this.query({ x: point.x, y: point.y, w: 0, h: 0 });
8
+ function snapToHexCenter(point, cellSize, orientation) {
9
+ if (orientation === "pointy") {
10
+ const hexW = Math.sqrt(3) * cellSize;
11
+ const rowH = 1.5 * cellSize;
12
+ const row = Math.round(point.y / rowH);
13
+ const offsetX = row % 2 !== 0 ? hexW / 2 : 0;
14
+ const col = Math.round((point.x - offsetX) / hexW);
15
+ return { x: col * hexW + offsetX || 0, y: row * rowH || 0 };
16
+ } else {
17
+ const hexH = Math.sqrt(3) * cellSize;
18
+ const colW = 1.5 * cellSize;
19
+ const col = Math.round(point.x / colW);
20
+ const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
21
+ const row = Math.round((point.y - offsetY) / hexH);
22
+ return { x: col * colW || 0, y: row * hexH + offsetY || 0 };
171
23
  }
172
- clear() {
173
- this.root = new QuadNode(this.worldBounds, 0);
174
- this._size = 0;
24
+ }
25
+ function smartSnap(point, ctx) {
26
+ if (!ctx.snapToGrid || !ctx.gridSize) return point;
27
+ if (ctx.gridType === "hex" && ctx.hexOrientation) {
28
+ return snapToHexCenter(point, ctx.gridSize, ctx.hexOrientation);
175
29
  }
176
- };
30
+ return snapPoint(point, ctx.gridSize);
31
+ }
177
32
 
178
33
  // src/elements/note-sanitizer.ts
179
34
  var BOLD_TAGS = /* @__PURE__ */ new Set(["b", "strong"]);
@@ -430,38 +285,6 @@ function migrateElement(obj) {
430
285
  }
431
286
  }
432
287
 
433
- // src/core/snap.ts
434
- function snapPoint(point, gridSize) {
435
- return {
436
- x: Math.round(point.x / gridSize) * gridSize || 0,
437
- y: Math.round(point.y / gridSize) * gridSize || 0
438
- };
439
- }
440
- function snapToHexCenter(point, cellSize, orientation) {
441
- if (orientation === "pointy") {
442
- const hexW = Math.sqrt(3) * cellSize;
443
- const rowH = 1.5 * cellSize;
444
- const row = Math.round(point.y / rowH);
445
- const offsetX = row % 2 !== 0 ? hexW / 2 : 0;
446
- const col = Math.round((point.x - offsetX) / hexW);
447
- return { x: col * hexW + offsetX || 0, y: row * rowH || 0 };
448
- } else {
449
- const hexH = Math.sqrt(3) * cellSize;
450
- const colW = 1.5 * cellSize;
451
- const col = Math.round(point.x / colW);
452
- const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
453
- const row = Math.round((point.y - offsetY) / hexH);
454
- return { x: col * colW || 0, y: row * hexH + offsetY || 0 };
455
- }
456
- }
457
- function smartSnap(point, ctx) {
458
- if (!ctx.snapToGrid || !ctx.gridSize) return point;
459
- if (ctx.gridType === "hex" && ctx.hexOrientation) {
460
- return snapToHexCenter(point, ctx.gridSize, ctx.hexOrientation);
461
- }
462
- return snapPoint(point, ctx.gridSize);
463
- }
464
-
465
288
  // src/core/auto-save.ts
466
289
  var DEFAULT_KEY = "fieldnotes-autosave";
467
290
  var DEFAULT_DEBOUNCE_MS = 1e3;
@@ -625,143 +448,6 @@ var Camera = class {
625
448
  }
626
449
  };
627
450
 
628
- // src/canvas/background.ts
629
- var MIN_PATTERN_SPACING = 16;
630
- var DEFAULTS = {
631
- pattern: "dots",
632
- spacing: 24,
633
- color: "#d0d0d0",
634
- dotRadius: 1,
635
- lineWidth: 0.5
636
- };
637
- var Background = class {
638
- pattern;
639
- spacing;
640
- color;
641
- dotRadius;
642
- lineWidth;
643
- cachedCanvas = null;
644
- cachedCtx = null;
645
- lastZoom = -1;
646
- lastOffsetX = -Infinity;
647
- lastOffsetY = -Infinity;
648
- lastWidth = 0;
649
- lastHeight = 0;
650
- constructor(options = {}) {
651
- this.pattern = options.pattern ?? DEFAULTS.pattern;
652
- this.spacing = options.spacing ?? DEFAULTS.spacing;
653
- this.color = options.color ?? DEFAULTS.color;
654
- this.dotRadius = options.dotRadius ?? DEFAULTS.dotRadius;
655
- this.lineWidth = options.lineWidth ?? DEFAULTS.lineWidth;
656
- }
657
- render(ctx, camera) {
658
- const { width, height } = ctx.canvas;
659
- const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
660
- const cssWidth = width / dpr;
661
- const cssHeight = height / dpr;
662
- ctx.save();
663
- ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
664
- ctx.clearRect(0, 0, cssWidth, cssHeight);
665
- if (this.pattern === "none") {
666
- ctx.restore();
667
- return;
668
- }
669
- const spacing = this.adaptSpacing(this.spacing, camera.zoom);
670
- const keyZoom = camera.zoom;
671
- const keyX = Math.floor(camera.position.x % spacing);
672
- const keyY = Math.floor(camera.position.y % spacing);
673
- if (this.cachedCanvas !== null && keyZoom === this.lastZoom && keyX === this.lastOffsetX && keyY === this.lastOffsetY && cssWidth === this.lastWidth && cssHeight === this.lastHeight) {
674
- ctx.drawImage(this.cachedCanvas, 0, 0);
675
- ctx.restore();
676
- return;
677
- }
678
- this.ensureCachedCanvas(cssWidth, cssHeight, dpr);
679
- if (this.cachedCtx === null) {
680
- if (this.pattern === "dots") {
681
- this.renderDots(ctx, camera, cssWidth, cssHeight);
682
- } else if (this.pattern === "grid") {
683
- this.renderGrid(ctx, camera, cssWidth, cssHeight);
684
- }
685
- ctx.restore();
686
- return;
687
- }
688
- const offCtx = this.cachedCtx;
689
- offCtx.clearRect(0, 0, cssWidth, cssHeight);
690
- if (this.pattern === "dots") {
691
- this.renderDots(offCtx, camera, cssWidth, cssHeight);
692
- } else if (this.pattern === "grid") {
693
- this.renderGrid(offCtx, camera, cssWidth, cssHeight);
694
- }
695
- this.lastZoom = keyZoom;
696
- this.lastOffsetX = keyX;
697
- this.lastOffsetY = keyY;
698
- this.lastWidth = cssWidth;
699
- this.lastHeight = cssHeight;
700
- ctx.drawImage(this.cachedCanvas, 0, 0);
701
- ctx.restore();
702
- }
703
- ensureCachedCanvas(cssWidth, cssHeight, dpr) {
704
- if (this.cachedCanvas !== null && this.lastWidth === cssWidth && this.lastHeight === cssHeight) {
705
- return;
706
- }
707
- const physWidth = Math.round(cssWidth * dpr);
708
- const physHeight = Math.round(cssHeight * dpr);
709
- if (typeof OffscreenCanvas !== "undefined") {
710
- this.cachedCanvas = new OffscreenCanvas(physWidth, physHeight);
711
- } else if (typeof document !== "undefined") {
712
- const el = document.createElement("canvas");
713
- el.width = physWidth;
714
- el.height = physHeight;
715
- this.cachedCanvas = el;
716
- } else {
717
- this.cachedCanvas = null;
718
- this.cachedCtx = null;
719
- return;
720
- }
721
- const offCtx = this.cachedCanvas.getContext("2d");
722
- if (offCtx !== null) {
723
- offCtx.scale(dpr, dpr);
724
- }
725
- this.cachedCtx = offCtx;
726
- this.lastZoom = -1;
727
- }
728
- adaptSpacing(baseSpacing, zoom) {
729
- let spacing = baseSpacing * zoom;
730
- while (spacing < MIN_PATTERN_SPACING) {
731
- spacing *= 2;
732
- }
733
- return spacing;
734
- }
735
- renderDots(ctx, camera, width, height) {
736
- const spacing = this.adaptSpacing(this.spacing, camera.zoom);
737
- const offsetX = camera.position.x % spacing;
738
- const offsetY = camera.position.y % spacing;
739
- const radius = this.dotRadius * Math.min(camera.zoom, 2);
740
- ctx.fillStyle = this.color;
741
- ctx.beginPath();
742
- for (let x = offsetX; x < width; x += spacing) {
743
- for (let y = offsetY; y < height; y += spacing) {
744
- ctx.moveTo(x + radius, y);
745
- ctx.arc(x, y, radius, 0, Math.PI * 2);
746
- }
747
- }
748
- ctx.fill();
749
- }
750
- renderGrid(ctx, camera, width, height) {
751
- const spacing = this.adaptSpacing(this.spacing, camera.zoom);
752
- const offsetX = camera.position.x % spacing;
753
- const offsetY = camera.position.y % spacing;
754
- const lineW = this.lineWidth * Math.min(camera.zoom, 2);
755
- ctx.fillStyle = this.color;
756
- for (let x = offsetX; x < width; x += spacing) {
757
- ctx.fillRect(x, 0, lineW, height);
758
- }
759
- for (let y = offsetY; y < height; y += spacing) {
760
- ctx.fillRect(0, y, width, lineW);
761
- }
762
- }
763
- };
764
-
765
451
  // src/canvas/input-filter.ts
766
452
  var InputFilter = class _InputFilter {
767
453
  activePenId = null;
@@ -923,16 +609,17 @@ var KeyboardActions = class {
923
609
  this.pasteCount = 0;
924
610
  }
925
611
  paste() {
612
+ if (this.deps.isToolActive()) return;
926
613
  this.flushPendingNudge();
927
- if (this.clipboard.length === 0 || this.deps.isToolActive()) return;
614
+ if (this.clipboard.length === 0) return;
928
615
  const sel = this.selectTool();
929
616
  if (!sel) return;
930
617
  this.pasteCount++;
931
618
  this.insertClones(this.clipboard, this.pasteCount * 20, sel);
932
619
  }
933
620
  duplicate() {
934
- this.flushPendingNudge();
935
621
  if (this.deps.isToolActive()) return;
622
+ this.flushPendingNudge();
936
623
  const sel = this.selectTool();
937
624
  if (!sel) return;
938
625
  const source = [];
@@ -1116,6 +803,11 @@ function parseBinding(binding) {
1116
803
  throw new Error(`Invalid shortcut binding "${binding}": unknown modifier "${part}"`);
1117
804
  }
1118
805
  }
806
+ if (parsed.mod && (parsed.ctrl || parsed.meta)) {
807
+ throw new Error(
808
+ `Invalid shortcut binding "${binding}": "mod" already means Ctrl or Cmd; don't combine it with ctrl/meta`
809
+ );
810
+ }
1119
811
  return parsed;
1120
812
  }
1121
813
  function bindingMatches(p, e, allowShift) {
@@ -1259,6 +951,10 @@ var InputHandler = class {
1259
951
  this.inputFilter.reset();
1260
952
  this.deferredDown = null;
1261
953
  this.lastPointerEvent = null;
954
+ if (this.scope === "focus") {
955
+ this.element.removeAttribute("tabindex");
956
+ this.element.style.outline = "";
957
+ }
1262
958
  }
1263
959
  bind() {
1264
960
  const opts = { signal: this.abortController.signal };
@@ -1449,145 +1145,433 @@ var InputHandler = class {
1449
1145
  }
1450
1146
  return;
1451
1147
  }
1452
- default:
1453
- if (action.startsWith("tool:")) {
1454
- if (this.isToolActive) return;
1455
- e.preventDefault();
1456
- this.toolContext?.switchTool?.(action.slice("tool:".length));
1457
- return;
1458
- }
1459
- console.warn(`[fieldnotes] unknown shortcut action "${action}"`);
1148
+ default:
1149
+ if (action.startsWith("tool:")) {
1150
+ if (this.isToolActive) return;
1151
+ e.preventDefault();
1152
+ this.toolContext?.switchTool?.(action.slice("tool:".length));
1153
+ return;
1154
+ }
1155
+ console.warn(`[fieldnotes] unknown shortcut action "${action}"`);
1156
+ }
1157
+ }
1158
+ startPinch() {
1159
+ this.inputFilter.reset();
1160
+ this.deferredDown = null;
1161
+ this.isPanning = true;
1162
+ const [a, b] = this.getPinchPoints();
1163
+ this.lastPinchDistance = this.distance(a, b);
1164
+ this.lastPinchCenter = this.midpoint(a, b);
1165
+ this.lastPointer = { ...this.lastPinchCenter };
1166
+ }
1167
+ handlePinchMove() {
1168
+ const [a, b] = this.getPinchPoints();
1169
+ const dist = this.distance(a, b);
1170
+ const center = this.midpoint(a, b);
1171
+ if (this.lastPinchDistance > 0) {
1172
+ const scale = dist / this.lastPinchDistance;
1173
+ const newZoom = this.camera.zoom * scale;
1174
+ this.camera.zoomAt(newZoom, center);
1175
+ }
1176
+ const dx = center.x - this.lastPointer.x;
1177
+ const dy = center.y - this.lastPointer.y;
1178
+ this.camera.pan(dx, dy);
1179
+ this.lastPinchDistance = dist;
1180
+ this.lastPinchCenter = center;
1181
+ this.lastPointer = { ...center };
1182
+ }
1183
+ getPinchPoints() {
1184
+ const pts = [...this.activePointers.values()];
1185
+ return [pts[0] ?? { x: 0, y: 0 }, pts[1] ?? { x: 0, y: 0 }];
1186
+ }
1187
+ distance(a, b) {
1188
+ const dx = a.x - b.x;
1189
+ const dy = a.y - b.y;
1190
+ return Math.sqrt(dx * dx + dy * dy);
1191
+ }
1192
+ midpoint(a, b) {
1193
+ return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
1194
+ }
1195
+ toPointerState(e) {
1196
+ const rect = this.element.getBoundingClientRect();
1197
+ return {
1198
+ x: e.clientX - rect.left,
1199
+ y: e.clientY - rect.top,
1200
+ pressure: e.pressure,
1201
+ pointerType: e.pointerType === "touch" || e.pointerType === "pen" ? e.pointerType : "mouse",
1202
+ shiftKey: e.shiftKey
1203
+ };
1204
+ }
1205
+ dispatchToolDown(e) {
1206
+ if (!this.toolManager || !this.toolContext) return;
1207
+ this.actions.flushPendingNudge();
1208
+ this.historyRecorder?.begin();
1209
+ this.isToolActive = true;
1210
+ this.toolManager.handlePointerDown(this.toPointerState(e), this.toolContext);
1211
+ }
1212
+ dispatchToolMove(e) {
1213
+ if (!this.toolManager || !this.toolContext) return;
1214
+ this.toolManager.handlePointerMove(this.toPointerState(e), this.toolContext);
1215
+ }
1216
+ dispatchToolHover(e) {
1217
+ if (!this.toolManager?.activeTool || !this.toolContext) return;
1218
+ const tool = this.toolManager.activeTool;
1219
+ if (tool.onHover) {
1220
+ tool.onHover(this.toPointerState(e), this.toolContext);
1221
+ }
1222
+ }
1223
+ dispatchToolUp(e) {
1224
+ if (!this.toolManager || !this.toolContext) return;
1225
+ this.toolManager.handlePointerUp(this.toPointerState(e), this.toolContext);
1226
+ this.historyRecorder?.commit();
1227
+ }
1228
+ isInScope() {
1229
+ if (this.scope === "window") return true;
1230
+ const active = document.activeElement;
1231
+ return active === this.element || this.element.contains(active);
1232
+ }
1233
+ focusSelf() {
1234
+ if (this.scope !== "focus" || this.isInScope()) return;
1235
+ this.element.focus({ preventScroll: true });
1236
+ }
1237
+ cancelToolIfActive(e) {
1238
+ if (this.isToolActive) {
1239
+ this.dispatchToolUp(e);
1240
+ this.isToolActive = false;
1241
+ }
1242
+ this.deferredDown = null;
1243
+ }
1244
+ };
1245
+
1246
+ // src/canvas/background.ts
1247
+ var MIN_PATTERN_SPACING = 16;
1248
+ var DEFAULTS = {
1249
+ pattern: "dots",
1250
+ spacing: 24,
1251
+ color: "#d0d0d0",
1252
+ dotRadius: 1,
1253
+ lineWidth: 0.5
1254
+ };
1255
+ var Background = class {
1256
+ pattern;
1257
+ spacing;
1258
+ color;
1259
+ dotRadius;
1260
+ lineWidth;
1261
+ cachedCanvas = null;
1262
+ cachedCtx = null;
1263
+ lastZoom = -1;
1264
+ lastOffsetX = -Infinity;
1265
+ lastOffsetY = -Infinity;
1266
+ lastWidth = 0;
1267
+ lastHeight = 0;
1268
+ constructor(options = {}) {
1269
+ this.pattern = options.pattern ?? DEFAULTS.pattern;
1270
+ this.spacing = options.spacing ?? DEFAULTS.spacing;
1271
+ this.color = options.color ?? DEFAULTS.color;
1272
+ this.dotRadius = options.dotRadius ?? DEFAULTS.dotRadius;
1273
+ this.lineWidth = options.lineWidth ?? DEFAULTS.lineWidth;
1274
+ }
1275
+ render(ctx, camera) {
1276
+ const { width, height } = ctx.canvas;
1277
+ const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
1278
+ const cssWidth = width / dpr;
1279
+ const cssHeight = height / dpr;
1280
+ ctx.save();
1281
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
1282
+ ctx.clearRect(0, 0, cssWidth, cssHeight);
1283
+ if (this.pattern === "none") {
1284
+ ctx.restore();
1285
+ return;
1286
+ }
1287
+ const spacing = this.adaptSpacing(this.spacing, camera.zoom);
1288
+ const keyZoom = camera.zoom;
1289
+ const keyX = Math.floor(camera.position.x % spacing);
1290
+ const keyY = Math.floor(camera.position.y % spacing);
1291
+ if (this.cachedCanvas !== null && keyZoom === this.lastZoom && keyX === this.lastOffsetX && keyY === this.lastOffsetY && cssWidth === this.lastWidth && cssHeight === this.lastHeight) {
1292
+ ctx.drawImage(this.cachedCanvas, 0, 0);
1293
+ ctx.restore();
1294
+ return;
1295
+ }
1296
+ this.ensureCachedCanvas(cssWidth, cssHeight, dpr);
1297
+ if (this.cachedCtx === null) {
1298
+ if (this.pattern === "dots") {
1299
+ this.renderDots(ctx, camera, cssWidth, cssHeight);
1300
+ } else if (this.pattern === "grid") {
1301
+ this.renderGrid(ctx, camera, cssWidth, cssHeight);
1302
+ }
1303
+ ctx.restore();
1304
+ return;
1305
+ }
1306
+ const offCtx = this.cachedCtx;
1307
+ offCtx.clearRect(0, 0, cssWidth, cssHeight);
1308
+ if (this.pattern === "dots") {
1309
+ this.renderDots(offCtx, camera, cssWidth, cssHeight);
1310
+ } else if (this.pattern === "grid") {
1311
+ this.renderGrid(offCtx, camera, cssWidth, cssHeight);
1312
+ }
1313
+ this.lastZoom = keyZoom;
1314
+ this.lastOffsetX = keyX;
1315
+ this.lastOffsetY = keyY;
1316
+ this.lastWidth = cssWidth;
1317
+ this.lastHeight = cssHeight;
1318
+ ctx.drawImage(this.cachedCanvas, 0, 0);
1319
+ ctx.restore();
1320
+ }
1321
+ ensureCachedCanvas(cssWidth, cssHeight, dpr) {
1322
+ if (this.cachedCanvas !== null && this.lastWidth === cssWidth && this.lastHeight === cssHeight) {
1323
+ return;
1324
+ }
1325
+ const physWidth = Math.round(cssWidth * dpr);
1326
+ const physHeight = Math.round(cssHeight * dpr);
1327
+ if (typeof OffscreenCanvas !== "undefined") {
1328
+ this.cachedCanvas = new OffscreenCanvas(physWidth, physHeight);
1329
+ } else if (typeof document !== "undefined") {
1330
+ const el = document.createElement("canvas");
1331
+ el.width = physWidth;
1332
+ el.height = physHeight;
1333
+ this.cachedCanvas = el;
1334
+ } else {
1335
+ this.cachedCanvas = null;
1336
+ this.cachedCtx = null;
1337
+ return;
1338
+ }
1339
+ const offCtx = this.cachedCanvas.getContext("2d");
1340
+ if (offCtx !== null) {
1341
+ offCtx.scale(dpr, dpr);
1342
+ }
1343
+ this.cachedCtx = offCtx;
1344
+ this.lastZoom = -1;
1345
+ }
1346
+ adaptSpacing(baseSpacing, zoom) {
1347
+ let spacing = baseSpacing * zoom;
1348
+ while (spacing < MIN_PATTERN_SPACING) {
1349
+ spacing *= 2;
1350
+ }
1351
+ return spacing;
1352
+ }
1353
+ renderDots(ctx, camera, width, height) {
1354
+ const spacing = this.adaptSpacing(this.spacing, camera.zoom);
1355
+ const offsetX = camera.position.x % spacing;
1356
+ const offsetY = camera.position.y % spacing;
1357
+ const radius = this.dotRadius * Math.min(camera.zoom, 2);
1358
+ ctx.fillStyle = this.color;
1359
+ ctx.beginPath();
1360
+ for (let x = offsetX; x < width; x += spacing) {
1361
+ for (let y = offsetY; y < height; y += spacing) {
1362
+ ctx.moveTo(x + radius, y);
1363
+ ctx.arc(x, y, radius, 0, Math.PI * 2);
1364
+ }
1365
+ }
1366
+ ctx.fill();
1367
+ }
1368
+ renderGrid(ctx, camera, width, height) {
1369
+ const spacing = this.adaptSpacing(this.spacing, camera.zoom);
1370
+ const offsetX = camera.position.x % spacing;
1371
+ const offsetY = camera.position.y % spacing;
1372
+ const lineW = this.lineWidth * Math.min(camera.zoom, 2);
1373
+ ctx.fillStyle = this.color;
1374
+ for (let x = offsetX; x < width; x += spacing) {
1375
+ ctx.fillRect(x, 0, lineW, height);
1376
+ }
1377
+ for (let y = offsetY; y < height; y += spacing) {
1378
+ ctx.fillRect(0, y, width, lineW);
1379
+ }
1380
+ }
1381
+ };
1382
+
1383
+ // src/core/event-bus.ts
1384
+ var EventBus = class {
1385
+ listeners = /* @__PURE__ */ new Map();
1386
+ on(event, listener) {
1387
+ const existing = this.listeners.get(event);
1388
+ if (existing) {
1389
+ existing.add(listener);
1390
+ } else {
1391
+ const set = /* @__PURE__ */ new Set([listener]);
1392
+ this.listeners.set(event, set);
1393
+ }
1394
+ return () => this.off(event, listener);
1395
+ }
1396
+ off(event, listener) {
1397
+ this.listeners.get(event)?.delete(listener);
1398
+ }
1399
+ emit(event, data) {
1400
+ this.listeners.get(event)?.forEach((listener) => {
1401
+ try {
1402
+ listener(data);
1403
+ } catch (err) {
1404
+ console.error(`[fieldnotes] listener error for "${String(event)}"`, err);
1405
+ }
1406
+ });
1407
+ }
1408
+ clear() {
1409
+ this.listeners.clear();
1410
+ }
1411
+ };
1412
+
1413
+ // src/core/quadtree.ts
1414
+ var MAX_ITEMS = 8;
1415
+ var MAX_DEPTH = 8;
1416
+ function intersects(a, b) {
1417
+ return a.x <= b.x + b.w && a.x + a.w >= b.x && a.y <= b.y + b.h && a.y + a.h >= b.y;
1418
+ }
1419
+ var QuadNode = class _QuadNode {
1420
+ constructor(bounds, depth) {
1421
+ this.bounds = bounds;
1422
+ this.depth = depth;
1423
+ }
1424
+ items = [];
1425
+ children = null;
1426
+ insert(entry) {
1427
+ if (this.children) {
1428
+ const idx = this.getChildIndex(entry.bounds);
1429
+ if (idx !== -1) {
1430
+ const child = this.children[idx];
1431
+ if (child) child.insert(entry);
1432
+ return;
1433
+ }
1434
+ this.items.push(entry);
1435
+ return;
1436
+ }
1437
+ this.items.push(entry);
1438
+ if (this.items.length > MAX_ITEMS && this.depth < MAX_DEPTH) {
1439
+ this.split();
1440
+ }
1441
+ }
1442
+ remove(id) {
1443
+ const idx = this.items.findIndex((e) => e.id === id);
1444
+ if (idx !== -1) {
1445
+ this.items.splice(idx, 1);
1446
+ return true;
1447
+ }
1448
+ if (this.children) {
1449
+ for (const child of this.children) {
1450
+ if (child.remove(id)) {
1451
+ this.collapseIfEmpty();
1452
+ return true;
1453
+ }
1454
+ }
1455
+ }
1456
+ return false;
1457
+ }
1458
+ query(rect, result) {
1459
+ if (!intersects(this.bounds, rect)) return;
1460
+ for (const item of this.items) {
1461
+ if (intersects(item.bounds, rect)) {
1462
+ result.push(item.id);
1463
+ }
1464
+ }
1465
+ if (this.children) {
1466
+ for (const child of this.children) {
1467
+ child.query(rect, result);
1468
+ }
1469
+ }
1470
+ }
1471
+ getChildIndex(itemBounds) {
1472
+ const midX = this.bounds.x + this.bounds.w / 2;
1473
+ const midY = this.bounds.y + this.bounds.h / 2;
1474
+ const left = itemBounds.x >= this.bounds.x && itemBounds.x + itemBounds.w <= midX;
1475
+ const right = itemBounds.x >= midX && itemBounds.x + itemBounds.w <= this.bounds.x + this.bounds.w;
1476
+ const top = itemBounds.y >= this.bounds.y && itemBounds.y + itemBounds.h <= midY;
1477
+ const bottom = itemBounds.y >= midY && itemBounds.y + itemBounds.h <= this.bounds.y + this.bounds.h;
1478
+ if (left && top) return 0;
1479
+ if (right && top) return 1;
1480
+ if (left && bottom) return 2;
1481
+ if (right && bottom) return 3;
1482
+ return -1;
1483
+ }
1484
+ split() {
1485
+ const { x, y, w, h } = this.bounds;
1486
+ const halfW = w / 2;
1487
+ const halfH = h / 2;
1488
+ const d = this.depth + 1;
1489
+ this.children = [
1490
+ new _QuadNode({ x, y, w: halfW, h: halfH }, d),
1491
+ new _QuadNode({ x: x + halfW, y, w: halfW, h: halfH }, d),
1492
+ new _QuadNode({ x, y: y + halfH, w: halfW, h: halfH }, d),
1493
+ new _QuadNode({ x: x + halfW, y: y + halfH, w: halfW, h: halfH }, d)
1494
+ ];
1495
+ const remaining = [];
1496
+ for (const item of this.items) {
1497
+ const idx = this.getChildIndex(item.bounds);
1498
+ if (idx !== -1) {
1499
+ const target = this.children[idx];
1500
+ if (target) target.insert(item);
1501
+ } else {
1502
+ remaining.push(item);
1503
+ }
1460
1504
  }
1505
+ this.items = remaining;
1461
1506
  }
1462
- startPinch() {
1463
- this.inputFilter.reset();
1464
- this.deferredDown = null;
1465
- this.isPanning = true;
1466
- const [a, b] = this.getPinchPoints();
1467
- this.lastPinchDistance = this.distance(a, b);
1468
- this.lastPinchCenter = this.midpoint(a, b);
1469
- this.lastPointer = { ...this.lastPinchCenter };
1470
- }
1471
- handlePinchMove() {
1472
- const [a, b] = this.getPinchPoints();
1473
- const dist = this.distance(a, b);
1474
- const center = this.midpoint(a, b);
1475
- if (this.lastPinchDistance > 0) {
1476
- const scale = dist / this.lastPinchDistance;
1477
- const newZoom = this.camera.zoom * scale;
1478
- this.camera.zoomAt(newZoom, center);
1507
+ collapseIfEmpty() {
1508
+ if (!this.children) return;
1509
+ let totalItems = this.items.length;
1510
+ for (const child of this.children) {
1511
+ if (child.children) return;
1512
+ totalItems += child.items.length;
1513
+ }
1514
+ if (totalItems <= MAX_ITEMS) {
1515
+ for (const child of this.children) {
1516
+ this.items.push(...child.items);
1517
+ }
1518
+ this.children = null;
1479
1519
  }
1480
- const dx = center.x - this.lastPointer.x;
1481
- const dy = center.y - this.lastPointer.y;
1482
- this.camera.pan(dx, dy);
1483
- this.lastPinchDistance = dist;
1484
- this.lastPinchCenter = center;
1485
- this.lastPointer = { ...center };
1486
- }
1487
- getPinchPoints() {
1488
- const pts = [...this.activePointers.values()];
1489
- return [pts[0] ?? { x: 0, y: 0 }, pts[1] ?? { x: 0, y: 0 }];
1490
- }
1491
- distance(a, b) {
1492
- const dx = a.x - b.x;
1493
- const dy = a.y - b.y;
1494
- return Math.sqrt(dx * dx + dy * dy);
1495
- }
1496
- midpoint(a, b) {
1497
- return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
1498
1520
  }
1499
- toPointerState(e) {
1500
- const rect = this.element.getBoundingClientRect();
1501
- return {
1502
- x: e.clientX - rect.left,
1503
- y: e.clientY - rect.top,
1504
- pressure: e.pressure,
1505
- pointerType: e.pointerType === "touch" || e.pointerType === "pen" ? e.pointerType : "mouse",
1506
- shiftKey: e.shiftKey
1507
- };
1521
+ };
1522
+ var Quadtree = class {
1523
+ root;
1524
+ _size = 0;
1525
+ worldBounds;
1526
+ constructor(worldBounds) {
1527
+ this.worldBounds = worldBounds;
1528
+ this.root = new QuadNode(worldBounds, 0);
1508
1529
  }
1509
- dispatchToolDown(e) {
1510
- if (!this.toolManager || !this.toolContext) return;
1511
- this.actions.flushPendingNudge();
1512
- this.historyRecorder?.begin();
1513
- this.isToolActive = true;
1514
- this.toolManager.handlePointerDown(this.toPointerState(e), this.toolContext);
1530
+ get size() {
1531
+ return this._size;
1515
1532
  }
1516
- dispatchToolMove(e) {
1517
- if (!this.toolManager || !this.toolContext) return;
1518
- this.toolManager.handlePointerMove(this.toPointerState(e), this.toolContext);
1533
+ insert(id, bounds) {
1534
+ this.root.insert({ id, bounds });
1535
+ this._size++;
1519
1536
  }
1520
- dispatchToolHover(e) {
1521
- if (!this.toolManager?.activeTool || !this.toolContext) return;
1522
- const tool = this.toolManager.activeTool;
1523
- if (tool.onHover) {
1524
- tool.onHover(this.toPointerState(e), this.toolContext);
1537
+ remove(id) {
1538
+ if (this.root.remove(id)) {
1539
+ this._size--;
1525
1540
  }
1526
1541
  }
1527
- dispatchToolUp(e) {
1528
- if (!this.toolManager || !this.toolContext) return;
1529
- this.toolManager.handlePointerUp(this.toPointerState(e), this.toolContext);
1530
- this.historyRecorder?.commit();
1542
+ update(id, newBounds) {
1543
+ this.remove(id);
1544
+ this.insert(id, newBounds);
1531
1545
  }
1532
- isInScope() {
1533
- if (this.scope === "window") return true;
1534
- const active = document.activeElement;
1535
- return active === this.element || this.element.contains(active);
1546
+ query(rect) {
1547
+ const result = [];
1548
+ this.root.query(rect, result);
1549
+ return result;
1536
1550
  }
1537
- focusSelf() {
1538
- if (this.scope !== "focus" || this.isInScope()) return;
1539
- this.element.focus({ preventScroll: true });
1551
+ queryPoint(point) {
1552
+ return this.query({ x: point.x, y: point.y, w: 0, h: 0 });
1540
1553
  }
1541
- cancelToolIfActive(e) {
1542
- if (this.isToolActive) {
1543
- this.dispatchToolUp(e);
1544
- this.isToolActive = false;
1545
- }
1546
- this.deferredDown = null;
1554
+ clear() {
1555
+ this.root = new QuadNode(this.worldBounds, 0);
1556
+ this._size = 0;
1547
1557
  }
1548
1558
  };
1549
1559
 
1550
- // src/canvas/double-tap-detector.ts
1551
- var DEFAULT_TIMEOUT = 300;
1552
- var DEFAULT_MAX_DISTANCE = 20;
1553
- var DoubleTapDetector = class {
1554
- timeout;
1555
- maxDistance;
1556
- lastTapTime = 0;
1557
- lastTapX = 0;
1558
- lastTapY = 0;
1559
- hasPendingTap = false;
1560
- constructor(options) {
1561
- this.timeout = options?.timeout ?? DEFAULT_TIMEOUT;
1562
- this.maxDistance = options?.maxDistance ?? DEFAULT_MAX_DISTANCE;
1563
- }
1564
- feed(e) {
1565
- const now = Date.now();
1566
- const x = e.clientX;
1567
- const y = e.clientY;
1568
- if (this.hasPendingTap) {
1569
- const elapsed = now - this.lastTapTime;
1570
- const dx = x - this.lastTapX;
1571
- const dy = y - this.lastTapY;
1572
- const dist = Math.sqrt(dx * dx + dy * dy);
1573
- if (elapsed <= this.timeout && dist <= this.maxDistance) {
1574
- this.reset();
1575
- return true;
1576
- }
1577
- }
1578
- this.lastTapTime = now;
1579
- this.lastTapX = x;
1580
- this.lastTapY = y;
1581
- this.hasPendingTap = true;
1582
- return false;
1583
- }
1584
- reset() {
1585
- this.hasPendingTap = false;
1586
- this.lastTapTime = 0;
1587
- this.lastTapX = 0;
1588
- this.lastTapY = 0;
1560
+ // src/core/geometry.ts
1561
+ function distSqToSegment(p, a, b) {
1562
+ const abx = b.x - a.x;
1563
+ const aby = b.y - a.y;
1564
+ const apx = p.x - a.x;
1565
+ const apy = p.y - a.y;
1566
+ const lenSq = abx * abx + aby * aby;
1567
+ if (lenSq === 0) {
1568
+ return apx * apx + apy * apy;
1589
1569
  }
1590
- };
1570
+ const t = Math.max(0, Math.min(1, (apx * abx + apy * aby) / lenSq));
1571
+ const dx = p.x - (a.x + t * abx);
1572
+ const dy = p.y - (a.y + t * aby);
1573
+ return dx * dx + dy * dy;
1574
+ }
1591
1575
 
1592
1576
  // src/elements/arrow-geometry.ts
1593
1577
  function getArrowControlPoint(from, to, bend) {
@@ -1677,16 +1661,7 @@ function bezierPoint(from, cp, to, t) {
1677
1661
  };
1678
1662
  }
1679
1663
  function isNearLine(point, a, b, threshold) {
1680
- const dx = b.x - a.x;
1681
- const dy = b.y - a.y;
1682
- const lenSq = dx * dx + dy * dy;
1683
- if (lenSq === 0) {
1684
- return Math.hypot(point.x - a.x, point.y - a.y) <= threshold;
1685
- }
1686
- const t = Math.max(0, Math.min(1, ((point.x - a.x) * dx + (point.y - a.y) * dy) / lenSq));
1687
- const projX = a.x + t * dx;
1688
- const projY = a.y + t * dy;
1689
- return Math.hypot(point.x - projX, point.y - projY) <= threshold;
1664
+ return distSqToSegment(point, a, b) <= threshold * threshold;
1690
1665
  }
1691
1666
 
1692
1667
  // src/elements/element-bounds.ts
@@ -2227,51 +2202,6 @@ function updateBoundArrow(arrow, store) {
2227
2202
  }
2228
2203
  return Object.keys(updates).length > 0 ? updates : null;
2229
2204
  }
2230
- function clearStaleBindings(arrow, store) {
2231
- const updates = {};
2232
- let hasUpdates = false;
2233
- if (arrow.fromBinding && !store.getById(arrow.fromBinding.elementId)) {
2234
- updates.fromBinding = void 0;
2235
- hasUpdates = true;
2236
- }
2237
- if (arrow.toBinding && !store.getById(arrow.toBinding.elementId)) {
2238
- updates.toBinding = void 0;
2239
- hasUpdates = true;
2240
- }
2241
- return hasUpdates ? updates : null;
2242
- }
2243
- function unbindArrow(arrow, store) {
2244
- const updates = {};
2245
- if (arrow.fromBinding) {
2246
- const el = store.getById(arrow.fromBinding.elementId);
2247
- const bounds = el ? getElementBounds(el) : null;
2248
- if (bounds) {
2249
- const angle = getArrowTangentAngle(arrow.from, arrow.to, arrow.bend, 0);
2250
- const rayTarget = {
2251
- x: arrow.from.x + Math.cos(angle) * 1e3,
2252
- y: arrow.from.y + Math.sin(angle) * 1e3
2253
- };
2254
- const edge = getEdgeIntersection(bounds, rayTarget);
2255
- updates.from = edge;
2256
- updates.position = edge;
2257
- }
2258
- updates.fromBinding = void 0;
2259
- }
2260
- if (arrow.toBinding) {
2261
- const el = store.getById(arrow.toBinding.elementId);
2262
- const bounds = el ? getElementBounds(el) : null;
2263
- if (bounds) {
2264
- const angle = getArrowTangentAngle(arrow.from, arrow.to, arrow.bend, 1);
2265
- const rayTarget = {
2266
- x: arrow.to.x - Math.cos(angle) * 1e3,
2267
- y: arrow.to.y - Math.sin(angle) * 1e3
2268
- };
2269
- updates.to = getEdgeIntersection(bounds, rayTarget);
2270
- }
2271
- updates.toBinding = void 0;
2272
- }
2273
- return updates;
2274
- }
2275
2205
 
2276
2206
  // src/elements/grid-renderer.ts
2277
2207
  function getSquareGridLines(bounds, cellSize) {
@@ -3117,9 +3047,9 @@ var ElementRenderer = class {
3117
3047
  });
3118
3048
  }
3119
3049
  };
3120
- img.onerror = () => {
3050
+ img.onerror = (event) => {
3121
3051
  this.imageCache.set(src, "failed");
3122
- this.onImageError?.(src);
3052
+ this.onImageError?.(src, event);
3123
3053
  this.onImageLoad?.();
3124
3054
  };
3125
3055
  return null;
@@ -3544,7 +3474,10 @@ var NoteEditor = class {
3544
3474
  this.editingNode.removeAttribute("data-fn-placeholder");
3545
3475
  this.editingNode.removeAttribute("data-fn-empty");
3546
3476
  const text = sanitizeNoteHtml(this.editingNode.innerHTML);
3547
- store.update(this.editingId, { text });
3477
+ const current = store.getById(this.editingId);
3478
+ if (current && (current.type === "note" || current.type === "text") && current.text !== text) {
3479
+ store.update(this.editingId, { text });
3480
+ }
3548
3481
  this.editingNode.contentEditable = "false";
3549
3482
  Object.assign(this.editingNode.style, {
3550
3483
  userSelect: "none",
@@ -4461,6 +4394,48 @@ var InteractMode = class {
4461
4394
  };
4462
4395
  };
4463
4396
 
4397
+ // src/canvas/double-tap-detector.ts
4398
+ var DEFAULT_TIMEOUT = 300;
4399
+ var DEFAULT_MAX_DISTANCE = 20;
4400
+ var DoubleTapDetector = class {
4401
+ timeout;
4402
+ maxDistance;
4403
+ lastTapTime = 0;
4404
+ lastTapX = 0;
4405
+ lastTapY = 0;
4406
+ hasPendingTap = false;
4407
+ constructor(options) {
4408
+ this.timeout = options?.timeout ?? DEFAULT_TIMEOUT;
4409
+ this.maxDistance = options?.maxDistance ?? DEFAULT_MAX_DISTANCE;
4410
+ }
4411
+ feed(e) {
4412
+ const now = Date.now();
4413
+ const x = e.clientX;
4414
+ const y = e.clientY;
4415
+ if (this.hasPendingTap) {
4416
+ const elapsed = now - this.lastTapTime;
4417
+ const dx = x - this.lastTapX;
4418
+ const dy = y - this.lastTapY;
4419
+ const dist = Math.sqrt(dx * dx + dy * dy);
4420
+ if (elapsed <= this.timeout && dist <= this.maxDistance) {
4421
+ this.reset();
4422
+ return true;
4423
+ }
4424
+ }
4425
+ this.lastTapTime = now;
4426
+ this.lastTapX = x;
4427
+ this.lastTapY = y;
4428
+ this.hasPendingTap = true;
4429
+ return false;
4430
+ }
4431
+ reset() {
4432
+ this.hasPendingTap = false;
4433
+ this.lastTapTime = 0;
4434
+ this.lastTapX = 0;
4435
+ this.lastTapY = 0;
4436
+ }
4437
+ };
4438
+
4464
4439
  // src/canvas/dom-node-manager.ts
4465
4440
  var DomNodeManager = class {
4466
4441
  domNodes = /* @__PURE__ */ new Map();
@@ -5141,13 +5116,13 @@ var Viewport = class {
5141
5116
  this.renderLoop.markAllLayersDirty();
5142
5117
  this.requestRender();
5143
5118
  });
5144
- this.renderer.setOnImageError((src) => {
5119
+ this.renderer.setOnImageError((src, cause) => {
5145
5120
  const elementIds = [];
5146
5121
  for (const el of this.store.getAll()) {
5147
5122
  if (el.type === "image" && el.src === src) elementIds.push(el.id);
5148
5123
  }
5149
5124
  if (options.onImageError) {
5150
- options.onImageError({ src, elementIds });
5125
+ options.onImageError({ src, elementIds, cause });
5151
5126
  } else {
5152
5127
  console.warn(`[fieldnotes] image failed to load: ${src}`);
5153
5128
  }
@@ -5366,6 +5341,10 @@ var Viewport = class {
5366
5341
  this.loadState(parseState(json));
5367
5342
  }
5368
5343
  setTool(name) {
5344
+ if (!this.toolManager.getTool(name)) {
5345
+ console.warn(`[fieldnotes] setTool: no tool registered as "${name}"`);
5346
+ return;
5347
+ }
5369
5348
  this.toolManager.setTool(name, this.toolContext);
5370
5349
  }
5371
5350
  get shortcuts() {
@@ -5862,20 +5841,6 @@ var PencilTool = class {
5862
5841
  };
5863
5842
 
5864
5843
  // src/elements/stroke-hit.ts
5865
- function distSqToSegment(p, a, b) {
5866
- const abx = b.x - a.x;
5867
- const aby = b.y - a.y;
5868
- const apx = p.x - a.x;
5869
- const apy = p.y - a.y;
5870
- const lenSq = abx * abx + aby * aby;
5871
- if (lenSq === 0) {
5872
- return apx * apx + apy * apy;
5873
- }
5874
- const t = Math.max(0, Math.min(1, (apx * abx + apy * aby) / lenSq));
5875
- const dx = p.x - (a.x + t * abx);
5876
- const dy = p.y - (a.y + t * aby);
5877
- return dx * dx + dy * dy;
5878
- }
5879
5844
  function hitTestStroke(stroke, point, radius) {
5880
5845
  const bounds = getElementBounds(stroke);
5881
5846
  if (!bounds) return false;
@@ -7472,52 +7437,32 @@ var TemplateTool = class {
7472
7437
  };
7473
7438
 
7474
7439
  // src/index.ts
7475
- var VERSION = "0.23.0";
7440
+ var VERSION = "0.25.0";
7476
7441
  export {
7477
- AddElementCommand,
7478
7442
  ArrowTool,
7479
7443
  AutoSave,
7480
- Background,
7481
- BatchCommand,
7482
7444
  Camera,
7483
- CreateLayerCommand,
7484
- DEFAULT_FONT_SIZE_PRESETS,
7485
7445
  DEFAULT_NOTE_FONT_SIZE,
7486
- DoubleTapDetector,
7487
- ElementRenderer,
7488
7446
  ElementStore,
7489
7447
  EraserTool,
7490
- EventBus,
7491
7448
  HandTool,
7492
- HistoryRecorder,
7493
7449
  HistoryStack,
7494
7450
  ImageTool,
7495
- InputFilter,
7496
- InputHandler,
7497
7451
  LayerManager,
7498
7452
  MeasureTool,
7499
- NoteEditor,
7500
7453
  NoteTool,
7501
- NoteToolbar,
7502
7454
  PencilTool,
7503
- Quadtree,
7504
- RemoveElementCommand,
7505
- RemoveLayerCommand,
7506
7455
  SelectTool,
7507
7456
  ShapeTool,
7508
7457
  TemplateTool,
7509
7458
  TextTool,
7510
7459
  ToolManager,
7511
- UpdateElementCommand,
7512
- UpdateLayerCommand,
7513
7460
  VERSION,
7514
7461
  Viewport,
7515
7462
  boundsIntersect,
7516
- clearStaleBindings,
7517
7463
  createArrow,
7518
7464
  createGrid,
7519
7465
  createHtmlElement,
7520
- createId,
7521
7466
  createImage,
7522
7467
  createNote,
7523
7468
  createShape,
@@ -7526,29 +7471,20 @@ export {
7526
7471
  createText,
7527
7472
  drawHexPath,
7528
7473
  exportImage,
7529
- exportState,
7530
- findBindTarget,
7531
- findBoundArrows,
7532
7474
  getActiveFormats,
7533
7475
  getArrowBounds,
7534
7476
  getArrowControlPoint,
7535
7477
  getArrowMidpoint,
7536
7478
  getArrowTangentAngle,
7537
7479
  getBendFromPoint,
7538
- getEdgeIntersection,
7539
7480
  getElementBounds,
7540
- getElementCenter,
7541
7481
  getElementsBoundingBox,
7542
7482
  getHexCellsInCone,
7543
7483
  getHexCellsInLine,
7544
7484
  getHexCellsInRadius,
7545
7485
  getHexCellsInSquare,
7546
7486
  getHexDistance,
7547
- isBindable,
7548
7487
  isNearBezier,
7549
- isNoteContentEmpty,
7550
- parseState,
7551
- sanitizeNoteHtml,
7552
7488
  setFontSize,
7553
7489
  smartSnap,
7554
7490
  snapPoint,
@@ -7556,8 +7492,6 @@ export {
7556
7492
  toggleBold,
7557
7493
  toggleItalic,
7558
7494
  toggleStrikethrough,
7559
- toggleUnderline,
7560
- unbindArrow,
7561
- updateBoundArrow
7495
+ toggleUnderline
7562
7496
  };
7563
7497
  //# sourceMappingURL=index.js.map