@fieldnotes/core 0.11.0 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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,
@@ -398,7 +399,13 @@ function exportState(elements, camera, layers = []) {
398
399
  position: { ...camera.position },
399
400
  zoom: camera.zoom
400
401
  },
401
- elements: elements.map((el) => structuredClone(el)),
402
+ elements: elements.map((el) => {
403
+ const clone = structuredClone(el);
404
+ if (clone.type === "arrow") {
405
+ delete clone.cachedControlPoint;
406
+ }
407
+ return clone;
408
+ }),
402
409
  layers: layers.map((l) => ({ ...l }))
403
410
  };
404
411
  }
@@ -836,6 +843,61 @@ var Background = class {
836
843
  }
837
844
  };
838
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
+
839
901
  // src/canvas/input-handler.ts
840
902
  var ZOOM_SENSITIVITY = 1e-3;
841
903
  var MIDDLE_BUTTON = 1;
@@ -861,6 +923,9 @@ var InputHandler = class {
861
923
  historyRecorder;
862
924
  historyStack;
863
925
  isToolActive = false;
926
+ lastPointerEvent = null;
927
+ inputFilter = new InputFilter();
928
+ deferredDown = null;
864
929
  abortController = new AbortController();
865
930
  setToolManager(toolManager, toolContext) {
866
931
  this.toolManager = toolManager;
@@ -868,6 +933,9 @@ var InputHandler = class {
868
933
  }
869
934
  destroy() {
870
935
  this.abortController.abort();
936
+ this.inputFilter.reset();
937
+ this.deferredDown = null;
938
+ this.lastPointerEvent = null;
871
939
  }
872
940
  bind() {
873
941
  const opts = { signal: this.abortController.signal };
@@ -903,11 +971,18 @@ var InputHandler = class {
903
971
  this.lastPointer = { x: e.clientX, y: e.clientY };
904
972
  return;
905
973
  }
906
- if (this.activePointers.size === 1 && e.button === 0) {
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
+ }
907
981
  this.dispatchToolDown(e);
908
982
  }
909
983
  };
910
984
  onPointerMove = (e) => {
985
+ this.lastPointerEvent = e;
911
986
  if (this.activePointers.has(e.pointerId)) {
912
987
  this.activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
913
988
  }
@@ -924,11 +999,22 @@ var InputHandler = class {
924
999
  }
925
1000
  if (this.isToolActive) {
926
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
+ }
927
1009
  } else if (this.activePointers.size === 0) {
928
1010
  this.dispatchToolHover(e);
929
1011
  }
930
1012
  };
931
1013
  onPointerUp = (e) => {
1014
+ try {
1015
+ this.element.releasePointerCapture(e.pointerId);
1016
+ } catch {
1017
+ }
932
1018
  this.activePointers.delete(e.pointerId);
933
1019
  if (this.activePointers.size < 2) {
934
1020
  this.lastPinchDistance = 0;
@@ -936,9 +1022,16 @@ var InputHandler = class {
936
1022
  if (this.isPanning && this.activePointers.size === 0) {
937
1023
  this.isPanning = false;
938
1024
  }
1025
+ const upResult = this.inputFilter.filterUp(e);
939
1026
  if (this.isToolActive) {
940
1027
  this.dispatchToolUp(e);
941
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;
942
1035
  }
943
1036
  };
944
1037
  onKeyDown = (e) => {
@@ -961,9 +1054,18 @@ var InputHandler = class {
961
1054
  onKeyUp = (e) => {
962
1055
  if (e.key === " ") {
963
1056
  this.spaceHeld = false;
1057
+ if (this.activePointers.size === 0) {
1058
+ if (this.lastPointerEvent) {
1059
+ this.dispatchToolHover(this.lastPointerEvent);
1060
+ } else {
1061
+ this.toolContext?.setCursor?.("default");
1062
+ }
1063
+ }
964
1064
  }
965
1065
  };
966
1066
  startPinch() {
1067
+ this.inputFilter.reset();
1068
+ this.deferredDown = null;
967
1069
  this.isPanning = true;
968
1070
  const [a, b] = this.getPinchPoints();
969
1071
  this.lastPinchDistance = this.distance(a, b);
@@ -1003,7 +1105,8 @@ var InputHandler = class {
1003
1105
  return {
1004
1106
  x: e.clientX - rect.left,
1005
1107
  y: e.clientY - rect.top,
1006
- pressure: e.pressure
1108
+ pressure: e.pressure,
1109
+ pointerType: e.pointerType === "touch" || e.pointerType === "pen" ? e.pointerType : "mouse"
1007
1110
  };
1008
1111
  }
1009
1112
  dispatchToolDown(e) {
@@ -1061,6 +1164,7 @@ var InputHandler = class {
1061
1164
  this.dispatchToolUp(e);
1062
1165
  this.isToolActive = false;
1063
1166
  }
1167
+ this.deferredDown = null;
1064
1168
  }
1065
1169
  };
1066
1170
 
@@ -4561,7 +4665,10 @@ var Viewport = class {
4561
4665
  position: "relative",
4562
4666
  width: "100%",
4563
4667
  height: "100%",
4564
- overflow: "hidden"
4668
+ overflow: "hidden",
4669
+ overscrollBehavior: "none",
4670
+ userSelect: "none",
4671
+ webkitUserSelect: "none"
4565
4672
  });
4566
4673
  return el;
4567
4674
  }
@@ -6307,7 +6414,7 @@ var UpdateLayerCommand = class {
6307
6414
  };
6308
6415
 
6309
6416
  // src/index.ts
6310
- var VERSION = "0.11.0";
6417
+ var VERSION = "0.11.2";
6311
6418
  // Annotate the CommonJS export names for ESM import in node:
6312
6419
  0 && (module.exports = {
6313
6420
  AddElementCommand,
@@ -6327,6 +6434,7 @@ var VERSION = "0.11.0";
6327
6434
  HistoryRecorder,
6328
6435
  HistoryStack,
6329
6436
  ImageTool,
6437
+ InputFilter,
6330
6438
  InputHandler,
6331
6439
  LayerManager,
6332
6440
  MeasureTool,
package/dist/index.d.cts CHANGED
@@ -238,6 +238,7 @@ interface PointerState {
238
238
  x: number;
239
239
  y: number;
240
240
  pressure: number;
241
+ pointerType: 'mouse' | 'touch' | 'pen';
241
242
  }
242
243
  interface Tool {
243
244
  readonly name: string;
@@ -423,6 +424,9 @@ declare class InputHandler {
423
424
  private historyRecorder;
424
425
  private historyStack;
425
426
  private isToolActive;
427
+ private lastPointerEvent;
428
+ private readonly inputFilter;
429
+ private deferredDown;
426
430
  private readonly abortController;
427
431
  constructor(element: HTMLElement, camera: Camera, options?: InputHandlerOptions);
428
432
  setToolManager(toolManager: ToolManager, toolContext: ToolContext): void;
@@ -450,6 +454,27 @@ declare class InputHandler {
450
454
  private cancelToolIfActive;
451
455
  }
452
456
 
457
+ type FilterAction = 'dispatch' | 'suppress' | 'defer';
458
+ interface FilteredEvent {
459
+ event: PointerEvent;
460
+ action: FilterAction;
461
+ }
462
+ interface FilteredUpEvent extends FilteredEvent {
463
+ pendingTap?: {
464
+ x: number;
465
+ y: number;
466
+ };
467
+ }
468
+ declare class InputFilter {
469
+ private activePenId;
470
+ private pendingTap;
471
+ static readonly MIN_MOVE_DISTANCE = 3;
472
+ filterDown(e: PointerEvent): FilteredEvent;
473
+ filterMove(e: PointerEvent): FilteredEvent;
474
+ filterUp(e: PointerEvent): FilteredUpEvent;
475
+ reset(): void;
476
+ }
477
+
453
478
  interface FontSizePreset {
454
479
  label: string;
455
480
  size: number;
@@ -1119,6 +1144,6 @@ declare class UpdateLayerCommand implements Command {
1119
1144
  undo(_store: ElementStore): void;
1120
1145
  }
1121
1146
 
1122
- declare const VERSION = "0.11.0";
1147
+ declare const VERSION = "0.11.2";
1123
1148
 
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 };
1149
+ 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
@@ -238,6 +238,7 @@ interface PointerState {
238
238
  x: number;
239
239
  y: number;
240
240
  pressure: number;
241
+ pointerType: 'mouse' | 'touch' | 'pen';
241
242
  }
242
243
  interface Tool {
243
244
  readonly name: string;
@@ -423,6 +424,9 @@ declare class InputHandler {
423
424
  private historyRecorder;
424
425
  private historyStack;
425
426
  private isToolActive;
427
+ private lastPointerEvent;
428
+ private readonly inputFilter;
429
+ private deferredDown;
426
430
  private readonly abortController;
427
431
  constructor(element: HTMLElement, camera: Camera, options?: InputHandlerOptions);
428
432
  setToolManager(toolManager: ToolManager, toolContext: ToolContext): void;
@@ -450,6 +454,27 @@ declare class InputHandler {
450
454
  private cancelToolIfActive;
451
455
  }
452
456
 
457
+ type FilterAction = 'dispatch' | 'suppress' | 'defer';
458
+ interface FilteredEvent {
459
+ event: PointerEvent;
460
+ action: FilterAction;
461
+ }
462
+ interface FilteredUpEvent extends FilteredEvent {
463
+ pendingTap?: {
464
+ x: number;
465
+ y: number;
466
+ };
467
+ }
468
+ declare class InputFilter {
469
+ private activePenId;
470
+ private pendingTap;
471
+ static readonly MIN_MOVE_DISTANCE = 3;
472
+ filterDown(e: PointerEvent): FilteredEvent;
473
+ filterMove(e: PointerEvent): FilteredEvent;
474
+ filterUp(e: PointerEvent): FilteredUpEvent;
475
+ reset(): void;
476
+ }
477
+
453
478
  interface FontSizePreset {
454
479
  label: string;
455
480
  size: number;
@@ -1119,6 +1144,6 @@ declare class UpdateLayerCommand implements Command {
1119
1144
  undo(_store: ElementStore): void;
1120
1145
  }
1121
1146
 
1122
- declare const VERSION = "0.11.0";
1147
+ declare const VERSION = "0.11.2";
1123
1148
 
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 };
1149
+ 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
@@ -292,7 +292,13 @@ function exportState(elements, camera, layers = []) {
292
292
  position: { ...camera.position },
293
293
  zoom: camera.zoom
294
294
  },
295
- elements: elements.map((el) => structuredClone(el)),
295
+ elements: elements.map((el) => {
296
+ const clone = structuredClone(el);
297
+ if (clone.type === "arrow") {
298
+ delete clone.cachedControlPoint;
299
+ }
300
+ return clone;
301
+ }),
296
302
  layers: layers.map((l) => ({ ...l }))
297
303
  };
298
304
  }
@@ -730,6 +736,61 @@ var Background = class {
730
736
  }
731
737
  };
732
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
+
733
794
  // src/canvas/input-handler.ts
734
795
  var ZOOM_SENSITIVITY = 1e-3;
735
796
  var MIDDLE_BUTTON = 1;
@@ -755,6 +816,9 @@ var InputHandler = class {
755
816
  historyRecorder;
756
817
  historyStack;
757
818
  isToolActive = false;
819
+ lastPointerEvent = null;
820
+ inputFilter = new InputFilter();
821
+ deferredDown = null;
758
822
  abortController = new AbortController();
759
823
  setToolManager(toolManager, toolContext) {
760
824
  this.toolManager = toolManager;
@@ -762,6 +826,9 @@ var InputHandler = class {
762
826
  }
763
827
  destroy() {
764
828
  this.abortController.abort();
829
+ this.inputFilter.reset();
830
+ this.deferredDown = null;
831
+ this.lastPointerEvent = null;
765
832
  }
766
833
  bind() {
767
834
  const opts = { signal: this.abortController.signal };
@@ -797,11 +864,18 @@ var InputHandler = class {
797
864
  this.lastPointer = { x: e.clientX, y: e.clientY };
798
865
  return;
799
866
  }
800
- if (this.activePointers.size === 1 && e.button === 0) {
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
+ }
801
874
  this.dispatchToolDown(e);
802
875
  }
803
876
  };
804
877
  onPointerMove = (e) => {
878
+ this.lastPointerEvent = e;
805
879
  if (this.activePointers.has(e.pointerId)) {
806
880
  this.activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
807
881
  }
@@ -818,11 +892,22 @@ var InputHandler = class {
818
892
  }
819
893
  if (this.isToolActive) {
820
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
+ }
821
902
  } else if (this.activePointers.size === 0) {
822
903
  this.dispatchToolHover(e);
823
904
  }
824
905
  };
825
906
  onPointerUp = (e) => {
907
+ try {
908
+ this.element.releasePointerCapture(e.pointerId);
909
+ } catch {
910
+ }
826
911
  this.activePointers.delete(e.pointerId);
827
912
  if (this.activePointers.size < 2) {
828
913
  this.lastPinchDistance = 0;
@@ -830,9 +915,16 @@ var InputHandler = class {
830
915
  if (this.isPanning && this.activePointers.size === 0) {
831
916
  this.isPanning = false;
832
917
  }
918
+ const upResult = this.inputFilter.filterUp(e);
833
919
  if (this.isToolActive) {
834
920
  this.dispatchToolUp(e);
835
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;
836
928
  }
837
929
  };
838
930
  onKeyDown = (e) => {
@@ -855,9 +947,18 @@ var InputHandler = class {
855
947
  onKeyUp = (e) => {
856
948
  if (e.key === " ") {
857
949
  this.spaceHeld = false;
950
+ if (this.activePointers.size === 0) {
951
+ if (this.lastPointerEvent) {
952
+ this.dispatchToolHover(this.lastPointerEvent);
953
+ } else {
954
+ this.toolContext?.setCursor?.("default");
955
+ }
956
+ }
858
957
  }
859
958
  };
860
959
  startPinch() {
960
+ this.inputFilter.reset();
961
+ this.deferredDown = null;
861
962
  this.isPanning = true;
862
963
  const [a, b] = this.getPinchPoints();
863
964
  this.lastPinchDistance = this.distance(a, b);
@@ -897,7 +998,8 @@ var InputHandler = class {
897
998
  return {
898
999
  x: e.clientX - rect.left,
899
1000
  y: e.clientY - rect.top,
900
- pressure: e.pressure
1001
+ pressure: e.pressure,
1002
+ pointerType: e.pointerType === "touch" || e.pointerType === "pen" ? e.pointerType : "mouse"
901
1003
  };
902
1004
  }
903
1005
  dispatchToolDown(e) {
@@ -955,6 +1057,7 @@ var InputHandler = class {
955
1057
  this.dispatchToolUp(e);
956
1058
  this.isToolActive = false;
957
1059
  }
1060
+ this.deferredDown = null;
958
1061
  }
959
1062
  };
960
1063
 
@@ -4455,7 +4558,10 @@ var Viewport = class {
4455
4558
  position: "relative",
4456
4559
  width: "100%",
4457
4560
  height: "100%",
4458
- overflow: "hidden"
4561
+ overflow: "hidden",
4562
+ overscrollBehavior: "none",
4563
+ userSelect: "none",
4564
+ webkitUserSelect: "none"
4459
4565
  });
4460
4566
  return el;
4461
4567
  }
@@ -6201,7 +6307,7 @@ var UpdateLayerCommand = class {
6201
6307
  };
6202
6308
 
6203
6309
  // src/index.ts
6204
- var VERSION = "0.11.0";
6310
+ var VERSION = "0.11.2";
6205
6311
  export {
6206
6312
  AddElementCommand,
6207
6313
  ArrowTool,
@@ -6220,6 +6326,7 @@ export {
6220
6326
  HistoryRecorder,
6221
6327
  HistoryStack,
6222
6328
  ImageTool,
6329
+ InputFilter,
6223
6330
  InputHandler,
6224
6331
  LayerManager,
6225
6332
  MeasureTool,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fieldnotes/core",
3
- "version": "0.11.0",
3
+ "version": "0.11.2",
4
4
  "description": "Vanilla TypeScript infinite canvas engine",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -21,11 +21,6 @@
21
21
  "files": [
22
22
  "dist"
23
23
  ],
24
- "scripts": {
25
- "build": "tsup",
26
- "test": "vitest run",
27
- "test:watch": "vitest"
28
- },
29
24
  "license": "MIT",
30
25
  "repository": {
31
26
  "type": "git",
@@ -45,9 +40,19 @@
45
40
  "sdk"
46
41
  ],
47
42
  "devDependencies": {
43
+ "@playwright/test": "^1.60.0",
48
44
  "@vitest/coverage-v8": "^4.1.0",
49
45
  "jsdom": "^29.0.0",
50
46
  "tsup": "^8.5.1",
51
47
  "vitest": "^4.1.0"
48
+ },
49
+ "scripts": {
50
+ "build": "tsup",
51
+ "test": "vitest run",
52
+ "test:watch": "vitest",
53
+ "test:coverage": "vitest run --coverage",
54
+ "test:all": "vitest run --coverage",
55
+ "e2e": "playwright test --config e2e/playwright.config.ts",
56
+ "e2e:update": "playwright test --config e2e/playwright.config.ts --update-snapshots"
52
57
  }
53
- }
58
+ }