@fieldnotes/core 0.16.0 → 0.18.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
@@ -15,7 +15,13 @@ var EventBus = class {
15
15
  this.listeners.get(event)?.delete(listener);
16
16
  }
17
17
  emit(event, data) {
18
- this.listeners.get(event)?.forEach((listener) => listener(data));
18
+ this.listeners.get(event)?.forEach((listener) => {
19
+ try {
20
+ listener(data);
21
+ } catch (err) {
22
+ console.error(`[fieldnotes] listener error for "${String(event)}"`, err);
23
+ }
24
+ });
19
25
  }
20
26
  clear() {
21
27
  this.listeners.clear();
@@ -811,9 +817,239 @@ function createId(prefix) {
811
817
  return `${prefix}_${Date.now().toString(36)}_${(counter++).toString(36)}`;
812
818
  }
813
819
 
820
+ // src/canvas/keyboard-actions.ts
821
+ var KeyboardActions = class {
822
+ constructor(deps) {
823
+ this.deps = deps;
824
+ }
825
+ clipboard = [];
826
+ pasteCount = 0;
827
+ nudgeTimer = null;
828
+ nudgeTxId = null;
829
+ dispose() {
830
+ this.flushPendingNudge();
831
+ }
832
+ selectTool() {
833
+ const tm = this.deps.getToolManager();
834
+ const ctx = this.deps.getToolContext();
835
+ if (!tm || !ctx) return null;
836
+ const tool = tm.activeTool;
837
+ if (tool?.name !== "select") return null;
838
+ return { tool, ctx };
839
+ }
840
+ nudge(dx, dy, byCell) {
841
+ if (this.deps.isToolActive()) return false;
842
+ const sel = this.selectTool();
843
+ if (!sel) return false;
844
+ if (sel.tool.selectedIds.length === 0) return false;
845
+ const step = byCell ? sel.ctx.gridSize ?? 10 : 1;
846
+ if (this.nudgeTimer === null) {
847
+ const recorder = this.deps.getHistoryRecorder();
848
+ recorder?.begin();
849
+ this.nudgeTxId = recorder?.currentTransactionId ?? null;
850
+ } else {
851
+ clearTimeout(this.nudgeTimer);
852
+ }
853
+ const moved = sel.tool.nudgeSelection(dx * step, dy * step, sel.ctx);
854
+ this.nudgeTimer = setTimeout(() => this.flushPendingNudge(), 400);
855
+ return moved;
856
+ }
857
+ flushPendingNudge() {
858
+ if (this.nudgeTimer === null) return;
859
+ clearTimeout(this.nudgeTimer);
860
+ this.nudgeTimer = null;
861
+ const recorder = this.deps.getHistoryRecorder();
862
+ if (this.nudgeTxId === null || recorder?.currentTransactionId === this.nudgeTxId) {
863
+ recorder?.commit();
864
+ }
865
+ this.nudgeTxId = null;
866
+ }
867
+ deleteSelected() {
868
+ if (this.deps.isToolActive()) return;
869
+ this.flushPendingNudge();
870
+ const sel = this.selectTool();
871
+ if (!sel) return;
872
+ const ids = sel.tool.selectedIds;
873
+ if (ids.length === 0) return;
874
+ const recorder = this.deps.getHistoryRecorder();
875
+ recorder?.begin();
876
+ for (const id of ids) {
877
+ sel.ctx.store.remove(id);
878
+ }
879
+ recorder?.commit();
880
+ sel.ctx.requestRender();
881
+ }
882
+ undo() {
883
+ if (this.deps.isToolActive()) return;
884
+ this.flushPendingNudge();
885
+ const ctx = this.deps.getToolContext();
886
+ const stack = this.deps.getHistoryStack();
887
+ if (!stack || !ctx) return;
888
+ const recorder = this.deps.getHistoryRecorder();
889
+ recorder?.pause();
890
+ stack.undo(ctx.store);
891
+ recorder?.resume();
892
+ ctx.requestRender();
893
+ }
894
+ redo() {
895
+ if (this.deps.isToolActive()) return;
896
+ this.flushPendingNudge();
897
+ const ctx = this.deps.getToolContext();
898
+ const stack = this.deps.getHistoryStack();
899
+ if (!stack || !ctx) return;
900
+ const recorder = this.deps.getHistoryRecorder();
901
+ recorder?.pause();
902
+ stack.redo(ctx.store);
903
+ recorder?.resume();
904
+ ctx.requestRender();
905
+ }
906
+ copy() {
907
+ if (this.deps.isToolActive()) return;
908
+ const sel = this.selectTool();
909
+ if (!sel) return;
910
+ const ids = sel.tool.selectedIds;
911
+ if (ids.length === 0) return;
912
+ this.clipboard = [];
913
+ for (const id of ids) {
914
+ const el = sel.ctx.store.getById(id);
915
+ if (el) this.clipboard.push(structuredClone(el));
916
+ }
917
+ this.pasteCount = 0;
918
+ }
919
+ paste() {
920
+ this.flushPendingNudge();
921
+ if (this.clipboard.length === 0 || this.deps.isToolActive()) return;
922
+ const sel = this.selectTool();
923
+ if (!sel) return;
924
+ this.pasteCount++;
925
+ this.insertClones(this.clipboard, this.pasteCount * 20, sel);
926
+ }
927
+ duplicate() {
928
+ this.flushPendingNudge();
929
+ if (this.deps.isToolActive()) return;
930
+ const sel = this.selectTool();
931
+ if (!sel) return;
932
+ const source = [];
933
+ for (const id of sel.tool.selectedIds) {
934
+ const el = sel.ctx.store.getById(id);
935
+ if (el) source.push(el);
936
+ }
937
+ if (source.length === 0) return;
938
+ this.insertClones(source, 20, sel);
939
+ }
940
+ deselect() {
941
+ if (this.deps.isToolActive()) return;
942
+ const sel = this.selectTool();
943
+ if (!sel) return;
944
+ if (sel.tool.selectedIds.length === 0) return;
945
+ sel.tool.setSelection([]);
946
+ sel.ctx.requestRender();
947
+ }
948
+ selectAll() {
949
+ if (this.deps.isToolActive()) return;
950
+ const tm = this.deps.getToolManager();
951
+ const ctx = this.deps.getToolContext();
952
+ if (!tm || !ctx) return;
953
+ if (tm.activeTool?.name !== "select") {
954
+ ctx.switchTool?.("select");
955
+ }
956
+ const sel = this.selectTool();
957
+ if (!sel) return;
958
+ const ids = sel.ctx.store.getAll().filter(
959
+ (el) => !el.locked && (sel.ctx.isLayerVisible?.(el.layerId) ?? true) && !(sel.ctx.isLayerLocked?.(el.layerId) ?? false)
960
+ ).map((el) => el.id);
961
+ sel.tool.setSelection(ids);
962
+ sel.ctx.requestRender();
963
+ }
964
+ zoomToFit() {
965
+ if (this.deps.isToolActive()) return;
966
+ this.deps.fitToContent?.();
967
+ }
968
+ zOrder(operation) {
969
+ if (this.deps.isToolActive()) return;
970
+ this.flushPendingNudge();
971
+ const sel = this.selectTool();
972
+ if (!sel) return;
973
+ const ids = sel.tool.selectedIds;
974
+ if (ids.length === 0) return;
975
+ const recorder = this.deps.getHistoryRecorder();
976
+ recorder?.begin();
977
+ for (const id of ids) {
978
+ switch (operation) {
979
+ case "forward":
980
+ sel.ctx.store.bringForward(id);
981
+ break;
982
+ case "backward":
983
+ sel.ctx.store.sendBackward(id);
984
+ break;
985
+ case "front":
986
+ sel.ctx.store.bringToFront(id);
987
+ break;
988
+ case "back":
989
+ sel.ctx.store.sendToBack(id);
990
+ break;
991
+ }
992
+ }
993
+ recorder?.commit();
994
+ sel.ctx.requestRender();
995
+ }
996
+ insertClones(source, offset, sel) {
997
+ const idMap = /* @__PURE__ */ new Map();
998
+ for (const el of source) {
999
+ idMap.set(el.id, createId(el.type));
1000
+ }
1001
+ const newIds = [];
1002
+ const recorder = this.deps.getHistoryRecorder();
1003
+ recorder?.begin();
1004
+ for (const el of source) {
1005
+ const clone = structuredClone(el);
1006
+ const newId = idMap.get(el.id);
1007
+ if (!newId) continue;
1008
+ clone.id = newId;
1009
+ clone.position = { x: clone.position.x + offset, y: clone.position.y + offset };
1010
+ if (clone.type === "arrow") {
1011
+ const arrow = clone;
1012
+ arrow.from = { x: arrow.from.x + offset, y: arrow.from.y + offset };
1013
+ arrow.to = { x: arrow.to.x + offset, y: arrow.to.y + offset };
1014
+ delete arrow.cachedControlPoint;
1015
+ if (arrow.fromBinding) {
1016
+ const newTarget = idMap.get(arrow.fromBinding.elementId);
1017
+ if (newTarget) {
1018
+ arrow.fromBinding = { elementId: newTarget };
1019
+ } else {
1020
+ delete arrow.fromBinding;
1021
+ }
1022
+ }
1023
+ if (arrow.toBinding) {
1024
+ const newTarget = idMap.get(arrow.toBinding.elementId);
1025
+ if (newTarget) {
1026
+ arrow.toBinding = { elementId: newTarget };
1027
+ } else {
1028
+ delete arrow.toBinding;
1029
+ }
1030
+ }
1031
+ }
1032
+ if (sel.ctx.activeLayerId) {
1033
+ clone.layerId = sel.ctx.activeLayerId;
1034
+ }
1035
+ sel.ctx.store.add(clone);
1036
+ newIds.push(clone.id);
1037
+ }
1038
+ recorder?.commit();
1039
+ sel.tool.setSelection(newIds);
1040
+ sel.ctx.requestRender();
1041
+ }
1042
+ };
1043
+
814
1044
  // src/canvas/input-handler.ts
815
1045
  var ZOOM_SENSITIVITY = 1e-3;
816
1046
  var MIDDLE_BUTTON = 1;
1047
+ var NUDGE_KEYS = {
1048
+ ArrowLeft: [-1, 0],
1049
+ ArrowRight: [1, 0],
1050
+ ArrowUp: [0, -1],
1051
+ ArrowDown: [0, 1]
1052
+ };
817
1053
  var InputHandler = class {
818
1054
  constructor(element, camera, options = {}) {
819
1055
  this.element = element;
@@ -822,6 +1058,14 @@ var InputHandler = class {
822
1058
  this.toolContext = options.toolContext ?? null;
823
1059
  this.historyRecorder = options.historyRecorder ?? null;
824
1060
  this.historyStack = options.historyStack ?? null;
1061
+ this.actions = new KeyboardActions({
1062
+ getToolManager: () => this.toolManager,
1063
+ getToolContext: () => this.toolContext,
1064
+ getHistoryRecorder: () => this.historyRecorder,
1065
+ getHistoryStack: () => this.historyStack,
1066
+ isToolActive: () => this.isToolActive,
1067
+ fitToContent: options.fitToContent
1068
+ });
825
1069
  this.element.style.touchAction = "none";
826
1070
  this.bind();
827
1071
  }
@@ -840,13 +1084,16 @@ var InputHandler = class {
840
1084
  inputFilter = new InputFilter();
841
1085
  deferredDown = null;
842
1086
  abortController = new AbortController();
843
- clipboard = [];
844
- pasteCount = 0;
1087
+ actions;
845
1088
  setToolManager(toolManager, toolContext) {
846
1089
  this.toolManager = toolManager;
847
1090
  this.toolContext = toolContext;
848
1091
  }
1092
+ flushPendingHistory() {
1093
+ this.actions.flushPendingNudge();
1094
+ }
849
1095
  destroy() {
1096
+ this.actions.dispose();
850
1097
  this.abortController.abort();
851
1098
  this.inputFilter.reset();
852
1099
  this.deferredDown = null;
@@ -952,36 +1199,61 @@ var InputHandler = class {
952
1199
  }
953
1200
  };
954
1201
  onKeyDown = (e) => {
955
- if (e.target?.isContentEditable) return;
1202
+ const target = e.target;
1203
+ if (target?.isContentEditable) return;
1204
+ const tag = target?.tagName;
1205
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
956
1206
  if (e.key === " ") {
957
1207
  this.spaceHeld = true;
958
1208
  }
959
1209
  if (e.key === "Delete" || e.key === "Backspace") {
960
- this.deleteSelected();
1210
+ this.actions.deleteSelected();
1211
+ }
1212
+ if (e.key === "Escape") {
1213
+ this.actions.deselect();
961
1214
  }
962
1215
  if ((e.ctrlKey || e.metaKey) && e.key === "z" && !e.shiftKey) {
963
1216
  e.preventDefault();
964
- this.handleUndo();
1217
+ this.actions.undo();
965
1218
  }
966
1219
  if ((e.ctrlKey || e.metaKey) && (e.key === "y" || e.key === "z" && e.shiftKey)) {
967
1220
  e.preventDefault();
968
- this.handleRedo();
1221
+ this.actions.redo();
1222
+ }
1223
+ if ((e.ctrlKey || e.metaKey) && e.key === "a") {
1224
+ e.preventDefault();
1225
+ this.actions.selectAll();
969
1226
  }
970
1227
  if ((e.ctrlKey || e.metaKey) && e.key === "c") {
971
1228
  e.preventDefault();
972
- this.handleCopy();
1229
+ this.actions.copy();
973
1230
  }
974
1231
  if ((e.ctrlKey || e.metaKey) && e.key === "v") {
975
1232
  e.preventDefault();
976
- this.handlePaste();
1233
+ this.actions.paste();
1234
+ }
1235
+ if ((e.ctrlKey || e.metaKey) && e.key === "d") {
1236
+ e.preventDefault();
1237
+ this.actions.duplicate();
977
1238
  }
978
1239
  if (e.key === "]") {
979
1240
  e.preventDefault();
980
- this.handleZOrder(e.ctrlKey || e.metaKey ? "front" : "forward");
1241
+ this.actions.zOrder(e.ctrlKey || e.metaKey ? "front" : "forward");
981
1242
  }
982
1243
  if (e.key === "[") {
983
1244
  e.preventDefault();
984
- this.handleZOrder(e.ctrlKey || e.metaKey ? "back" : "backward");
1245
+ this.actions.zOrder(e.ctrlKey || e.metaKey ? "back" : "backward");
1246
+ }
1247
+ if (e.shiftKey && e.code === "Digit1" && !e.ctrlKey && !e.metaKey && !e.altKey) {
1248
+ e.preventDefault();
1249
+ this.actions.zoomToFit();
1250
+ }
1251
+ const nudgeDelta = NUDGE_KEYS[e.key];
1252
+ if (nudgeDelta) {
1253
+ const [dx, dy] = nudgeDelta;
1254
+ if (this.actions.nudge(dx, dy, e.shiftKey)) {
1255
+ e.preventDefault();
1256
+ }
985
1257
  }
986
1258
  };
987
1259
  onKeyUp = (e) => {
@@ -1045,6 +1317,7 @@ var InputHandler = class {
1045
1317
  }
1046
1318
  dispatchToolDown(e) {
1047
1319
  if (!this.toolManager || !this.toolContext) return;
1320
+ this.actions.flushPendingNudge();
1048
1321
  this.historyRecorder?.begin();
1049
1322
  this.isToolActive = true;
1050
1323
  this.toolManager.handlePointerDown(this.toPointerState(e), this.toolContext);
@@ -1065,127 +1338,6 @@ var InputHandler = class {
1065
1338
  this.toolManager.handlePointerUp(this.toPointerState(e), this.toolContext);
1066
1339
  this.historyRecorder?.commit();
1067
1340
  }
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
1341
  cancelToolIfActive(e) {
1190
1342
  if (this.isToolActive) {
1191
1343
  this.dispatchToolUp(e);
@@ -3138,6 +3290,26 @@ var NoteEditor = class {
3138
3290
  }
3139
3291
  };
3140
3292
 
3293
+ // src/elements/bounds.ts
3294
+ function getElementsBoundingBox(elements) {
3295
+ let minX = Infinity;
3296
+ let minY = Infinity;
3297
+ let maxX = -Infinity;
3298
+ let maxY = -Infinity;
3299
+ let found = false;
3300
+ for (const el of elements) {
3301
+ const b = getElementBounds(el);
3302
+ if (!b) continue;
3303
+ found = true;
3304
+ if (b.x < minX) minX = b.x;
3305
+ if (b.y < minY) minY = b.y;
3306
+ if (b.x + b.w > maxX) maxX = b.x + b.w;
3307
+ if (b.y + b.h > maxY) maxY = b.y + b.h;
3308
+ }
3309
+ if (!found) return null;
3310
+ return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
3311
+ }
3312
+
3141
3313
  // src/tools/tool-manager.ts
3142
3314
  var ToolManager = class {
3143
3315
  tools = /* @__PURE__ */ new Map();
@@ -3354,6 +3526,7 @@ var HistoryRecorder = class {
3354
3526
  }
3355
3527
  recording = true;
3356
3528
  transaction = null;
3529
+ generation = 0;
3357
3530
  updateSnapshots = /* @__PURE__ */ new Map();
3358
3531
  unsubscribers;
3359
3532
  pause() {
@@ -3363,8 +3536,15 @@ var HistoryRecorder = class {
3363
3536
  this.recording = true;
3364
3537
  }
3365
3538
  begin() {
3539
+ if (this.transaction !== null) {
3540
+ this.commit();
3541
+ }
3366
3542
  this.transaction = [];
3367
3543
  this.updateSnapshots.clear();
3544
+ this.generation += 1;
3545
+ }
3546
+ get currentTransactionId() {
3547
+ return this.transaction !== null ? this.generation : null;
3368
3548
  }
3369
3549
  commit() {
3370
3550
  if (!this.transaction) return;
@@ -4551,7 +4731,8 @@ var Viewport = class {
4551
4731
  toolManager: this.toolManager,
4552
4732
  toolContext: this.toolContext,
4553
4733
  historyRecorder: this.historyRecorder,
4554
- historyStack: this.history
4734
+ historyStack: this.history,
4735
+ fitToContent: () => this.fitToContent()
4555
4736
  });
4556
4737
  this.domNodeManager = new DomNodeManager({
4557
4738
  domLayer: this.domLayer,
@@ -4661,6 +4842,13 @@ var Viewport = class {
4661
4842
  this._snapToGrid = enabled;
4662
4843
  this.toolContext.snapToGrid = enabled;
4663
4844
  }
4845
+ fitToContent(padding = 40) {
4846
+ if (this.wrapper.clientWidth === 0 || this.wrapper.clientHeight === 0) return;
4847
+ const visibleElements = this.store.getAll().filter((el) => this.layerManager.isLayerVisible(el.layerId));
4848
+ const bbox = getElementsBoundingBox(visibleElements);
4849
+ if (!bbox) return;
4850
+ this.camera.fitToContent(bbox, this.wrapper.clientWidth, this.wrapper.clientHeight, padding);
4851
+ }
4664
4852
  requestRender() {
4665
4853
  this.renderLoop.requestRender();
4666
4854
  }
@@ -4679,6 +4867,7 @@ var Viewport = class {
4679
4867
  return exportImage(this.store, options, this.layerManager);
4680
4868
  }
4681
4869
  loadState(state) {
4870
+ this.inputHandler.flushPendingHistory();
4682
4871
  this.historyRecorder.pause();
4683
4872
  this.noteEditor.destroy(this.store);
4684
4873
  this.domNodeManager.clearDomNodes();
@@ -4714,7 +4903,11 @@ var Viewport = class {
4714
4903
  loadJSON(json) {
4715
4904
  this.loadState(parseState(json));
4716
4905
  }
4906
+ setTool(name) {
4907
+ this.toolManager.setTool(name, this.toolContext);
4908
+ }
4717
4909
  undo() {
4910
+ this.inputHandler.flushPendingHistory();
4718
4911
  this.historyRecorder.pause();
4719
4912
  const result = this.history.undo(this.store);
4720
4913
  this.historyRecorder.resume();
@@ -4722,6 +4915,7 @@ var Viewport = class {
4722
4915
  return result;
4723
4916
  }
4724
4917
  redo() {
4918
+ this.inputHandler.flushPendingHistory();
4725
4919
  this.historyRecorder.pause();
4726
4920
  const result = this.history.redo(this.store);
4727
4921
  this.historyRecorder.resume();
@@ -5041,26 +5235,6 @@ var Viewport = class {
5041
5235
  }
5042
5236
  };
5043
5237
 
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
5238
  // src/tools/hand-tool.ts
5065
5239
  var HandTool = class {
5066
5240
  name = "hand";
@@ -5552,23 +5726,7 @@ var SelectTool = class {
5552
5726
  });
5553
5727
  }
5554
5728
  }
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
- }
5729
+ this.updateArrowsBoundTo(this._selectedIds, ctx);
5572
5730
  ctx.requestRender();
5573
5731
  return;
5574
5732
  }
@@ -5619,6 +5777,49 @@ var SelectTool = class {
5619
5777
  }
5620
5778
  }
5621
5779
  }
5780
+ updateArrowsBoundTo(ids, ctx) {
5781
+ const movedNonArrowIds = /* @__PURE__ */ new Set();
5782
+ for (const id of ids) {
5783
+ const el = ctx.store.getById(id);
5784
+ if (el && el.type !== "arrow") movedNonArrowIds.add(id);
5785
+ }
5786
+ if (movedNonArrowIds.size === 0) return;
5787
+ const updatedArrows = /* @__PURE__ */ new Set();
5788
+ for (const id of movedNonArrowIds) {
5789
+ const boundArrows = findBoundArrows(id, ctx.store);
5790
+ for (const ba of boundArrows) {
5791
+ if (updatedArrows.has(ba.id)) continue;
5792
+ updatedArrows.add(ba.id);
5793
+ const updates = updateBoundArrow(ba, ctx.store);
5794
+ if (updates) ctx.store.update(ba.id, updates);
5795
+ }
5796
+ }
5797
+ }
5798
+ nudgeSelection(dx, dy, ctx) {
5799
+ let moved = false;
5800
+ for (const id of this._selectedIds) {
5801
+ const el = ctx.store.getById(id);
5802
+ if (!el || el.locked) continue;
5803
+ if (el.type === "arrow") {
5804
+ if (el.fromBinding || el.toBinding) continue;
5805
+ ctx.store.update(id, {
5806
+ position: { x: el.position.x + dx, y: el.position.y + dy },
5807
+ from: { x: el.from.x + dx, y: el.from.y + dy },
5808
+ to: { x: el.to.x + dx, y: el.to.y + dy }
5809
+ });
5810
+ } else {
5811
+ ctx.store.update(id, {
5812
+ position: { x: el.position.x + dx, y: el.position.y + dy }
5813
+ });
5814
+ }
5815
+ moved = true;
5816
+ }
5817
+ if (moved) {
5818
+ this.updateArrowsBoundTo(this._selectedIds, ctx);
5819
+ ctx.requestRender();
5820
+ }
5821
+ return moved;
5822
+ }
5622
5823
  updateHoverCursor(world, ctx) {
5623
5824
  const arrowHit = hitTestArrowHandles(world, this._selectedIds, ctx);
5624
5825
  if (arrowHit) {
@@ -5696,11 +5897,7 @@ var SelectTool = class {
5696
5897
  position: { x, y },
5697
5898
  size: { w, h }
5698
5899
  });
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
- }
5900
+ this.updateArrowsBoundTo([this.mode.elementId], ctx);
5704
5901
  ctx.requestRender();
5705
5902
  }
5706
5903
  hitTestResizeHandle(world, ctx) {
@@ -6747,7 +6944,7 @@ var TemplateTool = class {
6747
6944
  };
6748
6945
 
6749
6946
  // src/index.ts
6750
- var VERSION = "0.16.0";
6947
+ var VERSION = "0.18.0";
6751
6948
  export {
6752
6949
  AddElementCommand,
6753
6950
  ArrowTool,
@@ -6834,3 +7031,4 @@ export {
6834
7031
  unbindArrow,
6835
7032
  updateBoundArrow
6836
7033
  };
7034
+ //# sourceMappingURL=index.js.map