@fieldnotes/core 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -811,9 +811,228 @@ function createId(prefix) {
811
811
  return `${prefix}_${Date.now().toString(36)}_${(counter++).toString(36)}`;
812
812
  }
813
813
 
814
+ // src/canvas/keyboard-actions.ts
815
+ var KeyboardActions = class {
816
+ constructor(deps) {
817
+ this.deps = deps;
818
+ }
819
+ clipboard = [];
820
+ pasteCount = 0;
821
+ nudgeTimer = null;
822
+ dispose() {
823
+ this.flushPendingNudge();
824
+ }
825
+ selectTool() {
826
+ const tm = this.deps.getToolManager();
827
+ const ctx = this.deps.getToolContext();
828
+ if (!tm || !ctx) return null;
829
+ const tool = tm.activeTool;
830
+ if (tool?.name !== "select") return null;
831
+ return { tool, ctx };
832
+ }
833
+ nudge(dx, dy, byCell) {
834
+ if (this.deps.isToolActive()) return false;
835
+ const sel = this.selectTool();
836
+ if (!sel) return false;
837
+ if (sel.tool.selectedIds.length === 0) return false;
838
+ const step = byCell ? sel.ctx.gridSize ?? 10 : 1;
839
+ if (this.nudgeTimer === null) {
840
+ this.deps.getHistoryRecorder()?.begin();
841
+ } else {
842
+ clearTimeout(this.nudgeTimer);
843
+ }
844
+ const moved = sel.tool.nudgeSelection(dx * step, dy * step, sel.ctx);
845
+ this.nudgeTimer = setTimeout(() => this.flushPendingNudge(), 400);
846
+ return moved;
847
+ }
848
+ flushPendingNudge() {
849
+ if (this.nudgeTimer === null) return;
850
+ clearTimeout(this.nudgeTimer);
851
+ this.nudgeTimer = null;
852
+ this.deps.getHistoryRecorder()?.commit();
853
+ }
854
+ deleteSelected() {
855
+ this.flushPendingNudge();
856
+ const sel = this.selectTool();
857
+ if (!sel) return;
858
+ const ids = sel.tool.selectedIds;
859
+ if (ids.length === 0) return;
860
+ const recorder = this.deps.getHistoryRecorder();
861
+ recorder?.begin();
862
+ for (const id of ids) {
863
+ sel.ctx.store.remove(id);
864
+ }
865
+ recorder?.commit();
866
+ sel.ctx.requestRender();
867
+ }
868
+ undo() {
869
+ this.flushPendingNudge();
870
+ const ctx = this.deps.getToolContext();
871
+ const stack = this.deps.getHistoryStack();
872
+ if (!stack || !ctx) return;
873
+ const recorder = this.deps.getHistoryRecorder();
874
+ recorder?.pause();
875
+ stack.undo(ctx.store);
876
+ recorder?.resume();
877
+ ctx.requestRender();
878
+ }
879
+ redo() {
880
+ this.flushPendingNudge();
881
+ const ctx = this.deps.getToolContext();
882
+ const stack = this.deps.getHistoryStack();
883
+ if (!stack || !ctx) return;
884
+ const recorder = this.deps.getHistoryRecorder();
885
+ recorder?.pause();
886
+ stack.redo(ctx.store);
887
+ recorder?.resume();
888
+ ctx.requestRender();
889
+ }
890
+ copy() {
891
+ if (this.deps.isToolActive()) return;
892
+ const sel = this.selectTool();
893
+ if (!sel) return;
894
+ const ids = sel.tool.selectedIds;
895
+ if (ids.length === 0) return;
896
+ this.clipboard = [];
897
+ for (const id of ids) {
898
+ const el = sel.ctx.store.getById(id);
899
+ if (el) this.clipboard.push(structuredClone(el));
900
+ }
901
+ this.pasteCount = 0;
902
+ }
903
+ paste() {
904
+ this.flushPendingNudge();
905
+ if (this.clipboard.length === 0 || this.deps.isToolActive()) return;
906
+ const sel = this.selectTool();
907
+ if (!sel) return;
908
+ this.pasteCount++;
909
+ this.insertClones(this.clipboard, this.pasteCount * 20, sel);
910
+ }
911
+ duplicate() {
912
+ this.flushPendingNudge();
913
+ if (this.deps.isToolActive()) return;
914
+ const sel = this.selectTool();
915
+ if (!sel) return;
916
+ const source = [];
917
+ for (const id of sel.tool.selectedIds) {
918
+ const el = sel.ctx.store.getById(id);
919
+ if (el) source.push(el);
920
+ }
921
+ if (source.length === 0) return;
922
+ this.insertClones(source, 20, sel);
923
+ }
924
+ deselect() {
925
+ if (this.deps.isToolActive()) return;
926
+ const sel = this.selectTool();
927
+ if (!sel) return;
928
+ if (sel.tool.selectedIds.length === 0) return;
929
+ sel.tool.setSelection([]);
930
+ sel.ctx.requestRender();
931
+ }
932
+ selectAll() {
933
+ if (this.deps.isToolActive()) return;
934
+ const tm = this.deps.getToolManager();
935
+ const ctx = this.deps.getToolContext();
936
+ if (!tm || !ctx) return;
937
+ if (tm.activeTool?.name !== "select") {
938
+ ctx.switchTool?.("select");
939
+ }
940
+ const sel = this.selectTool();
941
+ if (!sel) return;
942
+ const ids = sel.ctx.store.getAll().filter(
943
+ (el) => !el.locked && (sel.ctx.isLayerVisible?.(el.layerId) ?? true) && !(sel.ctx.isLayerLocked?.(el.layerId) ?? false)
944
+ ).map((el) => el.id);
945
+ sel.tool.setSelection(ids);
946
+ sel.ctx.requestRender();
947
+ }
948
+ zoomToFit() {
949
+ if (this.deps.isToolActive()) return;
950
+ this.deps.fitToContent?.();
951
+ }
952
+ zOrder(operation) {
953
+ this.flushPendingNudge();
954
+ const sel = this.selectTool();
955
+ if (!sel) return;
956
+ const ids = sel.tool.selectedIds;
957
+ if (ids.length === 0) return;
958
+ const recorder = this.deps.getHistoryRecorder();
959
+ recorder?.begin();
960
+ for (const id of ids) {
961
+ switch (operation) {
962
+ case "forward":
963
+ sel.ctx.store.bringForward(id);
964
+ break;
965
+ case "backward":
966
+ sel.ctx.store.sendBackward(id);
967
+ break;
968
+ case "front":
969
+ sel.ctx.store.bringToFront(id);
970
+ break;
971
+ case "back":
972
+ sel.ctx.store.sendToBack(id);
973
+ break;
974
+ }
975
+ }
976
+ recorder?.commit();
977
+ sel.ctx.requestRender();
978
+ }
979
+ insertClones(source, offset, sel) {
980
+ const idMap = /* @__PURE__ */ new Map();
981
+ for (const el of source) {
982
+ idMap.set(el.id, createId(el.type));
983
+ }
984
+ const newIds = [];
985
+ const recorder = this.deps.getHistoryRecorder();
986
+ recorder?.begin();
987
+ for (const el of source) {
988
+ const clone = structuredClone(el);
989
+ const newId = idMap.get(el.id);
990
+ if (!newId) continue;
991
+ clone.id = newId;
992
+ clone.position = { x: clone.position.x + offset, y: clone.position.y + offset };
993
+ if (clone.type === "arrow") {
994
+ const arrow = clone;
995
+ arrow.from = { x: arrow.from.x + offset, y: arrow.from.y + offset };
996
+ arrow.to = { x: arrow.to.x + offset, y: arrow.to.y + offset };
997
+ delete arrow.cachedControlPoint;
998
+ if (arrow.fromBinding) {
999
+ const newTarget = idMap.get(arrow.fromBinding.elementId);
1000
+ if (newTarget) {
1001
+ arrow.fromBinding = { elementId: newTarget };
1002
+ } else {
1003
+ delete arrow.fromBinding;
1004
+ }
1005
+ }
1006
+ if (arrow.toBinding) {
1007
+ const newTarget = idMap.get(arrow.toBinding.elementId);
1008
+ if (newTarget) {
1009
+ arrow.toBinding = { elementId: newTarget };
1010
+ } else {
1011
+ delete arrow.toBinding;
1012
+ }
1013
+ }
1014
+ }
1015
+ if (sel.ctx.activeLayerId) {
1016
+ clone.layerId = sel.ctx.activeLayerId;
1017
+ }
1018
+ sel.ctx.store.add(clone);
1019
+ newIds.push(clone.id);
1020
+ }
1021
+ recorder?.commit();
1022
+ sel.tool.setSelection(newIds);
1023
+ sel.ctx.requestRender();
1024
+ }
1025
+ };
1026
+
814
1027
  // src/canvas/input-handler.ts
815
1028
  var ZOOM_SENSITIVITY = 1e-3;
816
1029
  var MIDDLE_BUTTON = 1;
1030
+ var NUDGE_KEYS = {
1031
+ ArrowLeft: [-1, 0],
1032
+ ArrowRight: [1, 0],
1033
+ ArrowUp: [0, -1],
1034
+ ArrowDown: [0, 1]
1035
+ };
817
1036
  var InputHandler = class {
818
1037
  constructor(element, camera, options = {}) {
819
1038
  this.element = element;
@@ -822,6 +1041,14 @@ var InputHandler = class {
822
1041
  this.toolContext = options.toolContext ?? null;
823
1042
  this.historyRecorder = options.historyRecorder ?? null;
824
1043
  this.historyStack = options.historyStack ?? null;
1044
+ this.actions = new KeyboardActions({
1045
+ getToolManager: () => this.toolManager,
1046
+ getToolContext: () => this.toolContext,
1047
+ getHistoryRecorder: () => this.historyRecorder,
1048
+ getHistoryStack: () => this.historyStack,
1049
+ isToolActive: () => this.isToolActive,
1050
+ fitToContent: options.fitToContent
1051
+ });
825
1052
  this.element.style.touchAction = "none";
826
1053
  this.bind();
827
1054
  }
@@ -840,13 +1067,16 @@ var InputHandler = class {
840
1067
  inputFilter = new InputFilter();
841
1068
  deferredDown = null;
842
1069
  abortController = new AbortController();
843
- clipboard = [];
844
- pasteCount = 0;
1070
+ actions;
845
1071
  setToolManager(toolManager, toolContext) {
846
1072
  this.toolManager = toolManager;
847
1073
  this.toolContext = toolContext;
848
1074
  }
1075
+ flushPendingHistory() {
1076
+ this.actions.flushPendingNudge();
1077
+ }
849
1078
  destroy() {
1079
+ this.actions.dispose();
850
1080
  this.abortController.abort();
851
1081
  this.inputFilter.reset();
852
1082
  this.deferredDown = null;
@@ -952,36 +1182,61 @@ var InputHandler = class {
952
1182
  }
953
1183
  };
954
1184
  onKeyDown = (e) => {
955
- if (e.target?.isContentEditable) return;
1185
+ const target = e.target;
1186
+ if (target?.isContentEditable) return;
1187
+ const tag = target?.tagName;
1188
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
956
1189
  if (e.key === " ") {
957
1190
  this.spaceHeld = true;
958
1191
  }
959
1192
  if (e.key === "Delete" || e.key === "Backspace") {
960
- this.deleteSelected();
1193
+ this.actions.deleteSelected();
1194
+ }
1195
+ if (e.key === "Escape") {
1196
+ this.actions.deselect();
961
1197
  }
962
1198
  if ((e.ctrlKey || e.metaKey) && e.key === "z" && !e.shiftKey) {
963
1199
  e.preventDefault();
964
- this.handleUndo();
1200
+ this.actions.undo();
965
1201
  }
966
1202
  if ((e.ctrlKey || e.metaKey) && (e.key === "y" || e.key === "z" && e.shiftKey)) {
967
1203
  e.preventDefault();
968
- this.handleRedo();
1204
+ this.actions.redo();
1205
+ }
1206
+ if ((e.ctrlKey || e.metaKey) && e.key === "a") {
1207
+ e.preventDefault();
1208
+ this.actions.selectAll();
969
1209
  }
970
1210
  if ((e.ctrlKey || e.metaKey) && e.key === "c") {
971
1211
  e.preventDefault();
972
- this.handleCopy();
1212
+ this.actions.copy();
973
1213
  }
974
1214
  if ((e.ctrlKey || e.metaKey) && e.key === "v") {
975
1215
  e.preventDefault();
976
- this.handlePaste();
1216
+ this.actions.paste();
1217
+ }
1218
+ if ((e.ctrlKey || e.metaKey) && e.key === "d") {
1219
+ e.preventDefault();
1220
+ this.actions.duplicate();
977
1221
  }
978
1222
  if (e.key === "]") {
979
1223
  e.preventDefault();
980
- this.handleZOrder(e.ctrlKey || e.metaKey ? "front" : "forward");
1224
+ this.actions.zOrder(e.ctrlKey || e.metaKey ? "front" : "forward");
981
1225
  }
982
1226
  if (e.key === "[") {
983
1227
  e.preventDefault();
984
- this.handleZOrder(e.ctrlKey || e.metaKey ? "back" : "backward");
1228
+ this.actions.zOrder(e.ctrlKey || e.metaKey ? "back" : "backward");
1229
+ }
1230
+ if (e.shiftKey && e.code === "Digit1" && !e.ctrlKey && !e.metaKey && !e.altKey) {
1231
+ e.preventDefault();
1232
+ this.actions.zoomToFit();
1233
+ }
1234
+ const nudgeDelta = NUDGE_KEYS[e.key];
1235
+ if (nudgeDelta) {
1236
+ const [dx, dy] = nudgeDelta;
1237
+ if (this.actions.nudge(dx, dy, e.shiftKey)) {
1238
+ e.preventDefault();
1239
+ }
985
1240
  }
986
1241
  };
987
1242
  onKeyUp = (e) => {
@@ -1045,6 +1300,7 @@ var InputHandler = class {
1045
1300
  }
1046
1301
  dispatchToolDown(e) {
1047
1302
  if (!this.toolManager || !this.toolContext) return;
1303
+ this.actions.flushPendingNudge();
1048
1304
  this.historyRecorder?.begin();
1049
1305
  this.isToolActive = true;
1050
1306
  this.toolManager.handlePointerDown(this.toPointerState(e), this.toolContext);
@@ -1065,127 +1321,6 @@ var InputHandler = class {
1065
1321
  this.toolManager.handlePointerUp(this.toPointerState(e), this.toolContext);
1066
1322
  this.historyRecorder?.commit();
1067
1323
  }
1068
- deleteSelected() {
1069
- if (!this.toolManager || !this.toolContext) return;
1070
- const tool = this.toolManager.activeTool;
1071
- if (tool?.name !== "select") return;
1072
- const selectTool = tool;
1073
- const ids = selectTool.selectedIds;
1074
- if (ids.length === 0) return;
1075
- this.historyRecorder?.begin();
1076
- for (const id of ids) {
1077
- this.toolContext.store.remove(id);
1078
- }
1079
- this.historyRecorder?.commit();
1080
- this.toolContext.requestRender();
1081
- }
1082
- handleUndo() {
1083
- if (!this.historyStack || !this.toolContext) return;
1084
- this.historyRecorder?.pause();
1085
- this.historyStack.undo(this.toolContext.store);
1086
- this.historyRecorder?.resume();
1087
- this.toolContext.requestRender();
1088
- }
1089
- handleRedo() {
1090
- if (!this.historyStack || !this.toolContext) return;
1091
- this.historyRecorder?.pause();
1092
- this.historyStack.redo(this.toolContext.store);
1093
- this.historyRecorder?.resume();
1094
- this.toolContext.requestRender();
1095
- }
1096
- handleCopy() {
1097
- if (!this.toolManager || !this.toolContext || this.isToolActive) return;
1098
- const tool = this.toolManager.activeTool;
1099
- if (tool?.name !== "select") return;
1100
- const selectTool = tool;
1101
- const ids = selectTool.selectedIds;
1102
- if (ids.length === 0) return;
1103
- this.clipboard = [];
1104
- for (const id of ids) {
1105
- const el = this.toolContext.store.getById(id);
1106
- if (el) this.clipboard.push(structuredClone(el));
1107
- }
1108
- this.pasteCount = 0;
1109
- }
1110
- handlePaste() {
1111
- if (!this.toolManager || !this.toolContext || this.clipboard.length === 0 || this.isToolActive)
1112
- return;
1113
- const tool = this.toolManager.activeTool;
1114
- if (tool?.name !== "select") return;
1115
- const selectTool = tool;
1116
- this.pasteCount++;
1117
- const offset = this.pasteCount * 20;
1118
- const idMap = /* @__PURE__ */ new Map();
1119
- for (const el of this.clipboard) {
1120
- idMap.set(el.id, createId(el.type));
1121
- }
1122
- const newIds = [];
1123
- this.historyRecorder?.begin();
1124
- for (const el of this.clipboard) {
1125
- const clone = structuredClone(el);
1126
- const newId = idMap.get(el.id);
1127
- if (!newId) continue;
1128
- clone.id = newId;
1129
- clone.position = { x: clone.position.x + offset, y: clone.position.y + offset };
1130
- if (clone.type === "arrow") {
1131
- const arrow = clone;
1132
- arrow.from = { x: arrow.from.x + offset, y: arrow.from.y + offset };
1133
- arrow.to = { x: arrow.to.x + offset, y: arrow.to.y + offset };
1134
- delete arrow.cachedControlPoint;
1135
- if (arrow.fromBinding) {
1136
- const newTarget = idMap.get(arrow.fromBinding.elementId);
1137
- if (newTarget) {
1138
- arrow.fromBinding = { elementId: newTarget };
1139
- } else {
1140
- delete arrow.fromBinding;
1141
- }
1142
- }
1143
- if (arrow.toBinding) {
1144
- const newTarget = idMap.get(arrow.toBinding.elementId);
1145
- if (newTarget) {
1146
- arrow.toBinding = { elementId: newTarget };
1147
- } else {
1148
- delete arrow.toBinding;
1149
- }
1150
- }
1151
- }
1152
- if (this.toolContext.activeLayerId) {
1153
- clone.layerId = this.toolContext.activeLayerId;
1154
- }
1155
- this.toolContext.store.add(clone);
1156
- newIds.push(clone.id);
1157
- }
1158
- this.historyRecorder?.commit();
1159
- selectTool.setSelection(newIds);
1160
- this.toolContext.requestRender();
1161
- }
1162
- handleZOrder(operation) {
1163
- if (!this.toolManager || !this.toolContext) return;
1164
- const tool = this.toolManager.activeTool;
1165
- if (tool?.name !== "select") return;
1166
- const selectTool = tool;
1167
- const ids = selectTool.selectedIds;
1168
- if (ids.length === 0) return;
1169
- this.historyRecorder?.begin();
1170
- for (const id of ids) {
1171
- switch (operation) {
1172
- case "forward":
1173
- this.toolContext.store.bringForward(id);
1174
- break;
1175
- case "backward":
1176
- this.toolContext.store.sendBackward(id);
1177
- break;
1178
- case "front":
1179
- this.toolContext.store.bringToFront(id);
1180
- break;
1181
- case "back":
1182
- this.toolContext.store.sendToBack(id);
1183
- break;
1184
- }
1185
- }
1186
- this.historyRecorder?.commit();
1187
- this.toolContext.requestRender();
1188
- }
1189
1324
  cancelToolIfActive(e) {
1190
1325
  if (this.isToolActive) {
1191
1326
  this.dispatchToolUp(e);
@@ -3138,6 +3273,26 @@ var NoteEditor = class {
3138
3273
  }
3139
3274
  };
3140
3275
 
3276
+ // src/elements/bounds.ts
3277
+ function getElementsBoundingBox(elements) {
3278
+ let minX = Infinity;
3279
+ let minY = Infinity;
3280
+ let maxX = -Infinity;
3281
+ let maxY = -Infinity;
3282
+ let found = false;
3283
+ for (const el of elements) {
3284
+ const b = getElementBounds(el);
3285
+ if (!b) continue;
3286
+ found = true;
3287
+ if (b.x < minX) minX = b.x;
3288
+ if (b.y < minY) minY = b.y;
3289
+ if (b.x + b.w > maxX) maxX = b.x + b.w;
3290
+ if (b.y + b.h > maxY) maxY = b.y + b.h;
3291
+ }
3292
+ if (!found) return null;
3293
+ return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
3294
+ }
3295
+
3141
3296
  // src/tools/tool-manager.ts
3142
3297
  var ToolManager = class {
3143
3298
  tools = /* @__PURE__ */ new Map();
@@ -3363,6 +3518,9 @@ var HistoryRecorder = class {
3363
3518
  this.recording = true;
3364
3519
  }
3365
3520
  begin() {
3521
+ if (this.transaction !== null) {
3522
+ this.commit();
3523
+ }
3366
3524
  this.transaction = [];
3367
3525
  this.updateSnapshots.clear();
3368
3526
  }
@@ -4551,7 +4709,8 @@ var Viewport = class {
4551
4709
  toolManager: this.toolManager,
4552
4710
  toolContext: this.toolContext,
4553
4711
  historyRecorder: this.historyRecorder,
4554
- historyStack: this.history
4712
+ historyStack: this.history,
4713
+ fitToContent: () => this.fitToContent()
4555
4714
  });
4556
4715
  this.domNodeManager = new DomNodeManager({
4557
4716
  domLayer: this.domLayer,
@@ -4661,6 +4820,13 @@ var Viewport = class {
4661
4820
  this._snapToGrid = enabled;
4662
4821
  this.toolContext.snapToGrid = enabled;
4663
4822
  }
4823
+ fitToContent(padding = 40) {
4824
+ if (this.wrapper.clientWidth === 0 || this.wrapper.clientHeight === 0) return;
4825
+ const visibleElements = this.store.getAll().filter((el) => this.layerManager.isLayerVisible(el.layerId));
4826
+ const bbox = getElementsBoundingBox(visibleElements);
4827
+ if (!bbox) return;
4828
+ this.camera.fitToContent(bbox, this.wrapper.clientWidth, this.wrapper.clientHeight, padding);
4829
+ }
4664
4830
  requestRender() {
4665
4831
  this.renderLoop.requestRender();
4666
4832
  }
@@ -4679,6 +4845,7 @@ var Viewport = class {
4679
4845
  return exportImage(this.store, options, this.layerManager);
4680
4846
  }
4681
4847
  loadState(state) {
4848
+ this.inputHandler.flushPendingHistory();
4682
4849
  this.historyRecorder.pause();
4683
4850
  this.noteEditor.destroy(this.store);
4684
4851
  this.domNodeManager.clearDomNodes();
@@ -4715,6 +4882,7 @@ var Viewport = class {
4715
4882
  this.loadState(parseState(json));
4716
4883
  }
4717
4884
  undo() {
4885
+ this.inputHandler.flushPendingHistory();
4718
4886
  this.historyRecorder.pause();
4719
4887
  const result = this.history.undo(this.store);
4720
4888
  this.historyRecorder.resume();
@@ -4722,6 +4890,7 @@ var Viewport = class {
4722
4890
  return result;
4723
4891
  }
4724
4892
  redo() {
4893
+ this.inputHandler.flushPendingHistory();
4725
4894
  this.historyRecorder.pause();
4726
4895
  const result = this.history.redo(this.store);
4727
4896
  this.historyRecorder.resume();
@@ -5041,26 +5210,6 @@ var Viewport = class {
5041
5210
  }
5042
5211
  };
5043
5212
 
5044
- // src/elements/bounds.ts
5045
- function getElementsBoundingBox(elements) {
5046
- let minX = Infinity;
5047
- let minY = Infinity;
5048
- let maxX = -Infinity;
5049
- let maxY = -Infinity;
5050
- let found = false;
5051
- for (const el of elements) {
5052
- const b = getElementBounds(el);
5053
- if (!b) continue;
5054
- found = true;
5055
- if (b.x < minX) minX = b.x;
5056
- if (b.y < minY) minY = b.y;
5057
- if (b.x + b.w > maxX) maxX = b.x + b.w;
5058
- if (b.y + b.h > maxY) maxY = b.y + b.h;
5059
- }
5060
- if (!found) return null;
5061
- return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
5062
- }
5063
-
5064
5213
  // src/tools/hand-tool.ts
5065
5214
  var HandTool = class {
5066
5215
  name = "hand";
@@ -5552,23 +5701,7 @@ var SelectTool = class {
5552
5701
  });
5553
5702
  }
5554
5703
  }
5555
- const movedNonArrowIds = /* @__PURE__ */ new Set();
5556
- for (const id of this._selectedIds) {
5557
- const el = ctx.store.getById(id);
5558
- if (el && el.type !== "arrow") movedNonArrowIds.add(id);
5559
- }
5560
- if (movedNonArrowIds.size > 0) {
5561
- const updatedArrows = /* @__PURE__ */ new Set();
5562
- for (const id of movedNonArrowIds) {
5563
- const boundArrows = findBoundArrows(id, ctx.store);
5564
- for (const ba of boundArrows) {
5565
- if (updatedArrows.has(ba.id)) continue;
5566
- updatedArrows.add(ba.id);
5567
- const updates = updateBoundArrow(ba, ctx.store);
5568
- if (updates) ctx.store.update(ba.id, updates);
5569
- }
5570
- }
5571
- }
5704
+ this.updateArrowsBoundTo(this._selectedIds, ctx);
5572
5705
  ctx.requestRender();
5573
5706
  return;
5574
5707
  }
@@ -5619,6 +5752,49 @@ var SelectTool = class {
5619
5752
  }
5620
5753
  }
5621
5754
  }
5755
+ updateArrowsBoundTo(ids, ctx) {
5756
+ const movedNonArrowIds = /* @__PURE__ */ new Set();
5757
+ for (const id of ids) {
5758
+ const el = ctx.store.getById(id);
5759
+ if (el && el.type !== "arrow") movedNonArrowIds.add(id);
5760
+ }
5761
+ if (movedNonArrowIds.size === 0) return;
5762
+ const updatedArrows = /* @__PURE__ */ new Set();
5763
+ for (const id of movedNonArrowIds) {
5764
+ const boundArrows = findBoundArrows(id, ctx.store);
5765
+ for (const ba of boundArrows) {
5766
+ if (updatedArrows.has(ba.id)) continue;
5767
+ updatedArrows.add(ba.id);
5768
+ const updates = updateBoundArrow(ba, ctx.store);
5769
+ if (updates) ctx.store.update(ba.id, updates);
5770
+ }
5771
+ }
5772
+ }
5773
+ nudgeSelection(dx, dy, ctx) {
5774
+ let moved = false;
5775
+ for (const id of this._selectedIds) {
5776
+ const el = ctx.store.getById(id);
5777
+ if (!el || el.locked) continue;
5778
+ if (el.type === "arrow") {
5779
+ if (el.fromBinding || el.toBinding) continue;
5780
+ ctx.store.update(id, {
5781
+ position: { x: el.position.x + dx, y: el.position.y + dy },
5782
+ from: { x: el.from.x + dx, y: el.from.y + dy },
5783
+ to: { x: el.to.x + dx, y: el.to.y + dy }
5784
+ });
5785
+ } else {
5786
+ ctx.store.update(id, {
5787
+ position: { x: el.position.x + dx, y: el.position.y + dy }
5788
+ });
5789
+ }
5790
+ moved = true;
5791
+ }
5792
+ if (moved) {
5793
+ this.updateArrowsBoundTo(this._selectedIds, ctx);
5794
+ ctx.requestRender();
5795
+ }
5796
+ return moved;
5797
+ }
5622
5798
  updateHoverCursor(world, ctx) {
5623
5799
  const arrowHit = hitTestArrowHandles(world, this._selectedIds, ctx);
5624
5800
  if (arrowHit) {
@@ -5696,11 +5872,7 @@ var SelectTool = class {
5696
5872
  position: { x, y },
5697
5873
  size: { w, h }
5698
5874
  });
5699
- const boundArrows = findBoundArrows(this.mode.elementId, ctx.store);
5700
- for (const ba of boundArrows) {
5701
- const updates = updateBoundArrow(ba, ctx.store);
5702
- if (updates) ctx.store.update(ba.id, updates);
5703
- }
5875
+ this.updateArrowsBoundTo([this.mode.elementId], ctx);
5704
5876
  ctx.requestRender();
5705
5877
  }
5706
5878
  hitTestResizeHandle(world, ctx) {
@@ -6747,7 +6919,7 @@ var TemplateTool = class {
6747
6919
  };
6748
6920
 
6749
6921
  // src/index.ts
6750
- var VERSION = "0.16.0";
6922
+ var VERSION = "0.17.0";
6751
6923
  export {
6752
6924
  AddElementCommand,
6753
6925
  ArrowTool,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fieldnotes/core",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "Vanilla TypeScript infinite canvas engine",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",