@fieldnotes/core 0.11.3 → 0.12.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
@@ -898,6 +898,12 @@ var InputFilter = class _InputFilter {
898
898
  }
899
899
  };
900
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
+
901
907
  // src/canvas/input-handler.ts
902
908
  var ZOOM_SENSITIVITY = 1e-3;
903
909
  var MIDDLE_BUTTON = 1;
@@ -927,6 +933,8 @@ var InputHandler = class {
927
933
  inputFilter = new InputFilter();
928
934
  deferredDown = null;
929
935
  abortController = new AbortController();
936
+ clipboard = [];
937
+ pasteCount = 0;
930
938
  setToolManager(toolManager, toolContext) {
931
939
  this.toolManager = toolManager;
932
940
  this.toolContext = toolContext;
@@ -1050,6 +1058,14 @@ var InputHandler = class {
1050
1058
  e.preventDefault();
1051
1059
  this.handleRedo();
1052
1060
  }
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
+ }
1053
1069
  };
1054
1070
  onKeyUp = (e) => {
1055
1071
  if (e.key === " ") {
@@ -1106,7 +1122,8 @@ var InputHandler = class {
1106
1122
  x: e.clientX - rect.left,
1107
1123
  y: e.clientY - rect.top,
1108
1124
  pressure: e.pressure,
1109
- pointerType: e.pointerType === "touch" || e.pointerType === "pen" ? e.pointerType : "mouse"
1125
+ pointerType: e.pointerType === "touch" || e.pointerType === "pen" ? e.pointerType : "mouse",
1126
+ shiftKey: e.shiftKey
1110
1127
  };
1111
1128
  }
1112
1129
  dispatchToolDown(e) {
@@ -1159,6 +1176,72 @@ var InputHandler = class {
1159
1176
  this.historyRecorder?.resume();
1160
1177
  this.toolContext.requestRender();
1161
1178
  }
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
+ }
1162
1245
  cancelToolIfActive(e) {
1163
1246
  if (this.isToolActive) {
1164
1247
  this.dispatchToolUp(e);
@@ -2543,12 +2626,6 @@ var ElementRenderer = class {
2543
2626
  }
2544
2627
  };
2545
2628
 
2546
- // src/elements/create-id.ts
2547
- var counter = 0;
2548
- function createId(prefix) {
2549
- return `${prefix}_${Date.now().toString(36)}_${(counter++).toString(36)}`;
2550
- }
2551
-
2552
2629
  // src/elements/element-factory.ts
2553
2630
  var DEFAULT_NOTE_FONT_SIZE = 18;
2554
2631
  function createStroke(input) {
@@ -5099,9 +5176,15 @@ var SelectTool = class {
5099
5176
  lastWorld = { x: 0, y: 0 };
5100
5177
  currentWorld = { x: 0, y: 0 };
5101
5178
  ctx = null;
5179
+ pendingSingleSelectId = null;
5180
+ hasDragged = false;
5102
5181
  get selectedIds() {
5103
5182
  return [...this._selectedIds];
5104
5183
  }
5184
+ setSelection(ids) {
5185
+ this._selectedIds = ids;
5186
+ this.ctx?.requestRender();
5187
+ }
5105
5188
  get isMarqueeActive() {
5106
5189
  return this.mode.type === "marquee";
5107
5190
  }
@@ -5150,13 +5233,27 @@ var SelectTool = class {
5150
5233
  return;
5151
5234
  }
5152
5235
  }
5236
+ this.pendingSingleSelectId = null;
5237
+ this.hasDragged = false;
5153
5238
  const hit = this.hitTest(world, ctx);
5154
5239
  if (hit) {
5155
5240
  const alreadySelected = this._selectedIds.includes(hit.id);
5156
- if (!alreadySelected) {
5157
- this._selectedIds = [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" };
5158
5256
  }
5159
- this.mode = hit.locked ? { type: "idle" } : { type: "dragging" };
5160
5257
  } else {
5161
5258
  this._selectedIds = [];
5162
5259
  this.mode = { type: "marquee", start: world };
@@ -5182,6 +5279,7 @@ var SelectTool = class {
5182
5279
  return;
5183
5280
  }
5184
5281
  if (this.mode.type === "dragging" && this._selectedIds.length > 0) {
5282
+ this.hasDragged = true;
5185
5283
  ctx.setCursor?.("move");
5186
5284
  const snapped = this.snap(world, ctx);
5187
5285
  const dx = snapped.x - this.lastWorld.x;
@@ -5250,6 +5348,11 @@ var SelectTool = class {
5250
5348
  }
5251
5349
  ctx.requestRender();
5252
5350
  }
5351
+ if (!this.hasDragged && this.pendingSingleSelectId !== null) {
5352
+ this._selectedIds = [this.pendingSingleSelectId];
5353
+ }
5354
+ this.pendingSingleSelectId = null;
5355
+ this.hasDragged = false;
5253
5356
  this.mode = { type: "idle" };
5254
5357
  ctx.setCursor?.("default");
5255
5358
  }
@@ -6430,7 +6533,7 @@ var UpdateLayerCommand = class {
6430
6533
  };
6431
6534
 
6432
6535
  // src/index.ts
6433
- var VERSION = "0.11.3";
6536
+ var VERSION = "0.12.0";
6434
6537
  // Annotate the CommonJS export names for ESM import in node:
6435
6538
  0 && (module.exports = {
6436
6539
  AddElementCommand,
package/dist/index.d.cts CHANGED
@@ -240,6 +240,7 @@ interface PointerState {
240
240
  y: number;
241
241
  pressure: number;
242
242
  pointerType: 'mouse' | 'touch' | 'pen';
243
+ shiftKey: boolean;
243
244
  }
244
245
  interface Tool {
245
246
  readonly name: string;
@@ -429,6 +430,8 @@ declare class InputHandler {
429
430
  private readonly inputFilter;
430
431
  private deferredDown;
431
432
  private readonly abortController;
433
+ private clipboard;
434
+ private pasteCount;
432
435
  constructor(element: HTMLElement, camera: Camera, options?: InputHandlerOptions);
433
436
  setToolManager(toolManager: ToolManager, toolContext: ToolContext): void;
434
437
  destroy(): void;
@@ -452,6 +455,8 @@ declare class InputHandler {
452
455
  private deleteSelected;
453
456
  private handleUndo;
454
457
  private handleRedo;
458
+ private handleCopy;
459
+ private handlePaste;
455
460
  private cancelToolIfActive;
456
461
  }
457
462
 
@@ -900,7 +905,10 @@ declare class SelectTool implements Tool {
900
905
  private lastWorld;
901
906
  private currentWorld;
902
907
  private ctx;
908
+ private pendingSingleSelectId;
909
+ private hasDragged;
903
910
  get selectedIds(): string[];
911
+ setSelection(ids: string[]): void;
904
912
  get isMarqueeActive(): boolean;
905
913
  onActivate(ctx: ToolContext): void;
906
914
  onDeactivate(ctx: ToolContext): void;
@@ -1145,6 +1153,6 @@ declare class UpdateLayerCommand implements Command {
1145
1153
  undo(_store: ElementStore): void;
1146
1154
  }
1147
1155
 
1148
- declare const VERSION = "0.11.3";
1156
+ declare const VERSION = "0.12.0";
1149
1157
 
1150
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 };
package/dist/index.d.ts CHANGED
@@ -240,6 +240,7 @@ interface PointerState {
240
240
  y: number;
241
241
  pressure: number;
242
242
  pointerType: 'mouse' | 'touch' | 'pen';
243
+ shiftKey: boolean;
243
244
  }
244
245
  interface Tool {
245
246
  readonly name: string;
@@ -429,6 +430,8 @@ declare class InputHandler {
429
430
  private readonly inputFilter;
430
431
  private deferredDown;
431
432
  private readonly abortController;
433
+ private clipboard;
434
+ private pasteCount;
432
435
  constructor(element: HTMLElement, camera: Camera, options?: InputHandlerOptions);
433
436
  setToolManager(toolManager: ToolManager, toolContext: ToolContext): void;
434
437
  destroy(): void;
@@ -452,6 +455,8 @@ declare class InputHandler {
452
455
  private deleteSelected;
453
456
  private handleUndo;
454
457
  private handleRedo;
458
+ private handleCopy;
459
+ private handlePaste;
455
460
  private cancelToolIfActive;
456
461
  }
457
462
 
@@ -900,7 +905,10 @@ declare class SelectTool implements Tool {
900
905
  private lastWorld;
901
906
  private currentWorld;
902
907
  private ctx;
908
+ private pendingSingleSelectId;
909
+ private hasDragged;
903
910
  get selectedIds(): string[];
911
+ setSelection(ids: string[]): void;
904
912
  get isMarqueeActive(): boolean;
905
913
  onActivate(ctx: ToolContext): void;
906
914
  onDeactivate(ctx: ToolContext): void;
@@ -1145,6 +1153,6 @@ declare class UpdateLayerCommand implements Command {
1145
1153
  undo(_store: ElementStore): void;
1146
1154
  }
1147
1155
 
1148
- declare const VERSION = "0.11.3";
1156
+ declare const VERSION = "0.12.0";
1149
1157
 
1150
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 };
package/dist/index.js CHANGED
@@ -791,6 +791,12 @@ var InputFilter = class _InputFilter {
791
791
  }
792
792
  };
793
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
+
794
800
  // src/canvas/input-handler.ts
795
801
  var ZOOM_SENSITIVITY = 1e-3;
796
802
  var MIDDLE_BUTTON = 1;
@@ -820,6 +826,8 @@ var InputHandler = class {
820
826
  inputFilter = new InputFilter();
821
827
  deferredDown = null;
822
828
  abortController = new AbortController();
829
+ clipboard = [];
830
+ pasteCount = 0;
823
831
  setToolManager(toolManager, toolContext) {
824
832
  this.toolManager = toolManager;
825
833
  this.toolContext = toolContext;
@@ -943,6 +951,14 @@ var InputHandler = class {
943
951
  e.preventDefault();
944
952
  this.handleRedo();
945
953
  }
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
+ }
946
962
  };
947
963
  onKeyUp = (e) => {
948
964
  if (e.key === " ") {
@@ -999,7 +1015,8 @@ var InputHandler = class {
999
1015
  x: e.clientX - rect.left,
1000
1016
  y: e.clientY - rect.top,
1001
1017
  pressure: e.pressure,
1002
- pointerType: e.pointerType === "touch" || e.pointerType === "pen" ? e.pointerType : "mouse"
1018
+ pointerType: e.pointerType === "touch" || e.pointerType === "pen" ? e.pointerType : "mouse",
1019
+ shiftKey: e.shiftKey
1003
1020
  };
1004
1021
  }
1005
1022
  dispatchToolDown(e) {
@@ -1052,6 +1069,72 @@ var InputHandler = class {
1052
1069
  this.historyRecorder?.resume();
1053
1070
  this.toolContext.requestRender();
1054
1071
  }
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
+ }
1055
1138
  cancelToolIfActive(e) {
1056
1139
  if (this.isToolActive) {
1057
1140
  this.dispatchToolUp(e);
@@ -2436,12 +2519,6 @@ var ElementRenderer = class {
2436
2519
  }
2437
2520
  };
2438
2521
 
2439
- // src/elements/create-id.ts
2440
- var counter = 0;
2441
- function createId(prefix) {
2442
- return `${prefix}_${Date.now().toString(36)}_${(counter++).toString(36)}`;
2443
- }
2444
-
2445
2522
  // src/elements/element-factory.ts
2446
2523
  var DEFAULT_NOTE_FONT_SIZE = 18;
2447
2524
  function createStroke(input) {
@@ -4992,9 +5069,15 @@ var SelectTool = class {
4992
5069
  lastWorld = { x: 0, y: 0 };
4993
5070
  currentWorld = { x: 0, y: 0 };
4994
5071
  ctx = null;
5072
+ pendingSingleSelectId = null;
5073
+ hasDragged = false;
4995
5074
  get selectedIds() {
4996
5075
  return [...this._selectedIds];
4997
5076
  }
5077
+ setSelection(ids) {
5078
+ this._selectedIds = ids;
5079
+ this.ctx?.requestRender();
5080
+ }
4998
5081
  get isMarqueeActive() {
4999
5082
  return this.mode.type === "marquee";
5000
5083
  }
@@ -5043,13 +5126,27 @@ var SelectTool = class {
5043
5126
  return;
5044
5127
  }
5045
5128
  }
5129
+ this.pendingSingleSelectId = null;
5130
+ this.hasDragged = false;
5046
5131
  const hit = this.hitTest(world, ctx);
5047
5132
  if (hit) {
5048
5133
  const alreadySelected = this._selectedIds.includes(hit.id);
5049
- if (!alreadySelected) {
5050
- this._selectedIds = [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" };
5051
5149
  }
5052
- this.mode = hit.locked ? { type: "idle" } : { type: "dragging" };
5053
5150
  } else {
5054
5151
  this._selectedIds = [];
5055
5152
  this.mode = { type: "marquee", start: world };
@@ -5075,6 +5172,7 @@ var SelectTool = class {
5075
5172
  return;
5076
5173
  }
5077
5174
  if (this.mode.type === "dragging" && this._selectedIds.length > 0) {
5175
+ this.hasDragged = true;
5078
5176
  ctx.setCursor?.("move");
5079
5177
  const snapped = this.snap(world, ctx);
5080
5178
  const dx = snapped.x - this.lastWorld.x;
@@ -5143,6 +5241,11 @@ var SelectTool = class {
5143
5241
  }
5144
5242
  ctx.requestRender();
5145
5243
  }
5244
+ if (!this.hasDragged && this.pendingSingleSelectId !== null) {
5245
+ this._selectedIds = [this.pendingSingleSelectId];
5246
+ }
5247
+ this.pendingSingleSelectId = null;
5248
+ this.hasDragged = false;
5146
5249
  this.mode = { type: "idle" };
5147
5250
  ctx.setCursor?.("default");
5148
5251
  }
@@ -6323,7 +6426,7 @@ var UpdateLayerCommand = class {
6323
6426
  };
6324
6427
 
6325
6428
  // src/index.ts
6326
- var VERSION = "0.11.3";
6429
+ var VERSION = "0.12.0";
6327
6430
  export {
6328
6431
  AddElementCommand,
6329
6432
  ArrowTool,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fieldnotes/core",
3
- "version": "0.11.3",
3
+ "version": "0.12.0",
4
4
  "description": "Vanilla TypeScript infinite canvas engine",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",