@fieldnotes/core 0.11.1 → 0.11.3

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,6 +37,7 @@ __export(index_exports, {
37
37
  HistoryRecorder: () => HistoryRecorder,
38
38
  HistoryStack: () => HistoryStack,
39
39
  ImageTool: () => ImageTool,
40
+ InputFilter: () => InputFilter,
40
41
  InputHandler: () => InputHandler,
41
42
  LayerManager: () => LayerManager,
42
43
  MeasureTool: () => MeasureTool,
@@ -842,6 +843,61 @@ var Background = class {
842
843
  }
843
844
  };
844
845
 
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
+
845
901
  // src/canvas/input-handler.ts
846
902
  var ZOOM_SENSITIVITY = 1e-3;
847
903
  var MIDDLE_BUTTON = 1;
@@ -868,6 +924,8 @@ var InputHandler = class {
868
924
  historyStack;
869
925
  isToolActive = false;
870
926
  lastPointerEvent = null;
927
+ inputFilter = new InputFilter();
928
+ deferredDown = null;
871
929
  abortController = new AbortController();
872
930
  setToolManager(toolManager, toolContext) {
873
931
  this.toolManager = toolManager;
@@ -875,6 +933,8 @@ var InputHandler = class {
875
933
  }
876
934
  destroy() {
877
935
  this.abortController.abort();
936
+ this.inputFilter.reset();
937
+ this.deferredDown = null;
878
938
  this.lastPointerEvent = null;
879
939
  }
880
940
  bind() {
@@ -912,6 +972,12 @@ var InputHandler = class {
912
972
  return;
913
973
  }
914
974
  if (this.activePointers.size === 1 && (e.button === 0 || e.pointerType === "touch" || e.pointerType === "pen")) {
975
+ const result = this.inputFilter.filterDown(e);
976
+ if (result.action === "suppress") return;
977
+ if (result.action === "defer") {
978
+ this.deferredDown = e;
979
+ return;
980
+ }
915
981
  this.dispatchToolDown(e);
916
982
  }
917
983
  };
@@ -933,6 +999,13 @@ var InputHandler = class {
933
999
  }
934
1000
  if (this.isToolActive) {
935
1001
  this.dispatchToolMove(e);
1002
+ } else if (this.deferredDown) {
1003
+ const result = this.inputFilter.filterMove(e);
1004
+ if (result.action === "dispatch") {
1005
+ this.dispatchToolDown(this.deferredDown);
1006
+ this.deferredDown = null;
1007
+ this.dispatchToolMove(e);
1008
+ }
936
1009
  } else if (this.activePointers.size === 0) {
937
1010
  this.dispatchToolHover(e);
938
1011
  }
@@ -949,9 +1022,16 @@ var InputHandler = class {
949
1022
  if (this.isPanning && this.activePointers.size === 0) {
950
1023
  this.isPanning = false;
951
1024
  }
1025
+ const upResult = this.inputFilter.filterUp(e);
952
1026
  if (this.isToolActive) {
953
1027
  this.dispatchToolUp(e);
954
1028
  this.isToolActive = false;
1029
+ } else if (this.deferredDown && upResult.pendingTap) {
1030
+ this.dispatchToolDown(this.deferredDown);
1031
+ this.dispatchToolUp(e);
1032
+ this.deferredDown = null;
1033
+ } else {
1034
+ this.deferredDown = null;
955
1035
  }
956
1036
  };
957
1037
  onKeyDown = (e) => {
@@ -984,6 +1064,8 @@ var InputHandler = class {
984
1064
  }
985
1065
  };
986
1066
  startPinch() {
1067
+ this.inputFilter.reset();
1068
+ this.deferredDown = null;
987
1069
  this.isPanning = true;
988
1070
  const [a, b] = this.getPinchPoints();
989
1071
  this.lastPinchDistance = this.distance(a, b);
@@ -1023,7 +1105,8 @@ var InputHandler = class {
1023
1105
  return {
1024
1106
  x: e.clientX - rect.left,
1025
1107
  y: e.clientY - rect.top,
1026
- pressure: e.pressure
1108
+ pressure: e.pressure,
1109
+ pointerType: e.pointerType === "touch" || e.pointerType === "pen" ? e.pointerType : "mouse"
1027
1110
  };
1028
1111
  }
1029
1112
  dispatchToolDown(e) {
@@ -1081,6 +1164,7 @@ var InputHandler = class {
1081
1164
  this.dispatchToolUp(e);
1082
1165
  this.isToolActive = false;
1083
1166
  }
1167
+ this.deferredDown = null;
1084
1168
  }
1085
1169
  };
1086
1170
 
@@ -1328,19 +1412,23 @@ var ElementStore = class {
1328
1412
  bus = new EventBus();
1329
1413
  layerOrderMap = /* @__PURE__ */ new Map();
1330
1414
  spatialIndex = new Quadtree({ x: -1e5, y: -1e5, w: 2e5, h: 2e5 });
1415
+ sortedCache = null;
1331
1416
  get count() {
1332
1417
  return this.elements.size;
1333
1418
  }
1334
1419
  setLayerOrder(order) {
1335
1420
  this.layerOrderMap = new Map(order);
1421
+ this.sortedCache = null;
1336
1422
  }
1337
1423
  getAll() {
1338
- return [...this.elements.values()].sort((a, b) => {
1424
+ if (this.sortedCache) return this.sortedCache;
1425
+ this.sortedCache = [...this.elements.values()].sort((a, b) => {
1339
1426
  const layerA = this.layerOrderMap.get(a.layerId) ?? 0;
1340
1427
  const layerB = this.layerOrderMap.get(b.layerId) ?? 0;
1341
1428
  if (layerA !== layerB) return layerA - layerB;
1342
1429
  return a.zIndex - b.zIndex;
1343
1430
  });
1431
+ return this.sortedCache;
1344
1432
  }
1345
1433
  getById(id) {
1346
1434
  return this.elements.get(id);
@@ -1351,6 +1439,7 @@ var ElementStore = class {
1351
1439
  );
1352
1440
  }
1353
1441
  add(element) {
1442
+ this.sortedCache = null;
1354
1443
  this.elements.set(element.id, element);
1355
1444
  const bounds = getElementBounds(element);
1356
1445
  if (bounds) this.spatialIndex.insert(element.id, bounds);
@@ -1359,11 +1448,15 @@ var ElementStore = class {
1359
1448
  update(id, partial) {
1360
1449
  const existing = this.elements.get(id);
1361
1450
  if (!existing) return;
1451
+ this.sortedCache = null;
1362
1452
  const updated = { ...existing, ...partial, id: existing.id, type: existing.type };
1363
1453
  if (updated.type === "arrow") {
1364
1454
  const arrow = updated;
1365
1455
  arrow.cachedControlPoint = getArrowControlPoint(arrow.from, arrow.to, arrow.bend);
1366
1456
  }
1457
+ if (updated.type === "note" && "text" in partial) {
1458
+ updated.text = sanitizeNoteHtml(updated.text);
1459
+ }
1367
1460
  this.elements.set(id, updated);
1368
1461
  const newBounds = getElementBounds(updated);
1369
1462
  if (newBounds) {
@@ -1374,11 +1467,13 @@ var ElementStore = class {
1374
1467
  remove(id) {
1375
1468
  const element = this.elements.get(id);
1376
1469
  if (!element) return;
1470
+ this.sortedCache = null;
1377
1471
  this.elements.delete(id);
1378
1472
  this.spatialIndex.remove(id);
1379
1473
  this.bus.emit("remove", element);
1380
1474
  }
1381
1475
  clear() {
1476
+ this.sortedCache = null;
1382
1477
  this.elements.clear();
1383
1478
  this.spatialIndex.clear();
1384
1479
  this.bus.emit("clear", null);
@@ -1387,6 +1482,7 @@ var ElementStore = class {
1387
1482
  return this.getAll().map((el) => ({ ...el }));
1388
1483
  }
1389
1484
  loadSnapshot(elements) {
1485
+ this.sortedCache = null;
1390
1486
  this.elements.clear();
1391
1487
  this.spatialIndex.clear();
1392
1488
  for (const el of elements) {
@@ -1394,6 +1490,10 @@ var ElementStore = class {
1394
1490
  const bounds = getElementBounds(el);
1395
1491
  if (bounds) this.spatialIndex.insert(el.id, bounds);
1396
1492
  }
1493
+ this.bus.emit("clear", null);
1494
+ for (const el of elements) {
1495
+ this.bus.emit("add", el);
1496
+ }
1397
1497
  }
1398
1498
  queryRect(rect) {
1399
1499
  const ids = this.spatialIndex.query(rect);
@@ -2474,7 +2574,7 @@ function createNote(input) {
2474
2574
  locked: input.locked ?? false,
2475
2575
  layerId: input.layerId ?? "",
2476
2576
  size: input.size ?? { w: 200, h: 100 },
2477
- text: input.text ?? "",
2577
+ text: sanitizeNoteHtml(input.text ?? ""),
2478
2578
  backgroundColor: input.backgroundColor ?? "#ffeb3b",
2479
2579
  textColor: input.textColor ?? "#000000",
2480
2580
  fontSize: input.fontSize ?? DEFAULT_NOTE_FONT_SIZE
@@ -6330,7 +6430,7 @@ var UpdateLayerCommand = class {
6330
6430
  };
6331
6431
 
6332
6432
  // src/index.ts
6333
- var VERSION = "0.11.0";
6433
+ var VERSION = "0.11.3";
6334
6434
  // Annotate the CommonJS export names for ESM import in node:
6335
6435
  0 && (module.exports = {
6336
6436
  AddElementCommand,
@@ -6350,6 +6450,7 @@ var VERSION = "0.11.0";
6350
6450
  HistoryRecorder,
6351
6451
  HistoryStack,
6352
6452
  ImageTool,
6453
+ InputFilter,
6353
6454
  InputHandler,
6354
6455
  LayerManager,
6355
6456
  MeasureTool,
package/dist/index.d.cts CHANGED
@@ -200,6 +200,7 @@ declare class ElementStore {
200
200
  private bus;
201
201
  private layerOrderMap;
202
202
  private spatialIndex;
203
+ private sortedCache;
203
204
  get count(): number;
204
205
  setLayerOrder(order: Map<string, number>): void;
205
206
  getAll(): CanvasElement[];
@@ -238,6 +239,7 @@ interface PointerState {
238
239
  x: number;
239
240
  y: number;
240
241
  pressure: number;
242
+ pointerType: 'mouse' | 'touch' | 'pen';
241
243
  }
242
244
  interface Tool {
243
245
  readonly name: string;
@@ -424,6 +426,8 @@ declare class InputHandler {
424
426
  private historyStack;
425
427
  private isToolActive;
426
428
  private lastPointerEvent;
429
+ private readonly inputFilter;
430
+ private deferredDown;
427
431
  private readonly abortController;
428
432
  constructor(element: HTMLElement, camera: Camera, options?: InputHandlerOptions);
429
433
  setToolManager(toolManager: ToolManager, toolContext: ToolContext): void;
@@ -451,6 +455,27 @@ declare class InputHandler {
451
455
  private cancelToolIfActive;
452
456
  }
453
457
 
458
+ type FilterAction = 'dispatch' | 'suppress' | 'defer';
459
+ interface FilteredEvent {
460
+ event: PointerEvent;
461
+ action: FilterAction;
462
+ }
463
+ interface FilteredUpEvent extends FilteredEvent {
464
+ pendingTap?: {
465
+ x: number;
466
+ y: number;
467
+ };
468
+ }
469
+ declare class InputFilter {
470
+ private activePenId;
471
+ private pendingTap;
472
+ static readonly MIN_MOVE_DISTANCE = 3;
473
+ filterDown(e: PointerEvent): FilteredEvent;
474
+ filterMove(e: PointerEvent): FilteredEvent;
475
+ filterUp(e: PointerEvent): FilteredUpEvent;
476
+ reset(): void;
477
+ }
478
+
454
479
  interface FontSizePreset {
455
480
  label: string;
456
481
  size: number;
@@ -1120,6 +1145,6 @@ declare class UpdateLayerCommand implements Command {
1120
1145
  undo(_store: ElementStore): void;
1121
1146
  }
1122
1147
 
1123
- declare const VERSION = "0.11.0";
1148
+ declare const VERSION = "0.11.3";
1124
1149
 
1125
- 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 };
1150
+ 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 };
package/dist/index.d.ts CHANGED
@@ -200,6 +200,7 @@ declare class ElementStore {
200
200
  private bus;
201
201
  private layerOrderMap;
202
202
  private spatialIndex;
203
+ private sortedCache;
203
204
  get count(): number;
204
205
  setLayerOrder(order: Map<string, number>): void;
205
206
  getAll(): CanvasElement[];
@@ -238,6 +239,7 @@ interface PointerState {
238
239
  x: number;
239
240
  y: number;
240
241
  pressure: number;
242
+ pointerType: 'mouse' | 'touch' | 'pen';
241
243
  }
242
244
  interface Tool {
243
245
  readonly name: string;
@@ -424,6 +426,8 @@ declare class InputHandler {
424
426
  private historyStack;
425
427
  private isToolActive;
426
428
  private lastPointerEvent;
429
+ private readonly inputFilter;
430
+ private deferredDown;
427
431
  private readonly abortController;
428
432
  constructor(element: HTMLElement, camera: Camera, options?: InputHandlerOptions);
429
433
  setToolManager(toolManager: ToolManager, toolContext: ToolContext): void;
@@ -451,6 +455,27 @@ declare class InputHandler {
451
455
  private cancelToolIfActive;
452
456
  }
453
457
 
458
+ type FilterAction = 'dispatch' | 'suppress' | 'defer';
459
+ interface FilteredEvent {
460
+ event: PointerEvent;
461
+ action: FilterAction;
462
+ }
463
+ interface FilteredUpEvent extends FilteredEvent {
464
+ pendingTap?: {
465
+ x: number;
466
+ y: number;
467
+ };
468
+ }
469
+ declare class InputFilter {
470
+ private activePenId;
471
+ private pendingTap;
472
+ static readonly MIN_MOVE_DISTANCE = 3;
473
+ filterDown(e: PointerEvent): FilteredEvent;
474
+ filterMove(e: PointerEvent): FilteredEvent;
475
+ filterUp(e: PointerEvent): FilteredUpEvent;
476
+ reset(): void;
477
+ }
478
+
454
479
  interface FontSizePreset {
455
480
  label: string;
456
481
  size: number;
@@ -1120,6 +1145,6 @@ declare class UpdateLayerCommand implements Command {
1120
1145
  undo(_store: ElementStore): void;
1121
1146
  }
1122
1147
 
1123
- declare const VERSION = "0.11.0";
1148
+ declare const VERSION = "0.11.3";
1124
1149
 
1125
- 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 };
1150
+ 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 };
package/dist/index.js CHANGED
@@ -736,6 +736,61 @@ var Background = class {
736
736
  }
737
737
  };
738
738
 
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
+
739
794
  // src/canvas/input-handler.ts
740
795
  var ZOOM_SENSITIVITY = 1e-3;
741
796
  var MIDDLE_BUTTON = 1;
@@ -762,6 +817,8 @@ var InputHandler = class {
762
817
  historyStack;
763
818
  isToolActive = false;
764
819
  lastPointerEvent = null;
820
+ inputFilter = new InputFilter();
821
+ deferredDown = null;
765
822
  abortController = new AbortController();
766
823
  setToolManager(toolManager, toolContext) {
767
824
  this.toolManager = toolManager;
@@ -769,6 +826,8 @@ var InputHandler = class {
769
826
  }
770
827
  destroy() {
771
828
  this.abortController.abort();
829
+ this.inputFilter.reset();
830
+ this.deferredDown = null;
772
831
  this.lastPointerEvent = null;
773
832
  }
774
833
  bind() {
@@ -806,6 +865,12 @@ var InputHandler = class {
806
865
  return;
807
866
  }
808
867
  if (this.activePointers.size === 1 && (e.button === 0 || e.pointerType === "touch" || e.pointerType === "pen")) {
868
+ const result = this.inputFilter.filterDown(e);
869
+ if (result.action === "suppress") return;
870
+ if (result.action === "defer") {
871
+ this.deferredDown = e;
872
+ return;
873
+ }
809
874
  this.dispatchToolDown(e);
810
875
  }
811
876
  };
@@ -827,6 +892,13 @@ var InputHandler = class {
827
892
  }
828
893
  if (this.isToolActive) {
829
894
  this.dispatchToolMove(e);
895
+ } else if (this.deferredDown) {
896
+ const result = this.inputFilter.filterMove(e);
897
+ if (result.action === "dispatch") {
898
+ this.dispatchToolDown(this.deferredDown);
899
+ this.deferredDown = null;
900
+ this.dispatchToolMove(e);
901
+ }
830
902
  } else if (this.activePointers.size === 0) {
831
903
  this.dispatchToolHover(e);
832
904
  }
@@ -843,9 +915,16 @@ var InputHandler = class {
843
915
  if (this.isPanning && this.activePointers.size === 0) {
844
916
  this.isPanning = false;
845
917
  }
918
+ const upResult = this.inputFilter.filterUp(e);
846
919
  if (this.isToolActive) {
847
920
  this.dispatchToolUp(e);
848
921
  this.isToolActive = false;
922
+ } else if (this.deferredDown && upResult.pendingTap) {
923
+ this.dispatchToolDown(this.deferredDown);
924
+ this.dispatchToolUp(e);
925
+ this.deferredDown = null;
926
+ } else {
927
+ this.deferredDown = null;
849
928
  }
850
929
  };
851
930
  onKeyDown = (e) => {
@@ -878,6 +957,8 @@ var InputHandler = class {
878
957
  }
879
958
  };
880
959
  startPinch() {
960
+ this.inputFilter.reset();
961
+ this.deferredDown = null;
881
962
  this.isPanning = true;
882
963
  const [a, b] = this.getPinchPoints();
883
964
  this.lastPinchDistance = this.distance(a, b);
@@ -917,7 +998,8 @@ var InputHandler = class {
917
998
  return {
918
999
  x: e.clientX - rect.left,
919
1000
  y: e.clientY - rect.top,
920
- pressure: e.pressure
1001
+ pressure: e.pressure,
1002
+ pointerType: e.pointerType === "touch" || e.pointerType === "pen" ? e.pointerType : "mouse"
921
1003
  };
922
1004
  }
923
1005
  dispatchToolDown(e) {
@@ -975,6 +1057,7 @@ var InputHandler = class {
975
1057
  this.dispatchToolUp(e);
976
1058
  this.isToolActive = false;
977
1059
  }
1060
+ this.deferredDown = null;
978
1061
  }
979
1062
  };
980
1063
 
@@ -1222,19 +1305,23 @@ var ElementStore = class {
1222
1305
  bus = new EventBus();
1223
1306
  layerOrderMap = /* @__PURE__ */ new Map();
1224
1307
  spatialIndex = new Quadtree({ x: -1e5, y: -1e5, w: 2e5, h: 2e5 });
1308
+ sortedCache = null;
1225
1309
  get count() {
1226
1310
  return this.elements.size;
1227
1311
  }
1228
1312
  setLayerOrder(order) {
1229
1313
  this.layerOrderMap = new Map(order);
1314
+ this.sortedCache = null;
1230
1315
  }
1231
1316
  getAll() {
1232
- return [...this.elements.values()].sort((a, b) => {
1317
+ if (this.sortedCache) return this.sortedCache;
1318
+ this.sortedCache = [...this.elements.values()].sort((a, b) => {
1233
1319
  const layerA = this.layerOrderMap.get(a.layerId) ?? 0;
1234
1320
  const layerB = this.layerOrderMap.get(b.layerId) ?? 0;
1235
1321
  if (layerA !== layerB) return layerA - layerB;
1236
1322
  return a.zIndex - b.zIndex;
1237
1323
  });
1324
+ return this.sortedCache;
1238
1325
  }
1239
1326
  getById(id) {
1240
1327
  return this.elements.get(id);
@@ -1245,6 +1332,7 @@ var ElementStore = class {
1245
1332
  );
1246
1333
  }
1247
1334
  add(element) {
1335
+ this.sortedCache = null;
1248
1336
  this.elements.set(element.id, element);
1249
1337
  const bounds = getElementBounds(element);
1250
1338
  if (bounds) this.spatialIndex.insert(element.id, bounds);
@@ -1253,11 +1341,15 @@ var ElementStore = class {
1253
1341
  update(id, partial) {
1254
1342
  const existing = this.elements.get(id);
1255
1343
  if (!existing) return;
1344
+ this.sortedCache = null;
1256
1345
  const updated = { ...existing, ...partial, id: existing.id, type: existing.type };
1257
1346
  if (updated.type === "arrow") {
1258
1347
  const arrow = updated;
1259
1348
  arrow.cachedControlPoint = getArrowControlPoint(arrow.from, arrow.to, arrow.bend);
1260
1349
  }
1350
+ if (updated.type === "note" && "text" in partial) {
1351
+ updated.text = sanitizeNoteHtml(updated.text);
1352
+ }
1261
1353
  this.elements.set(id, updated);
1262
1354
  const newBounds = getElementBounds(updated);
1263
1355
  if (newBounds) {
@@ -1268,11 +1360,13 @@ var ElementStore = class {
1268
1360
  remove(id) {
1269
1361
  const element = this.elements.get(id);
1270
1362
  if (!element) return;
1363
+ this.sortedCache = null;
1271
1364
  this.elements.delete(id);
1272
1365
  this.spatialIndex.remove(id);
1273
1366
  this.bus.emit("remove", element);
1274
1367
  }
1275
1368
  clear() {
1369
+ this.sortedCache = null;
1276
1370
  this.elements.clear();
1277
1371
  this.spatialIndex.clear();
1278
1372
  this.bus.emit("clear", null);
@@ -1281,6 +1375,7 @@ var ElementStore = class {
1281
1375
  return this.getAll().map((el) => ({ ...el }));
1282
1376
  }
1283
1377
  loadSnapshot(elements) {
1378
+ this.sortedCache = null;
1284
1379
  this.elements.clear();
1285
1380
  this.spatialIndex.clear();
1286
1381
  for (const el of elements) {
@@ -1288,6 +1383,10 @@ var ElementStore = class {
1288
1383
  const bounds = getElementBounds(el);
1289
1384
  if (bounds) this.spatialIndex.insert(el.id, bounds);
1290
1385
  }
1386
+ this.bus.emit("clear", null);
1387
+ for (const el of elements) {
1388
+ this.bus.emit("add", el);
1389
+ }
1291
1390
  }
1292
1391
  queryRect(rect) {
1293
1392
  const ids = this.spatialIndex.query(rect);
@@ -2368,7 +2467,7 @@ function createNote(input) {
2368
2467
  locked: input.locked ?? false,
2369
2468
  layerId: input.layerId ?? "",
2370
2469
  size: input.size ?? { w: 200, h: 100 },
2371
- text: input.text ?? "",
2470
+ text: sanitizeNoteHtml(input.text ?? ""),
2372
2471
  backgroundColor: input.backgroundColor ?? "#ffeb3b",
2373
2472
  textColor: input.textColor ?? "#000000",
2374
2473
  fontSize: input.fontSize ?? DEFAULT_NOTE_FONT_SIZE
@@ -6224,7 +6323,7 @@ var UpdateLayerCommand = class {
6224
6323
  };
6225
6324
 
6226
6325
  // src/index.ts
6227
- var VERSION = "0.11.0";
6326
+ var VERSION = "0.11.3";
6228
6327
  export {
6229
6328
  AddElementCommand,
6230
6329
  ArrowTool,
@@ -6243,6 +6342,7 @@ export {
6243
6342
  HistoryRecorder,
6244
6343
  HistoryStack,
6245
6344
  ImageTool,
6345
+ InputFilter,
6246
6346
  InputHandler,
6247
6347
  LayerManager,
6248
6348
  MeasureTool,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fieldnotes/core",
3
- "version": "0.11.1",
3
+ "version": "0.11.3",
4
4
  "description": "Vanilla TypeScript infinite canvas engine",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",