@fieldnotes/core 0.12.0 → 0.13.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.cjs CHANGED
@@ -37,7 +37,6 @@ __export(index_exports, {
37
37
  HistoryRecorder: () => HistoryRecorder,
38
38
  HistoryStack: () => HistoryStack,
39
39
  ImageTool: () => ImageTool,
40
- InputFilter: () => InputFilter,
41
40
  InputHandler: () => InputHandler,
42
41
  LayerManager: () => LayerManager,
43
42
  MeasureTool: () => MeasureTool,
@@ -399,13 +398,7 @@ function exportState(elements, camera, layers = []) {
399
398
  position: { ...camera.position },
400
399
  zoom: camera.zoom
401
400
  },
402
- elements: elements.map((el) => {
403
- const clone = structuredClone(el);
404
- if (clone.type === "arrow") {
405
- delete clone.cachedControlPoint;
406
- }
407
- return clone;
408
- }),
401
+ elements: elements.map((el) => structuredClone(el)),
409
402
  layers: layers.map((l) => ({ ...l }))
410
403
  };
411
404
  }
@@ -843,67 +836,6 @@ var Background = class {
843
836
  }
844
837
  };
845
838
 
846
- // src/canvas/input-filter.ts
847
- var InputFilter = class _InputFilter {
848
- activePenId = null;
849
- pendingTap = null;
850
- static MIN_MOVE_DISTANCE = 3;
851
- filterDown(e) {
852
- if (e.pointerType === "pen") {
853
- this.activePenId = e.pointerId;
854
- return { event: e, action: "dispatch" };
855
- }
856
- if (e.pointerType === "touch" && this.activePenId !== null) {
857
- return { event: e, action: "suppress" };
858
- }
859
- if (e.pointerType === "touch") {
860
- this.pendingTap = { pointerId: e.pointerId, x: e.clientX, y: e.clientY };
861
- return { event: e, action: "defer" };
862
- }
863
- return { event: e, action: "dispatch" };
864
- }
865
- filterMove(e) {
866
- if (e.pointerType === "touch" && this.activePenId !== null) {
867
- return { event: e, action: "suppress" };
868
- }
869
- if (this.pendingTap && e.pointerId === this.pendingTap.pointerId) {
870
- const dx = e.clientX - this.pendingTap.x;
871
- const dy = e.clientY - this.pendingTap.y;
872
- if (dx * dx + dy * dy > _InputFilter.MIN_MOVE_DISTANCE * _InputFilter.MIN_MOVE_DISTANCE) {
873
- this.pendingTap = null;
874
- return { event: e, action: "dispatch" };
875
- }
876
- return { event: e, action: "suppress" };
877
- }
878
- return { event: e, action: "dispatch" };
879
- }
880
- filterUp(e) {
881
- if (e.pointerId === this.activePenId) {
882
- this.activePenId = null;
883
- return { event: e, action: "dispatch" };
884
- }
885
- if (e.pointerType === "touch" && this.activePenId !== null) {
886
- return { event: e, action: "suppress" };
887
- }
888
- if (this.pendingTap && e.pointerId === this.pendingTap.pointerId) {
889
- const tap = { x: this.pendingTap.x, y: this.pendingTap.y };
890
- this.pendingTap = null;
891
- return { event: e, action: "dispatch", pendingTap: tap };
892
- }
893
- return { event: e, action: "dispatch" };
894
- }
895
- reset() {
896
- this.activePenId = null;
897
- this.pendingTap = null;
898
- }
899
- };
900
-
901
- // src/elements/create-id.ts
902
- var counter = 0;
903
- function createId(prefix) {
904
- return `${prefix}_${Date.now().toString(36)}_${(counter++).toString(36)}`;
905
- }
906
-
907
839
  // src/canvas/input-handler.ts
908
840
  var ZOOM_SENSITIVITY = 1e-3;
909
841
  var MIDDLE_BUTTON = 1;
@@ -929,21 +861,13 @@ var InputHandler = class {
929
861
  historyRecorder;
930
862
  historyStack;
931
863
  isToolActive = false;
932
- lastPointerEvent = null;
933
- inputFilter = new InputFilter();
934
- deferredDown = null;
935
864
  abortController = new AbortController();
936
- clipboard = [];
937
- pasteCount = 0;
938
865
  setToolManager(toolManager, toolContext) {
939
866
  this.toolManager = toolManager;
940
867
  this.toolContext = toolContext;
941
868
  }
942
869
  destroy() {
943
870
  this.abortController.abort();
944
- this.inputFilter.reset();
945
- this.deferredDown = null;
946
- this.lastPointerEvent = null;
947
871
  }
948
872
  bind() {
949
873
  const opts = { signal: this.abortController.signal };
@@ -979,18 +903,11 @@ var InputHandler = class {
979
903
  this.lastPointer = { x: e.clientX, y: e.clientY };
980
904
  return;
981
905
  }
982
- if (this.activePointers.size === 1 && (e.button === 0 || e.pointerType === "touch" || e.pointerType === "pen")) {
983
- const result = this.inputFilter.filterDown(e);
984
- if (result.action === "suppress") return;
985
- if (result.action === "defer") {
986
- this.deferredDown = e;
987
- return;
988
- }
906
+ if (this.activePointers.size === 1 && e.button === 0) {
989
907
  this.dispatchToolDown(e);
990
908
  }
991
909
  };
992
910
  onPointerMove = (e) => {
993
- this.lastPointerEvent = e;
994
911
  if (this.activePointers.has(e.pointerId)) {
995
912
  this.activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
996
913
  }
@@ -1007,22 +924,11 @@ var InputHandler = class {
1007
924
  }
1008
925
  if (this.isToolActive) {
1009
926
  this.dispatchToolMove(e);
1010
- } else if (this.deferredDown) {
1011
- const result = this.inputFilter.filterMove(e);
1012
- if (result.action === "dispatch") {
1013
- this.dispatchToolDown(this.deferredDown);
1014
- this.deferredDown = null;
1015
- this.dispatchToolMove(e);
1016
- }
1017
927
  } else if (this.activePointers.size === 0) {
1018
928
  this.dispatchToolHover(e);
1019
929
  }
1020
930
  };
1021
931
  onPointerUp = (e) => {
1022
- try {
1023
- this.element.releasePointerCapture(e.pointerId);
1024
- } catch {
1025
- }
1026
932
  this.activePointers.delete(e.pointerId);
1027
933
  if (this.activePointers.size < 2) {
1028
934
  this.lastPinchDistance = 0;
@@ -1030,16 +936,9 @@ var InputHandler = class {
1030
936
  if (this.isPanning && this.activePointers.size === 0) {
1031
937
  this.isPanning = false;
1032
938
  }
1033
- const upResult = this.inputFilter.filterUp(e);
1034
939
  if (this.isToolActive) {
1035
940
  this.dispatchToolUp(e);
1036
941
  this.isToolActive = false;
1037
- } else if (this.deferredDown && upResult.pendingTap) {
1038
- this.dispatchToolDown(this.deferredDown);
1039
- this.dispatchToolUp(e);
1040
- this.deferredDown = null;
1041
- } else {
1042
- this.deferredDown = null;
1043
942
  }
1044
943
  };
1045
944
  onKeyDown = (e) => {
@@ -1058,30 +957,13 @@ var InputHandler = class {
1058
957
  e.preventDefault();
1059
958
  this.handleRedo();
1060
959
  }
1061
- if ((e.ctrlKey || e.metaKey) && e.key === "c") {
1062
- e.preventDefault();
1063
- this.handleCopy();
1064
- }
1065
- if ((e.ctrlKey || e.metaKey) && e.key === "v") {
1066
- e.preventDefault();
1067
- this.handlePaste();
1068
- }
1069
960
  };
1070
961
  onKeyUp = (e) => {
1071
962
  if (e.key === " ") {
1072
963
  this.spaceHeld = false;
1073
- if (this.activePointers.size === 0) {
1074
- if (this.lastPointerEvent) {
1075
- this.dispatchToolHover(this.lastPointerEvent);
1076
- } else {
1077
- this.toolContext?.setCursor?.("default");
1078
- }
1079
- }
1080
964
  }
1081
965
  };
1082
966
  startPinch() {
1083
- this.inputFilter.reset();
1084
- this.deferredDown = null;
1085
967
  this.isPanning = true;
1086
968
  const [a, b] = this.getPinchPoints();
1087
969
  this.lastPinchDistance = this.distance(a, b);
@@ -1121,9 +1003,7 @@ var InputHandler = class {
1121
1003
  return {
1122
1004
  x: e.clientX - rect.left,
1123
1005
  y: e.clientY - rect.top,
1124
- pressure: e.pressure,
1125
- pointerType: e.pointerType === "touch" || e.pointerType === "pen" ? e.pointerType : "mouse",
1126
- shiftKey: e.shiftKey
1006
+ pressure: e.pressure
1127
1007
  };
1128
1008
  }
1129
1009
  dispatchToolDown(e) {
@@ -1176,78 +1056,11 @@ var InputHandler = class {
1176
1056
  this.historyRecorder?.resume();
1177
1057
  this.toolContext.requestRender();
1178
1058
  }
1179
- handleCopy() {
1180
- if (!this.toolManager || !this.toolContext || this.isToolActive) return;
1181
- const tool = this.toolManager.activeTool;
1182
- if (tool?.name !== "select") return;
1183
- const selectTool = tool;
1184
- const ids = selectTool.selectedIds;
1185
- if (ids.length === 0) return;
1186
- this.clipboard = [];
1187
- for (const id of ids) {
1188
- const el = this.toolContext.store.getById(id);
1189
- if (el) this.clipboard.push(structuredClone(el));
1190
- }
1191
- this.pasteCount = 0;
1192
- }
1193
- handlePaste() {
1194
- if (!this.toolManager || !this.toolContext || this.clipboard.length === 0 || this.isToolActive)
1195
- return;
1196
- const tool = this.toolManager.activeTool;
1197
- if (tool?.name !== "select") return;
1198
- const selectTool = tool;
1199
- this.pasteCount++;
1200
- const offset = this.pasteCount * 20;
1201
- const idMap = /* @__PURE__ */ new Map();
1202
- for (const el of this.clipboard) {
1203
- idMap.set(el.id, createId(el.type));
1204
- }
1205
- const newIds = [];
1206
- this.historyRecorder?.begin();
1207
- for (const el of this.clipboard) {
1208
- const clone = structuredClone(el);
1209
- const newId = idMap.get(el.id);
1210
- if (!newId) continue;
1211
- clone.id = newId;
1212
- clone.position = { x: clone.position.x + offset, y: clone.position.y + offset };
1213
- if (clone.type === "arrow") {
1214
- const arrow = clone;
1215
- arrow.from = { x: arrow.from.x + offset, y: arrow.from.y + offset };
1216
- arrow.to = { x: arrow.to.x + offset, y: arrow.to.y + offset };
1217
- delete arrow.cachedControlPoint;
1218
- if (arrow.fromBinding) {
1219
- const newTarget = idMap.get(arrow.fromBinding.elementId);
1220
- if (newTarget) {
1221
- arrow.fromBinding = { elementId: newTarget };
1222
- } else {
1223
- delete arrow.fromBinding;
1224
- }
1225
- }
1226
- if (arrow.toBinding) {
1227
- const newTarget = idMap.get(arrow.toBinding.elementId);
1228
- if (newTarget) {
1229
- arrow.toBinding = { elementId: newTarget };
1230
- } else {
1231
- delete arrow.toBinding;
1232
- }
1233
- }
1234
- }
1235
- if (this.toolContext.activeLayerId) {
1236
- clone.layerId = this.toolContext.activeLayerId;
1237
- }
1238
- this.toolContext.store.add(clone);
1239
- newIds.push(clone.id);
1240
- }
1241
- this.historyRecorder?.commit();
1242
- selectTool.setSelection(newIds);
1243
- this.toolContext.requestRender();
1244
- }
1245
1059
  cancelToolIfActive(e) {
1246
1060
  if (this.isToolActive) {
1247
1061
  this.dispatchToolUp(e);
1248
1062
  this.isToolActive = false;
1249
1063
  }
1250
- this.deferredDown = null;
1251
1064
  }
1252
1065
  };
1253
1066
 
@@ -1495,23 +1308,19 @@ var ElementStore = class {
1495
1308
  bus = new EventBus();
1496
1309
  layerOrderMap = /* @__PURE__ */ new Map();
1497
1310
  spatialIndex = new Quadtree({ x: -1e5, y: -1e5, w: 2e5, h: 2e5 });
1498
- sortedCache = null;
1499
1311
  get count() {
1500
1312
  return this.elements.size;
1501
1313
  }
1502
1314
  setLayerOrder(order) {
1503
1315
  this.layerOrderMap = new Map(order);
1504
- this.sortedCache = null;
1505
1316
  }
1506
1317
  getAll() {
1507
- if (this.sortedCache) return this.sortedCache;
1508
- this.sortedCache = [...this.elements.values()].sort((a, b) => {
1318
+ return [...this.elements.values()].sort((a, b) => {
1509
1319
  const layerA = this.layerOrderMap.get(a.layerId) ?? 0;
1510
1320
  const layerB = this.layerOrderMap.get(b.layerId) ?? 0;
1511
1321
  if (layerA !== layerB) return layerA - layerB;
1512
1322
  return a.zIndex - b.zIndex;
1513
1323
  });
1514
- return this.sortedCache;
1515
1324
  }
1516
1325
  getById(id) {
1517
1326
  return this.elements.get(id);
@@ -1522,7 +1331,6 @@ var ElementStore = class {
1522
1331
  );
1523
1332
  }
1524
1333
  add(element) {
1525
- this.sortedCache = null;
1526
1334
  this.elements.set(element.id, element);
1527
1335
  const bounds = getElementBounds(element);
1528
1336
  if (bounds) this.spatialIndex.insert(element.id, bounds);
@@ -1531,15 +1339,11 @@ var ElementStore = class {
1531
1339
  update(id, partial) {
1532
1340
  const existing = this.elements.get(id);
1533
1341
  if (!existing) return;
1534
- this.sortedCache = null;
1535
1342
  const updated = { ...existing, ...partial, id: existing.id, type: existing.type };
1536
1343
  if (updated.type === "arrow") {
1537
1344
  const arrow = updated;
1538
1345
  arrow.cachedControlPoint = getArrowControlPoint(arrow.from, arrow.to, arrow.bend);
1539
1346
  }
1540
- if (updated.type === "note" && "text" in partial) {
1541
- updated.text = sanitizeNoteHtml(updated.text);
1542
- }
1543
1347
  this.elements.set(id, updated);
1544
1348
  const newBounds = getElementBounds(updated);
1545
1349
  if (newBounds) {
@@ -1550,13 +1354,11 @@ var ElementStore = class {
1550
1354
  remove(id) {
1551
1355
  const element = this.elements.get(id);
1552
1356
  if (!element) return;
1553
- this.sortedCache = null;
1554
1357
  this.elements.delete(id);
1555
1358
  this.spatialIndex.remove(id);
1556
1359
  this.bus.emit("remove", element);
1557
1360
  }
1558
1361
  clear() {
1559
- this.sortedCache = null;
1560
1362
  this.elements.clear();
1561
1363
  this.spatialIndex.clear();
1562
1364
  this.bus.emit("clear", null);
@@ -1565,7 +1367,6 @@ var ElementStore = class {
1565
1367
  return this.getAll().map((el) => ({ ...el }));
1566
1368
  }
1567
1369
  loadSnapshot(elements) {
1568
- this.sortedCache = null;
1569
1370
  this.elements.clear();
1570
1371
  this.spatialIndex.clear();
1571
1372
  for (const el of elements) {
@@ -1573,10 +1374,6 @@ var ElementStore = class {
1573
1374
  const bounds = getElementBounds(el);
1574
1375
  if (bounds) this.spatialIndex.insert(el.id, bounds);
1575
1376
  }
1576
- this.bus.emit("clear", null);
1577
- for (const el of elements) {
1578
- this.bus.emit("add", el);
1579
- }
1580
1377
  }
1581
1378
  queryRect(rect) {
1582
1379
  const ids = this.spatialIndex.query(rect);
@@ -2626,6 +2423,12 @@ var ElementRenderer = class {
2626
2423
  }
2627
2424
  };
2628
2425
 
2426
+ // src/elements/create-id.ts
2427
+ var counter = 0;
2428
+ function createId(prefix) {
2429
+ return `${prefix}_${Date.now().toString(36)}_${(counter++).toString(36)}`;
2430
+ }
2431
+
2629
2432
  // src/elements/element-factory.ts
2630
2433
  var DEFAULT_NOTE_FONT_SIZE = 18;
2631
2434
  function createStroke(input) {
@@ -2651,7 +2454,7 @@ function createNote(input) {
2651
2454
  locked: input.locked ?? false,
2652
2455
  layerId: input.layerId ?? "",
2653
2456
  size: input.size ?? { w: 200, h: 100 },
2654
- text: sanitizeNoteHtml(input.text ?? ""),
2457
+ text: input.text ?? "",
2655
2458
  backgroundColor: input.backgroundColor ?? "#ffeb3b",
2656
2459
  textColor: input.textColor ?? "#000000",
2657
2460
  fontSize: input.fontSize ?? DEFAULT_NOTE_FONT_SIZE
@@ -4758,10 +4561,7 @@ var Viewport = class {
4758
4561
  position: "relative",
4759
4562
  width: "100%",
4760
4563
  height: "100%",
4761
- overflow: "hidden",
4762
- overscrollBehavior: "none",
4763
- userSelect: "none",
4764
- webkitUserSelect: "none"
4564
+ overflow: "hidden"
4765
4565
  });
4766
4566
  return el;
4767
4567
  }
@@ -5176,15 +4976,9 @@ var SelectTool = class {
5176
4976
  lastWorld = { x: 0, y: 0 };
5177
4977
  currentWorld = { x: 0, y: 0 };
5178
4978
  ctx = null;
5179
- pendingSingleSelectId = null;
5180
- hasDragged = false;
5181
4979
  get selectedIds() {
5182
4980
  return [...this._selectedIds];
5183
4981
  }
5184
- setSelection(ids) {
5185
- this._selectedIds = ids;
5186
- this.ctx?.requestRender();
5187
- }
5188
4982
  get isMarqueeActive() {
5189
4983
  return this.mode.type === "marquee";
5190
4984
  }
@@ -5233,27 +5027,13 @@ var SelectTool = class {
5233
5027
  return;
5234
5028
  }
5235
5029
  }
5236
- this.pendingSingleSelectId = null;
5237
- this.hasDragged = false;
5238
5030
  const hit = this.hitTest(world, ctx);
5239
5031
  if (hit) {
5240
5032
  const alreadySelected = this._selectedIds.includes(hit.id);
5241
- if (state.shiftKey) {
5242
- if (alreadySelected) {
5243
- this._selectedIds = this._selectedIds.filter((id) => id !== hit.id);
5244
- this.mode = { type: "idle" };
5245
- } else {
5246
- this._selectedIds = [...this._selectedIds, hit.id];
5247
- this.mode = hit.locked ? { type: "idle" } : { type: "dragging" };
5248
- }
5249
- } else {
5250
- if (!alreadySelected) {
5251
- this._selectedIds = [hit.id];
5252
- } else if (this._selectedIds.length > 1) {
5253
- this.pendingSingleSelectId = hit.id;
5254
- }
5255
- this.mode = hit.locked ? { type: "idle" } : { type: "dragging" };
5033
+ if (!alreadySelected) {
5034
+ this._selectedIds = [hit.id];
5256
5035
  }
5036
+ this.mode = hit.locked ? { type: "idle" } : { type: "dragging" };
5257
5037
  } else {
5258
5038
  this._selectedIds = [];
5259
5039
  this.mode = { type: "marquee", start: world };
@@ -5279,7 +5059,6 @@ var SelectTool = class {
5279
5059
  return;
5280
5060
  }
5281
5061
  if (this.mode.type === "dragging" && this._selectedIds.length > 0) {
5282
- this.hasDragged = true;
5283
5062
  ctx.setCursor?.("move");
5284
5063
  const snapped = this.snap(world, ctx);
5285
5064
  const dx = snapped.x - this.lastWorld.x;
@@ -5348,11 +5127,6 @@ var SelectTool = class {
5348
5127
  }
5349
5128
  ctx.requestRender();
5350
5129
  }
5351
- if (!this.hasDragged && this.pendingSingleSelectId !== null) {
5352
- this._selectedIds = [this.pendingSingleSelectId];
5353
- }
5354
- this.pendingSingleSelectId = null;
5355
- this.hasDragged = false;
5356
5130
  this.mode = { type: "idle" };
5357
5131
  ctx.setCursor?.("default");
5358
5132
  }
@@ -6533,7 +6307,7 @@ var UpdateLayerCommand = class {
6533
6307
  };
6534
6308
 
6535
6309
  // src/index.ts
6536
- var VERSION = "0.12.0";
6310
+ var VERSION = "0.11.0";
6537
6311
  // Annotate the CommonJS export names for ESM import in node:
6538
6312
  0 && (module.exports = {
6539
6313
  AddElementCommand,
@@ -6553,7 +6327,6 @@ var VERSION = "0.12.0";
6553
6327
  HistoryRecorder,
6554
6328
  HistoryStack,
6555
6329
  ImageTool,
6556
- InputFilter,
6557
6330
  InputHandler,
6558
6331
  LayerManager,
6559
6332
  MeasureTool,
package/dist/index.d.cts CHANGED
@@ -200,7 +200,6 @@ declare class ElementStore {
200
200
  private bus;
201
201
  private layerOrderMap;
202
202
  private spatialIndex;
203
- private sortedCache;
204
203
  get count(): number;
205
204
  setLayerOrder(order: Map<string, number>): void;
206
205
  getAll(): CanvasElement[];
@@ -239,8 +238,6 @@ interface PointerState {
239
238
  x: number;
240
239
  y: number;
241
240
  pressure: number;
242
- pointerType: 'mouse' | 'touch' | 'pen';
243
- shiftKey: boolean;
244
241
  }
245
242
  interface Tool {
246
243
  readonly name: string;
@@ -426,12 +423,7 @@ declare class InputHandler {
426
423
  private historyRecorder;
427
424
  private historyStack;
428
425
  private isToolActive;
429
- private lastPointerEvent;
430
- private readonly inputFilter;
431
- private deferredDown;
432
426
  private readonly abortController;
433
- private clipboard;
434
- private pasteCount;
435
427
  constructor(element: HTMLElement, camera: Camera, options?: InputHandlerOptions);
436
428
  setToolManager(toolManager: ToolManager, toolContext: ToolContext): void;
437
429
  destroy(): void;
@@ -455,32 +447,9 @@ declare class InputHandler {
455
447
  private deleteSelected;
456
448
  private handleUndo;
457
449
  private handleRedo;
458
- private handleCopy;
459
- private handlePaste;
460
450
  private cancelToolIfActive;
461
451
  }
462
452
 
463
- type FilterAction = 'dispatch' | 'suppress' | 'defer';
464
- interface FilteredEvent {
465
- event: PointerEvent;
466
- action: FilterAction;
467
- }
468
- interface FilteredUpEvent extends FilteredEvent {
469
- pendingTap?: {
470
- x: number;
471
- y: number;
472
- };
473
- }
474
- declare class InputFilter {
475
- private activePenId;
476
- private pendingTap;
477
- static readonly MIN_MOVE_DISTANCE = 3;
478
- filterDown(e: PointerEvent): FilteredEvent;
479
- filterMove(e: PointerEvent): FilteredEvent;
480
- filterUp(e: PointerEvent): FilteredUpEvent;
481
- reset(): void;
482
- }
483
-
484
453
  interface FontSizePreset {
485
454
  label: string;
486
455
  size: number;
@@ -905,10 +874,7 @@ declare class SelectTool implements Tool {
905
874
  private lastWorld;
906
875
  private currentWorld;
907
876
  private ctx;
908
- private pendingSingleSelectId;
909
- private hasDragged;
910
877
  get selectedIds(): string[];
911
- setSelection(ids: string[]): void;
912
878
  get isMarqueeActive(): boolean;
913
879
  onActivate(ctx: ToolContext): void;
914
880
  onDeactivate(ctx: ToolContext): void;
@@ -1153,6 +1119,6 @@ declare class UpdateLayerCommand implements Command {
1153
1119
  undo(_store: ElementStore): void;
1154
1120
  }
1155
1121
 
1156
- declare const VERSION = "0.12.0";
1122
+ declare const VERSION = "0.11.0";
1157
1123
 
1158
- export { type ActiveFormats, AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, CreateLayerCommand, DEFAULT_FONT_SIZE_PRESETS, DEFAULT_NOTE_FONT_SIZE, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, type ExportImageOptions, type FilterAction, type FilteredEvent, type FilteredUpEvent, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputFilter, InputHandler, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, NoteEditor, type NoteEditorOptions, type NoteElement, NoteTool, type NoteToolOptions, NoteToolbar, PencilTool, type PencilToolOptions, type Point, type PointerState, Quadtree, RemoveElementCommand, RemoveLayerCommand, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type Size, type StrokeElement, type StrokePoint, type StyledRun, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, UpdateLayerCommand, VERSION, Viewport, type ViewportOptions, boundsIntersect, clearStaleBindings, createArrow, createGrid, createHtmlElement, createId, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportState, findBindTarget, findBoundArrows, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getEdgeIntersection, getElementBounds, getElementCenter, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isBindable, isNearBezier, parseState, sanitizeNoteHtml, setFontSize, smartSnap, snapPoint, snapToHexCenter, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline, unbindArrow, updateBoundArrow };
1124
+ export { type ActiveFormats, AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, CreateLayerCommand, DEFAULT_FONT_SIZE_PRESETS, DEFAULT_NOTE_FONT_SIZE, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, type ExportImageOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputHandler, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, NoteEditor, type NoteEditorOptions, type NoteElement, NoteTool, type NoteToolOptions, NoteToolbar, PencilTool, type PencilToolOptions, type Point, type PointerState, Quadtree, RemoveElementCommand, RemoveLayerCommand, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type Size, type StrokeElement, type StrokePoint, type StyledRun, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, UpdateLayerCommand, VERSION, Viewport, type ViewportOptions, boundsIntersect, clearStaleBindings, createArrow, createGrid, createHtmlElement, createId, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportState, findBindTarget, findBoundArrows, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getEdgeIntersection, getElementBounds, getElementCenter, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isBindable, isNearBezier, parseState, sanitizeNoteHtml, setFontSize, smartSnap, snapPoint, snapToHexCenter, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline, unbindArrow, updateBoundArrow };
package/dist/index.d.ts CHANGED
@@ -200,7 +200,6 @@ declare class ElementStore {
200
200
  private bus;
201
201
  private layerOrderMap;
202
202
  private spatialIndex;
203
- private sortedCache;
204
203
  get count(): number;
205
204
  setLayerOrder(order: Map<string, number>): void;
206
205
  getAll(): CanvasElement[];
@@ -239,8 +238,6 @@ interface PointerState {
239
238
  x: number;
240
239
  y: number;
241
240
  pressure: number;
242
- pointerType: 'mouse' | 'touch' | 'pen';
243
- shiftKey: boolean;
244
241
  }
245
242
  interface Tool {
246
243
  readonly name: string;
@@ -426,12 +423,7 @@ declare class InputHandler {
426
423
  private historyRecorder;
427
424
  private historyStack;
428
425
  private isToolActive;
429
- private lastPointerEvent;
430
- private readonly inputFilter;
431
- private deferredDown;
432
426
  private readonly abortController;
433
- private clipboard;
434
- private pasteCount;
435
427
  constructor(element: HTMLElement, camera: Camera, options?: InputHandlerOptions);
436
428
  setToolManager(toolManager: ToolManager, toolContext: ToolContext): void;
437
429
  destroy(): void;
@@ -455,32 +447,9 @@ declare class InputHandler {
455
447
  private deleteSelected;
456
448
  private handleUndo;
457
449
  private handleRedo;
458
- private handleCopy;
459
- private handlePaste;
460
450
  private cancelToolIfActive;
461
451
  }
462
452
 
463
- type FilterAction = 'dispatch' | 'suppress' | 'defer';
464
- interface FilteredEvent {
465
- event: PointerEvent;
466
- action: FilterAction;
467
- }
468
- interface FilteredUpEvent extends FilteredEvent {
469
- pendingTap?: {
470
- x: number;
471
- y: number;
472
- };
473
- }
474
- declare class InputFilter {
475
- private activePenId;
476
- private pendingTap;
477
- static readonly MIN_MOVE_DISTANCE = 3;
478
- filterDown(e: PointerEvent): FilteredEvent;
479
- filterMove(e: PointerEvent): FilteredEvent;
480
- filterUp(e: PointerEvent): FilteredUpEvent;
481
- reset(): void;
482
- }
483
-
484
453
  interface FontSizePreset {
485
454
  label: string;
486
455
  size: number;
@@ -905,10 +874,7 @@ declare class SelectTool implements Tool {
905
874
  private lastWorld;
906
875
  private currentWorld;
907
876
  private ctx;
908
- private pendingSingleSelectId;
909
- private hasDragged;
910
877
  get selectedIds(): string[];
911
- setSelection(ids: string[]): void;
912
878
  get isMarqueeActive(): boolean;
913
879
  onActivate(ctx: ToolContext): void;
914
880
  onDeactivate(ctx: ToolContext): void;
@@ -1153,6 +1119,6 @@ declare class UpdateLayerCommand implements Command {
1153
1119
  undo(_store: ElementStore): void;
1154
1120
  }
1155
1121
 
1156
- declare const VERSION = "0.12.0";
1122
+ declare const VERSION = "0.11.0";
1157
1123
 
1158
- export { type ActiveFormats, AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, CreateLayerCommand, DEFAULT_FONT_SIZE_PRESETS, DEFAULT_NOTE_FONT_SIZE, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, type ExportImageOptions, type FilterAction, type FilteredEvent, type FilteredUpEvent, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputFilter, InputHandler, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, NoteEditor, type NoteEditorOptions, type NoteElement, NoteTool, type NoteToolOptions, NoteToolbar, PencilTool, type PencilToolOptions, type Point, type PointerState, Quadtree, RemoveElementCommand, RemoveLayerCommand, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type Size, type StrokeElement, type StrokePoint, type StyledRun, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, UpdateLayerCommand, VERSION, Viewport, type ViewportOptions, boundsIntersect, clearStaleBindings, createArrow, createGrid, createHtmlElement, createId, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportState, findBindTarget, findBoundArrows, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getEdgeIntersection, getElementBounds, getElementCenter, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isBindable, isNearBezier, parseState, sanitizeNoteHtml, setFontSize, smartSnap, snapPoint, snapToHexCenter, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline, unbindArrow, updateBoundArrow };
1124
+ export { type ActiveFormats, AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, CreateLayerCommand, DEFAULT_FONT_SIZE_PRESETS, DEFAULT_NOTE_FONT_SIZE, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, type ExportImageOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputHandler, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, NoteEditor, type NoteEditorOptions, type NoteElement, NoteTool, type NoteToolOptions, NoteToolbar, PencilTool, type PencilToolOptions, type Point, type PointerState, Quadtree, RemoveElementCommand, RemoveLayerCommand, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type Size, type StrokeElement, type StrokePoint, type StyledRun, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, UpdateLayerCommand, VERSION, Viewport, type ViewportOptions, boundsIntersect, clearStaleBindings, createArrow, createGrid, createHtmlElement, createId, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportState, findBindTarget, findBoundArrows, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getEdgeIntersection, getElementBounds, getElementCenter, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isBindable, isNearBezier, parseState, sanitizeNoteHtml, setFontSize, smartSnap, snapPoint, snapToHexCenter, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline, unbindArrow, updateBoundArrow };
package/dist/index.js CHANGED
@@ -292,13 +292,7 @@ function exportState(elements, camera, layers = []) {
292
292
  position: { ...camera.position },
293
293
  zoom: camera.zoom
294
294
  },
295
- elements: elements.map((el) => {
296
- const clone = structuredClone(el);
297
- if (clone.type === "arrow") {
298
- delete clone.cachedControlPoint;
299
- }
300
- return clone;
301
- }),
295
+ elements: elements.map((el) => structuredClone(el)),
302
296
  layers: layers.map((l) => ({ ...l }))
303
297
  };
304
298
  }
@@ -736,67 +730,6 @@ var Background = class {
736
730
  }
737
731
  };
738
732
 
739
- // src/canvas/input-filter.ts
740
- var InputFilter = class _InputFilter {
741
- activePenId = null;
742
- pendingTap = null;
743
- static MIN_MOVE_DISTANCE = 3;
744
- filterDown(e) {
745
- if (e.pointerType === "pen") {
746
- this.activePenId = e.pointerId;
747
- return { event: e, action: "dispatch" };
748
- }
749
- if (e.pointerType === "touch" && this.activePenId !== null) {
750
- return { event: e, action: "suppress" };
751
- }
752
- if (e.pointerType === "touch") {
753
- this.pendingTap = { pointerId: e.pointerId, x: e.clientX, y: e.clientY };
754
- return { event: e, action: "defer" };
755
- }
756
- return { event: e, action: "dispatch" };
757
- }
758
- filterMove(e) {
759
- if (e.pointerType === "touch" && this.activePenId !== null) {
760
- return { event: e, action: "suppress" };
761
- }
762
- if (this.pendingTap && e.pointerId === this.pendingTap.pointerId) {
763
- const dx = e.clientX - this.pendingTap.x;
764
- const dy = e.clientY - this.pendingTap.y;
765
- if (dx * dx + dy * dy > _InputFilter.MIN_MOVE_DISTANCE * _InputFilter.MIN_MOVE_DISTANCE) {
766
- this.pendingTap = null;
767
- return { event: e, action: "dispatch" };
768
- }
769
- return { event: e, action: "suppress" };
770
- }
771
- return { event: e, action: "dispatch" };
772
- }
773
- filterUp(e) {
774
- if (e.pointerId === this.activePenId) {
775
- this.activePenId = null;
776
- return { event: e, action: "dispatch" };
777
- }
778
- if (e.pointerType === "touch" && this.activePenId !== null) {
779
- return { event: e, action: "suppress" };
780
- }
781
- if (this.pendingTap && e.pointerId === this.pendingTap.pointerId) {
782
- const tap = { x: this.pendingTap.x, y: this.pendingTap.y };
783
- this.pendingTap = null;
784
- return { event: e, action: "dispatch", pendingTap: tap };
785
- }
786
- return { event: e, action: "dispatch" };
787
- }
788
- reset() {
789
- this.activePenId = null;
790
- this.pendingTap = null;
791
- }
792
- };
793
-
794
- // src/elements/create-id.ts
795
- var counter = 0;
796
- function createId(prefix) {
797
- return `${prefix}_${Date.now().toString(36)}_${(counter++).toString(36)}`;
798
- }
799
-
800
733
  // src/canvas/input-handler.ts
801
734
  var ZOOM_SENSITIVITY = 1e-3;
802
735
  var MIDDLE_BUTTON = 1;
@@ -822,21 +755,13 @@ var InputHandler = class {
822
755
  historyRecorder;
823
756
  historyStack;
824
757
  isToolActive = false;
825
- lastPointerEvent = null;
826
- inputFilter = new InputFilter();
827
- deferredDown = null;
828
758
  abortController = new AbortController();
829
- clipboard = [];
830
- pasteCount = 0;
831
759
  setToolManager(toolManager, toolContext) {
832
760
  this.toolManager = toolManager;
833
761
  this.toolContext = toolContext;
834
762
  }
835
763
  destroy() {
836
764
  this.abortController.abort();
837
- this.inputFilter.reset();
838
- this.deferredDown = null;
839
- this.lastPointerEvent = null;
840
765
  }
841
766
  bind() {
842
767
  const opts = { signal: this.abortController.signal };
@@ -872,18 +797,11 @@ var InputHandler = class {
872
797
  this.lastPointer = { x: e.clientX, y: e.clientY };
873
798
  return;
874
799
  }
875
- if (this.activePointers.size === 1 && (e.button === 0 || e.pointerType === "touch" || e.pointerType === "pen")) {
876
- const result = this.inputFilter.filterDown(e);
877
- if (result.action === "suppress") return;
878
- if (result.action === "defer") {
879
- this.deferredDown = e;
880
- return;
881
- }
800
+ if (this.activePointers.size === 1 && e.button === 0) {
882
801
  this.dispatchToolDown(e);
883
802
  }
884
803
  };
885
804
  onPointerMove = (e) => {
886
- this.lastPointerEvent = e;
887
805
  if (this.activePointers.has(e.pointerId)) {
888
806
  this.activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
889
807
  }
@@ -900,22 +818,11 @@ var InputHandler = class {
900
818
  }
901
819
  if (this.isToolActive) {
902
820
  this.dispatchToolMove(e);
903
- } else if (this.deferredDown) {
904
- const result = this.inputFilter.filterMove(e);
905
- if (result.action === "dispatch") {
906
- this.dispatchToolDown(this.deferredDown);
907
- this.deferredDown = null;
908
- this.dispatchToolMove(e);
909
- }
910
821
  } else if (this.activePointers.size === 0) {
911
822
  this.dispatchToolHover(e);
912
823
  }
913
824
  };
914
825
  onPointerUp = (e) => {
915
- try {
916
- this.element.releasePointerCapture(e.pointerId);
917
- } catch {
918
- }
919
826
  this.activePointers.delete(e.pointerId);
920
827
  if (this.activePointers.size < 2) {
921
828
  this.lastPinchDistance = 0;
@@ -923,16 +830,9 @@ var InputHandler = class {
923
830
  if (this.isPanning && this.activePointers.size === 0) {
924
831
  this.isPanning = false;
925
832
  }
926
- const upResult = this.inputFilter.filterUp(e);
927
833
  if (this.isToolActive) {
928
834
  this.dispatchToolUp(e);
929
835
  this.isToolActive = false;
930
- } else if (this.deferredDown && upResult.pendingTap) {
931
- this.dispatchToolDown(this.deferredDown);
932
- this.dispatchToolUp(e);
933
- this.deferredDown = null;
934
- } else {
935
- this.deferredDown = null;
936
836
  }
937
837
  };
938
838
  onKeyDown = (e) => {
@@ -951,30 +851,13 @@ var InputHandler = class {
951
851
  e.preventDefault();
952
852
  this.handleRedo();
953
853
  }
954
- if ((e.ctrlKey || e.metaKey) && e.key === "c") {
955
- e.preventDefault();
956
- this.handleCopy();
957
- }
958
- if ((e.ctrlKey || e.metaKey) && e.key === "v") {
959
- e.preventDefault();
960
- this.handlePaste();
961
- }
962
854
  };
963
855
  onKeyUp = (e) => {
964
856
  if (e.key === " ") {
965
857
  this.spaceHeld = false;
966
- if (this.activePointers.size === 0) {
967
- if (this.lastPointerEvent) {
968
- this.dispatchToolHover(this.lastPointerEvent);
969
- } else {
970
- this.toolContext?.setCursor?.("default");
971
- }
972
- }
973
858
  }
974
859
  };
975
860
  startPinch() {
976
- this.inputFilter.reset();
977
- this.deferredDown = null;
978
861
  this.isPanning = true;
979
862
  const [a, b] = this.getPinchPoints();
980
863
  this.lastPinchDistance = this.distance(a, b);
@@ -1014,9 +897,7 @@ var InputHandler = class {
1014
897
  return {
1015
898
  x: e.clientX - rect.left,
1016
899
  y: e.clientY - rect.top,
1017
- pressure: e.pressure,
1018
- pointerType: e.pointerType === "touch" || e.pointerType === "pen" ? e.pointerType : "mouse",
1019
- shiftKey: e.shiftKey
900
+ pressure: e.pressure
1020
901
  };
1021
902
  }
1022
903
  dispatchToolDown(e) {
@@ -1069,78 +950,11 @@ var InputHandler = class {
1069
950
  this.historyRecorder?.resume();
1070
951
  this.toolContext.requestRender();
1071
952
  }
1072
- handleCopy() {
1073
- if (!this.toolManager || !this.toolContext || this.isToolActive) return;
1074
- const tool = this.toolManager.activeTool;
1075
- if (tool?.name !== "select") return;
1076
- const selectTool = tool;
1077
- const ids = selectTool.selectedIds;
1078
- if (ids.length === 0) return;
1079
- this.clipboard = [];
1080
- for (const id of ids) {
1081
- const el = this.toolContext.store.getById(id);
1082
- if (el) this.clipboard.push(structuredClone(el));
1083
- }
1084
- this.pasteCount = 0;
1085
- }
1086
- handlePaste() {
1087
- if (!this.toolManager || !this.toolContext || this.clipboard.length === 0 || this.isToolActive)
1088
- return;
1089
- const tool = this.toolManager.activeTool;
1090
- if (tool?.name !== "select") return;
1091
- const selectTool = tool;
1092
- this.pasteCount++;
1093
- const offset = this.pasteCount * 20;
1094
- const idMap = /* @__PURE__ */ new Map();
1095
- for (const el of this.clipboard) {
1096
- idMap.set(el.id, createId(el.type));
1097
- }
1098
- const newIds = [];
1099
- this.historyRecorder?.begin();
1100
- for (const el of this.clipboard) {
1101
- const clone = structuredClone(el);
1102
- const newId = idMap.get(el.id);
1103
- if (!newId) continue;
1104
- clone.id = newId;
1105
- clone.position = { x: clone.position.x + offset, y: clone.position.y + offset };
1106
- if (clone.type === "arrow") {
1107
- const arrow = clone;
1108
- arrow.from = { x: arrow.from.x + offset, y: arrow.from.y + offset };
1109
- arrow.to = { x: arrow.to.x + offset, y: arrow.to.y + offset };
1110
- delete arrow.cachedControlPoint;
1111
- if (arrow.fromBinding) {
1112
- const newTarget = idMap.get(arrow.fromBinding.elementId);
1113
- if (newTarget) {
1114
- arrow.fromBinding = { elementId: newTarget };
1115
- } else {
1116
- delete arrow.fromBinding;
1117
- }
1118
- }
1119
- if (arrow.toBinding) {
1120
- const newTarget = idMap.get(arrow.toBinding.elementId);
1121
- if (newTarget) {
1122
- arrow.toBinding = { elementId: newTarget };
1123
- } else {
1124
- delete arrow.toBinding;
1125
- }
1126
- }
1127
- }
1128
- if (this.toolContext.activeLayerId) {
1129
- clone.layerId = this.toolContext.activeLayerId;
1130
- }
1131
- this.toolContext.store.add(clone);
1132
- newIds.push(clone.id);
1133
- }
1134
- this.historyRecorder?.commit();
1135
- selectTool.setSelection(newIds);
1136
- this.toolContext.requestRender();
1137
- }
1138
953
  cancelToolIfActive(e) {
1139
954
  if (this.isToolActive) {
1140
955
  this.dispatchToolUp(e);
1141
956
  this.isToolActive = false;
1142
957
  }
1143
- this.deferredDown = null;
1144
958
  }
1145
959
  };
1146
960
 
@@ -1388,23 +1202,19 @@ var ElementStore = class {
1388
1202
  bus = new EventBus();
1389
1203
  layerOrderMap = /* @__PURE__ */ new Map();
1390
1204
  spatialIndex = new Quadtree({ x: -1e5, y: -1e5, w: 2e5, h: 2e5 });
1391
- sortedCache = null;
1392
1205
  get count() {
1393
1206
  return this.elements.size;
1394
1207
  }
1395
1208
  setLayerOrder(order) {
1396
1209
  this.layerOrderMap = new Map(order);
1397
- this.sortedCache = null;
1398
1210
  }
1399
1211
  getAll() {
1400
- if (this.sortedCache) return this.sortedCache;
1401
- this.sortedCache = [...this.elements.values()].sort((a, b) => {
1212
+ return [...this.elements.values()].sort((a, b) => {
1402
1213
  const layerA = this.layerOrderMap.get(a.layerId) ?? 0;
1403
1214
  const layerB = this.layerOrderMap.get(b.layerId) ?? 0;
1404
1215
  if (layerA !== layerB) return layerA - layerB;
1405
1216
  return a.zIndex - b.zIndex;
1406
1217
  });
1407
- return this.sortedCache;
1408
1218
  }
1409
1219
  getById(id) {
1410
1220
  return this.elements.get(id);
@@ -1415,7 +1225,6 @@ var ElementStore = class {
1415
1225
  );
1416
1226
  }
1417
1227
  add(element) {
1418
- this.sortedCache = null;
1419
1228
  this.elements.set(element.id, element);
1420
1229
  const bounds = getElementBounds(element);
1421
1230
  if (bounds) this.spatialIndex.insert(element.id, bounds);
@@ -1424,15 +1233,11 @@ var ElementStore = class {
1424
1233
  update(id, partial) {
1425
1234
  const existing = this.elements.get(id);
1426
1235
  if (!existing) return;
1427
- this.sortedCache = null;
1428
1236
  const updated = { ...existing, ...partial, id: existing.id, type: existing.type };
1429
1237
  if (updated.type === "arrow") {
1430
1238
  const arrow = updated;
1431
1239
  arrow.cachedControlPoint = getArrowControlPoint(arrow.from, arrow.to, arrow.bend);
1432
1240
  }
1433
- if (updated.type === "note" && "text" in partial) {
1434
- updated.text = sanitizeNoteHtml(updated.text);
1435
- }
1436
1241
  this.elements.set(id, updated);
1437
1242
  const newBounds = getElementBounds(updated);
1438
1243
  if (newBounds) {
@@ -1443,13 +1248,11 @@ var ElementStore = class {
1443
1248
  remove(id) {
1444
1249
  const element = this.elements.get(id);
1445
1250
  if (!element) return;
1446
- this.sortedCache = null;
1447
1251
  this.elements.delete(id);
1448
1252
  this.spatialIndex.remove(id);
1449
1253
  this.bus.emit("remove", element);
1450
1254
  }
1451
1255
  clear() {
1452
- this.sortedCache = null;
1453
1256
  this.elements.clear();
1454
1257
  this.spatialIndex.clear();
1455
1258
  this.bus.emit("clear", null);
@@ -1458,7 +1261,6 @@ var ElementStore = class {
1458
1261
  return this.getAll().map((el) => ({ ...el }));
1459
1262
  }
1460
1263
  loadSnapshot(elements) {
1461
- this.sortedCache = null;
1462
1264
  this.elements.clear();
1463
1265
  this.spatialIndex.clear();
1464
1266
  for (const el of elements) {
@@ -1466,10 +1268,6 @@ var ElementStore = class {
1466
1268
  const bounds = getElementBounds(el);
1467
1269
  if (bounds) this.spatialIndex.insert(el.id, bounds);
1468
1270
  }
1469
- this.bus.emit("clear", null);
1470
- for (const el of elements) {
1471
- this.bus.emit("add", el);
1472
- }
1473
1271
  }
1474
1272
  queryRect(rect) {
1475
1273
  const ids = this.spatialIndex.query(rect);
@@ -2519,6 +2317,12 @@ var ElementRenderer = class {
2519
2317
  }
2520
2318
  };
2521
2319
 
2320
+ // src/elements/create-id.ts
2321
+ var counter = 0;
2322
+ function createId(prefix) {
2323
+ return `${prefix}_${Date.now().toString(36)}_${(counter++).toString(36)}`;
2324
+ }
2325
+
2522
2326
  // src/elements/element-factory.ts
2523
2327
  var DEFAULT_NOTE_FONT_SIZE = 18;
2524
2328
  function createStroke(input) {
@@ -2544,7 +2348,7 @@ function createNote(input) {
2544
2348
  locked: input.locked ?? false,
2545
2349
  layerId: input.layerId ?? "",
2546
2350
  size: input.size ?? { w: 200, h: 100 },
2547
- text: sanitizeNoteHtml(input.text ?? ""),
2351
+ text: input.text ?? "",
2548
2352
  backgroundColor: input.backgroundColor ?? "#ffeb3b",
2549
2353
  textColor: input.textColor ?? "#000000",
2550
2354
  fontSize: input.fontSize ?? DEFAULT_NOTE_FONT_SIZE
@@ -4651,10 +4455,7 @@ var Viewport = class {
4651
4455
  position: "relative",
4652
4456
  width: "100%",
4653
4457
  height: "100%",
4654
- overflow: "hidden",
4655
- overscrollBehavior: "none",
4656
- userSelect: "none",
4657
- webkitUserSelect: "none"
4458
+ overflow: "hidden"
4658
4459
  });
4659
4460
  return el;
4660
4461
  }
@@ -5069,15 +4870,9 @@ var SelectTool = class {
5069
4870
  lastWorld = { x: 0, y: 0 };
5070
4871
  currentWorld = { x: 0, y: 0 };
5071
4872
  ctx = null;
5072
- pendingSingleSelectId = null;
5073
- hasDragged = false;
5074
4873
  get selectedIds() {
5075
4874
  return [...this._selectedIds];
5076
4875
  }
5077
- setSelection(ids) {
5078
- this._selectedIds = ids;
5079
- this.ctx?.requestRender();
5080
- }
5081
4876
  get isMarqueeActive() {
5082
4877
  return this.mode.type === "marquee";
5083
4878
  }
@@ -5126,27 +4921,13 @@ var SelectTool = class {
5126
4921
  return;
5127
4922
  }
5128
4923
  }
5129
- this.pendingSingleSelectId = null;
5130
- this.hasDragged = false;
5131
4924
  const hit = this.hitTest(world, ctx);
5132
4925
  if (hit) {
5133
4926
  const alreadySelected = this._selectedIds.includes(hit.id);
5134
- if (state.shiftKey) {
5135
- if (alreadySelected) {
5136
- this._selectedIds = this._selectedIds.filter((id) => id !== hit.id);
5137
- this.mode = { type: "idle" };
5138
- } else {
5139
- this._selectedIds = [...this._selectedIds, hit.id];
5140
- this.mode = hit.locked ? { type: "idle" } : { type: "dragging" };
5141
- }
5142
- } else {
5143
- if (!alreadySelected) {
5144
- this._selectedIds = [hit.id];
5145
- } else if (this._selectedIds.length > 1) {
5146
- this.pendingSingleSelectId = hit.id;
5147
- }
5148
- this.mode = hit.locked ? { type: "idle" } : { type: "dragging" };
4927
+ if (!alreadySelected) {
4928
+ this._selectedIds = [hit.id];
5149
4929
  }
4930
+ this.mode = hit.locked ? { type: "idle" } : { type: "dragging" };
5150
4931
  } else {
5151
4932
  this._selectedIds = [];
5152
4933
  this.mode = { type: "marquee", start: world };
@@ -5172,7 +4953,6 @@ var SelectTool = class {
5172
4953
  return;
5173
4954
  }
5174
4955
  if (this.mode.type === "dragging" && this._selectedIds.length > 0) {
5175
- this.hasDragged = true;
5176
4956
  ctx.setCursor?.("move");
5177
4957
  const snapped = this.snap(world, ctx);
5178
4958
  const dx = snapped.x - this.lastWorld.x;
@@ -5241,11 +5021,6 @@ var SelectTool = class {
5241
5021
  }
5242
5022
  ctx.requestRender();
5243
5023
  }
5244
- if (!this.hasDragged && this.pendingSingleSelectId !== null) {
5245
- this._selectedIds = [this.pendingSingleSelectId];
5246
- }
5247
- this.pendingSingleSelectId = null;
5248
- this.hasDragged = false;
5249
5024
  this.mode = { type: "idle" };
5250
5025
  ctx.setCursor?.("default");
5251
5026
  }
@@ -6426,7 +6201,7 @@ var UpdateLayerCommand = class {
6426
6201
  };
6427
6202
 
6428
6203
  // src/index.ts
6429
- var VERSION = "0.12.0";
6204
+ var VERSION = "0.11.0";
6430
6205
  export {
6431
6206
  AddElementCommand,
6432
6207
  ArrowTool,
@@ -6445,7 +6220,6 @@ export {
6445
6220
  HistoryRecorder,
6446
6221
  HistoryStack,
6447
6222
  ImageTool,
6448
- InputFilter,
6449
6223
  InputHandler,
6450
6224
  LayerManager,
6451
6225
  MeasureTool,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fieldnotes/core",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Vanilla TypeScript infinite canvas engine",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",