@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.cjs CHANGED
@@ -124,7 +124,13 @@ var EventBus = class {
124
124
  this.listeners.get(event)?.delete(listener);
125
125
  }
126
126
  emit(event, data) {
127
- this.listeners.get(event)?.forEach((listener) => listener(data));
127
+ this.listeners.get(event)?.forEach((listener) => {
128
+ try {
129
+ listener(data);
130
+ } catch (err) {
131
+ console.error(`[fieldnotes] listener error for "${String(event)}"`, err);
132
+ }
133
+ });
128
134
  }
129
135
  clear() {
130
136
  this.listeners.clear();
@@ -920,9 +926,239 @@ function createId(prefix) {
920
926
  return `${prefix}_${Date.now().toString(36)}_${(counter++).toString(36)}`;
921
927
  }
922
928
 
929
+ // src/canvas/keyboard-actions.ts
930
+ var KeyboardActions = class {
931
+ constructor(deps) {
932
+ this.deps = deps;
933
+ }
934
+ clipboard = [];
935
+ pasteCount = 0;
936
+ nudgeTimer = null;
937
+ nudgeTxId = null;
938
+ dispose() {
939
+ this.flushPendingNudge();
940
+ }
941
+ selectTool() {
942
+ const tm = this.deps.getToolManager();
943
+ const ctx = this.deps.getToolContext();
944
+ if (!tm || !ctx) return null;
945
+ const tool = tm.activeTool;
946
+ if (tool?.name !== "select") return null;
947
+ return { tool, ctx };
948
+ }
949
+ nudge(dx, dy, byCell) {
950
+ if (this.deps.isToolActive()) return false;
951
+ const sel = this.selectTool();
952
+ if (!sel) return false;
953
+ if (sel.tool.selectedIds.length === 0) return false;
954
+ const step = byCell ? sel.ctx.gridSize ?? 10 : 1;
955
+ if (this.nudgeTimer === null) {
956
+ const recorder = this.deps.getHistoryRecorder();
957
+ recorder?.begin();
958
+ this.nudgeTxId = recorder?.currentTransactionId ?? null;
959
+ } else {
960
+ clearTimeout(this.nudgeTimer);
961
+ }
962
+ const moved = sel.tool.nudgeSelection(dx * step, dy * step, sel.ctx);
963
+ this.nudgeTimer = setTimeout(() => this.flushPendingNudge(), 400);
964
+ return moved;
965
+ }
966
+ flushPendingNudge() {
967
+ if (this.nudgeTimer === null) return;
968
+ clearTimeout(this.nudgeTimer);
969
+ this.nudgeTimer = null;
970
+ const recorder = this.deps.getHistoryRecorder();
971
+ if (this.nudgeTxId === null || recorder?.currentTransactionId === this.nudgeTxId) {
972
+ recorder?.commit();
973
+ }
974
+ this.nudgeTxId = null;
975
+ }
976
+ deleteSelected() {
977
+ if (this.deps.isToolActive()) return;
978
+ this.flushPendingNudge();
979
+ const sel = this.selectTool();
980
+ if (!sel) return;
981
+ const ids = sel.tool.selectedIds;
982
+ if (ids.length === 0) return;
983
+ const recorder = this.deps.getHistoryRecorder();
984
+ recorder?.begin();
985
+ for (const id of ids) {
986
+ sel.ctx.store.remove(id);
987
+ }
988
+ recorder?.commit();
989
+ sel.ctx.requestRender();
990
+ }
991
+ undo() {
992
+ if (this.deps.isToolActive()) return;
993
+ this.flushPendingNudge();
994
+ const ctx = this.deps.getToolContext();
995
+ const stack = this.deps.getHistoryStack();
996
+ if (!stack || !ctx) return;
997
+ const recorder = this.deps.getHistoryRecorder();
998
+ recorder?.pause();
999
+ stack.undo(ctx.store);
1000
+ recorder?.resume();
1001
+ ctx.requestRender();
1002
+ }
1003
+ redo() {
1004
+ if (this.deps.isToolActive()) return;
1005
+ this.flushPendingNudge();
1006
+ const ctx = this.deps.getToolContext();
1007
+ const stack = this.deps.getHistoryStack();
1008
+ if (!stack || !ctx) return;
1009
+ const recorder = this.deps.getHistoryRecorder();
1010
+ recorder?.pause();
1011
+ stack.redo(ctx.store);
1012
+ recorder?.resume();
1013
+ ctx.requestRender();
1014
+ }
1015
+ copy() {
1016
+ if (this.deps.isToolActive()) return;
1017
+ const sel = this.selectTool();
1018
+ if (!sel) return;
1019
+ const ids = sel.tool.selectedIds;
1020
+ if (ids.length === 0) return;
1021
+ this.clipboard = [];
1022
+ for (const id of ids) {
1023
+ const el = sel.ctx.store.getById(id);
1024
+ if (el) this.clipboard.push(structuredClone(el));
1025
+ }
1026
+ this.pasteCount = 0;
1027
+ }
1028
+ paste() {
1029
+ this.flushPendingNudge();
1030
+ if (this.clipboard.length === 0 || this.deps.isToolActive()) return;
1031
+ const sel = this.selectTool();
1032
+ if (!sel) return;
1033
+ this.pasteCount++;
1034
+ this.insertClones(this.clipboard, this.pasteCount * 20, sel);
1035
+ }
1036
+ duplicate() {
1037
+ this.flushPendingNudge();
1038
+ if (this.deps.isToolActive()) return;
1039
+ const sel = this.selectTool();
1040
+ if (!sel) return;
1041
+ const source = [];
1042
+ for (const id of sel.tool.selectedIds) {
1043
+ const el = sel.ctx.store.getById(id);
1044
+ if (el) source.push(el);
1045
+ }
1046
+ if (source.length === 0) return;
1047
+ this.insertClones(source, 20, sel);
1048
+ }
1049
+ deselect() {
1050
+ if (this.deps.isToolActive()) return;
1051
+ const sel = this.selectTool();
1052
+ if (!sel) return;
1053
+ if (sel.tool.selectedIds.length === 0) return;
1054
+ sel.tool.setSelection([]);
1055
+ sel.ctx.requestRender();
1056
+ }
1057
+ selectAll() {
1058
+ if (this.deps.isToolActive()) return;
1059
+ const tm = this.deps.getToolManager();
1060
+ const ctx = this.deps.getToolContext();
1061
+ if (!tm || !ctx) return;
1062
+ if (tm.activeTool?.name !== "select") {
1063
+ ctx.switchTool?.("select");
1064
+ }
1065
+ const sel = this.selectTool();
1066
+ if (!sel) return;
1067
+ const ids = sel.ctx.store.getAll().filter(
1068
+ (el) => !el.locked && (sel.ctx.isLayerVisible?.(el.layerId) ?? true) && !(sel.ctx.isLayerLocked?.(el.layerId) ?? false)
1069
+ ).map((el) => el.id);
1070
+ sel.tool.setSelection(ids);
1071
+ sel.ctx.requestRender();
1072
+ }
1073
+ zoomToFit() {
1074
+ if (this.deps.isToolActive()) return;
1075
+ this.deps.fitToContent?.();
1076
+ }
1077
+ zOrder(operation) {
1078
+ if (this.deps.isToolActive()) return;
1079
+ this.flushPendingNudge();
1080
+ const sel = this.selectTool();
1081
+ if (!sel) return;
1082
+ const ids = sel.tool.selectedIds;
1083
+ if (ids.length === 0) return;
1084
+ const recorder = this.deps.getHistoryRecorder();
1085
+ recorder?.begin();
1086
+ for (const id of ids) {
1087
+ switch (operation) {
1088
+ case "forward":
1089
+ sel.ctx.store.bringForward(id);
1090
+ break;
1091
+ case "backward":
1092
+ sel.ctx.store.sendBackward(id);
1093
+ break;
1094
+ case "front":
1095
+ sel.ctx.store.bringToFront(id);
1096
+ break;
1097
+ case "back":
1098
+ sel.ctx.store.sendToBack(id);
1099
+ break;
1100
+ }
1101
+ }
1102
+ recorder?.commit();
1103
+ sel.ctx.requestRender();
1104
+ }
1105
+ insertClones(source, offset, sel) {
1106
+ const idMap = /* @__PURE__ */ new Map();
1107
+ for (const el of source) {
1108
+ idMap.set(el.id, createId(el.type));
1109
+ }
1110
+ const newIds = [];
1111
+ const recorder = this.deps.getHistoryRecorder();
1112
+ recorder?.begin();
1113
+ for (const el of source) {
1114
+ const clone = structuredClone(el);
1115
+ const newId = idMap.get(el.id);
1116
+ if (!newId) continue;
1117
+ clone.id = newId;
1118
+ clone.position = { x: clone.position.x + offset, y: clone.position.y + offset };
1119
+ if (clone.type === "arrow") {
1120
+ const arrow = clone;
1121
+ arrow.from = { x: arrow.from.x + offset, y: arrow.from.y + offset };
1122
+ arrow.to = { x: arrow.to.x + offset, y: arrow.to.y + offset };
1123
+ delete arrow.cachedControlPoint;
1124
+ if (arrow.fromBinding) {
1125
+ const newTarget = idMap.get(arrow.fromBinding.elementId);
1126
+ if (newTarget) {
1127
+ arrow.fromBinding = { elementId: newTarget };
1128
+ } else {
1129
+ delete arrow.fromBinding;
1130
+ }
1131
+ }
1132
+ if (arrow.toBinding) {
1133
+ const newTarget = idMap.get(arrow.toBinding.elementId);
1134
+ if (newTarget) {
1135
+ arrow.toBinding = { elementId: newTarget };
1136
+ } else {
1137
+ delete arrow.toBinding;
1138
+ }
1139
+ }
1140
+ }
1141
+ if (sel.ctx.activeLayerId) {
1142
+ clone.layerId = sel.ctx.activeLayerId;
1143
+ }
1144
+ sel.ctx.store.add(clone);
1145
+ newIds.push(clone.id);
1146
+ }
1147
+ recorder?.commit();
1148
+ sel.tool.setSelection(newIds);
1149
+ sel.ctx.requestRender();
1150
+ }
1151
+ };
1152
+
923
1153
  // src/canvas/input-handler.ts
924
1154
  var ZOOM_SENSITIVITY = 1e-3;
925
1155
  var MIDDLE_BUTTON = 1;
1156
+ var NUDGE_KEYS = {
1157
+ ArrowLeft: [-1, 0],
1158
+ ArrowRight: [1, 0],
1159
+ ArrowUp: [0, -1],
1160
+ ArrowDown: [0, 1]
1161
+ };
926
1162
  var InputHandler = class {
927
1163
  constructor(element, camera, options = {}) {
928
1164
  this.element = element;
@@ -931,6 +1167,14 @@ var InputHandler = class {
931
1167
  this.toolContext = options.toolContext ?? null;
932
1168
  this.historyRecorder = options.historyRecorder ?? null;
933
1169
  this.historyStack = options.historyStack ?? null;
1170
+ this.actions = new KeyboardActions({
1171
+ getToolManager: () => this.toolManager,
1172
+ getToolContext: () => this.toolContext,
1173
+ getHistoryRecorder: () => this.historyRecorder,
1174
+ getHistoryStack: () => this.historyStack,
1175
+ isToolActive: () => this.isToolActive,
1176
+ fitToContent: options.fitToContent
1177
+ });
934
1178
  this.element.style.touchAction = "none";
935
1179
  this.bind();
936
1180
  }
@@ -949,13 +1193,16 @@ var InputHandler = class {
949
1193
  inputFilter = new InputFilter();
950
1194
  deferredDown = null;
951
1195
  abortController = new AbortController();
952
- clipboard = [];
953
- pasteCount = 0;
1196
+ actions;
954
1197
  setToolManager(toolManager, toolContext) {
955
1198
  this.toolManager = toolManager;
956
1199
  this.toolContext = toolContext;
957
1200
  }
1201
+ flushPendingHistory() {
1202
+ this.actions.flushPendingNudge();
1203
+ }
958
1204
  destroy() {
1205
+ this.actions.dispose();
959
1206
  this.abortController.abort();
960
1207
  this.inputFilter.reset();
961
1208
  this.deferredDown = null;
@@ -1061,36 +1308,61 @@ var InputHandler = class {
1061
1308
  }
1062
1309
  };
1063
1310
  onKeyDown = (e) => {
1064
- if (e.target?.isContentEditable) return;
1311
+ const target = e.target;
1312
+ if (target?.isContentEditable) return;
1313
+ const tag = target?.tagName;
1314
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
1065
1315
  if (e.key === " ") {
1066
1316
  this.spaceHeld = true;
1067
1317
  }
1068
1318
  if (e.key === "Delete" || e.key === "Backspace") {
1069
- this.deleteSelected();
1319
+ this.actions.deleteSelected();
1320
+ }
1321
+ if (e.key === "Escape") {
1322
+ this.actions.deselect();
1070
1323
  }
1071
1324
  if ((e.ctrlKey || e.metaKey) && e.key === "z" && !e.shiftKey) {
1072
1325
  e.preventDefault();
1073
- this.handleUndo();
1326
+ this.actions.undo();
1074
1327
  }
1075
1328
  if ((e.ctrlKey || e.metaKey) && (e.key === "y" || e.key === "z" && e.shiftKey)) {
1076
1329
  e.preventDefault();
1077
- this.handleRedo();
1330
+ this.actions.redo();
1331
+ }
1332
+ if ((e.ctrlKey || e.metaKey) && e.key === "a") {
1333
+ e.preventDefault();
1334
+ this.actions.selectAll();
1078
1335
  }
1079
1336
  if ((e.ctrlKey || e.metaKey) && e.key === "c") {
1080
1337
  e.preventDefault();
1081
- this.handleCopy();
1338
+ this.actions.copy();
1082
1339
  }
1083
1340
  if ((e.ctrlKey || e.metaKey) && e.key === "v") {
1084
1341
  e.preventDefault();
1085
- this.handlePaste();
1342
+ this.actions.paste();
1343
+ }
1344
+ if ((e.ctrlKey || e.metaKey) && e.key === "d") {
1345
+ e.preventDefault();
1346
+ this.actions.duplicate();
1086
1347
  }
1087
1348
  if (e.key === "]") {
1088
1349
  e.preventDefault();
1089
- this.handleZOrder(e.ctrlKey || e.metaKey ? "front" : "forward");
1350
+ this.actions.zOrder(e.ctrlKey || e.metaKey ? "front" : "forward");
1090
1351
  }
1091
1352
  if (e.key === "[") {
1092
1353
  e.preventDefault();
1093
- this.handleZOrder(e.ctrlKey || e.metaKey ? "back" : "backward");
1354
+ this.actions.zOrder(e.ctrlKey || e.metaKey ? "back" : "backward");
1355
+ }
1356
+ if (e.shiftKey && e.code === "Digit1" && !e.ctrlKey && !e.metaKey && !e.altKey) {
1357
+ e.preventDefault();
1358
+ this.actions.zoomToFit();
1359
+ }
1360
+ const nudgeDelta = NUDGE_KEYS[e.key];
1361
+ if (nudgeDelta) {
1362
+ const [dx, dy] = nudgeDelta;
1363
+ if (this.actions.nudge(dx, dy, e.shiftKey)) {
1364
+ e.preventDefault();
1365
+ }
1094
1366
  }
1095
1367
  };
1096
1368
  onKeyUp = (e) => {
@@ -1154,6 +1426,7 @@ var InputHandler = class {
1154
1426
  }
1155
1427
  dispatchToolDown(e) {
1156
1428
  if (!this.toolManager || !this.toolContext) return;
1429
+ this.actions.flushPendingNudge();
1157
1430
  this.historyRecorder?.begin();
1158
1431
  this.isToolActive = true;
1159
1432
  this.toolManager.handlePointerDown(this.toPointerState(e), this.toolContext);
@@ -1174,127 +1447,6 @@ var InputHandler = class {
1174
1447
  this.toolManager.handlePointerUp(this.toPointerState(e), this.toolContext);
1175
1448
  this.historyRecorder?.commit();
1176
1449
  }
1177
- deleteSelected() {
1178
- if (!this.toolManager || !this.toolContext) return;
1179
- const tool = this.toolManager.activeTool;
1180
- if (tool?.name !== "select") return;
1181
- const selectTool = tool;
1182
- const ids = selectTool.selectedIds;
1183
- if (ids.length === 0) return;
1184
- this.historyRecorder?.begin();
1185
- for (const id of ids) {
1186
- this.toolContext.store.remove(id);
1187
- }
1188
- this.historyRecorder?.commit();
1189
- this.toolContext.requestRender();
1190
- }
1191
- handleUndo() {
1192
- if (!this.historyStack || !this.toolContext) return;
1193
- this.historyRecorder?.pause();
1194
- this.historyStack.undo(this.toolContext.store);
1195
- this.historyRecorder?.resume();
1196
- this.toolContext.requestRender();
1197
- }
1198
- handleRedo() {
1199
- if (!this.historyStack || !this.toolContext) return;
1200
- this.historyRecorder?.pause();
1201
- this.historyStack.redo(this.toolContext.store);
1202
- this.historyRecorder?.resume();
1203
- this.toolContext.requestRender();
1204
- }
1205
- handleCopy() {
1206
- if (!this.toolManager || !this.toolContext || this.isToolActive) return;
1207
- const tool = this.toolManager.activeTool;
1208
- if (tool?.name !== "select") return;
1209
- const selectTool = tool;
1210
- const ids = selectTool.selectedIds;
1211
- if (ids.length === 0) return;
1212
- this.clipboard = [];
1213
- for (const id of ids) {
1214
- const el = this.toolContext.store.getById(id);
1215
- if (el) this.clipboard.push(structuredClone(el));
1216
- }
1217
- this.pasteCount = 0;
1218
- }
1219
- handlePaste() {
1220
- if (!this.toolManager || !this.toolContext || this.clipboard.length === 0 || this.isToolActive)
1221
- return;
1222
- const tool = this.toolManager.activeTool;
1223
- if (tool?.name !== "select") return;
1224
- const selectTool = tool;
1225
- this.pasteCount++;
1226
- const offset = this.pasteCount * 20;
1227
- const idMap = /* @__PURE__ */ new Map();
1228
- for (const el of this.clipboard) {
1229
- idMap.set(el.id, createId(el.type));
1230
- }
1231
- const newIds = [];
1232
- this.historyRecorder?.begin();
1233
- for (const el of this.clipboard) {
1234
- const clone = structuredClone(el);
1235
- const newId = idMap.get(el.id);
1236
- if (!newId) continue;
1237
- clone.id = newId;
1238
- clone.position = { x: clone.position.x + offset, y: clone.position.y + offset };
1239
- if (clone.type === "arrow") {
1240
- const arrow = clone;
1241
- arrow.from = { x: arrow.from.x + offset, y: arrow.from.y + offset };
1242
- arrow.to = { x: arrow.to.x + offset, y: arrow.to.y + offset };
1243
- delete arrow.cachedControlPoint;
1244
- if (arrow.fromBinding) {
1245
- const newTarget = idMap.get(arrow.fromBinding.elementId);
1246
- if (newTarget) {
1247
- arrow.fromBinding = { elementId: newTarget };
1248
- } else {
1249
- delete arrow.fromBinding;
1250
- }
1251
- }
1252
- if (arrow.toBinding) {
1253
- const newTarget = idMap.get(arrow.toBinding.elementId);
1254
- if (newTarget) {
1255
- arrow.toBinding = { elementId: newTarget };
1256
- } else {
1257
- delete arrow.toBinding;
1258
- }
1259
- }
1260
- }
1261
- if (this.toolContext.activeLayerId) {
1262
- clone.layerId = this.toolContext.activeLayerId;
1263
- }
1264
- this.toolContext.store.add(clone);
1265
- newIds.push(clone.id);
1266
- }
1267
- this.historyRecorder?.commit();
1268
- selectTool.setSelection(newIds);
1269
- this.toolContext.requestRender();
1270
- }
1271
- handleZOrder(operation) {
1272
- if (!this.toolManager || !this.toolContext) return;
1273
- const tool = this.toolManager.activeTool;
1274
- if (tool?.name !== "select") return;
1275
- const selectTool = tool;
1276
- const ids = selectTool.selectedIds;
1277
- if (ids.length === 0) return;
1278
- this.historyRecorder?.begin();
1279
- for (const id of ids) {
1280
- switch (operation) {
1281
- case "forward":
1282
- this.toolContext.store.bringForward(id);
1283
- break;
1284
- case "backward":
1285
- this.toolContext.store.sendBackward(id);
1286
- break;
1287
- case "front":
1288
- this.toolContext.store.bringToFront(id);
1289
- break;
1290
- case "back":
1291
- this.toolContext.store.sendToBack(id);
1292
- break;
1293
- }
1294
- }
1295
- this.historyRecorder?.commit();
1296
- this.toolContext.requestRender();
1297
- }
1298
1450
  cancelToolIfActive(e) {
1299
1451
  if (this.isToolActive) {
1300
1452
  this.dispatchToolUp(e);
@@ -3247,6 +3399,26 @@ var NoteEditor = class {
3247
3399
  }
3248
3400
  };
3249
3401
 
3402
+ // src/elements/bounds.ts
3403
+ function getElementsBoundingBox(elements) {
3404
+ let minX = Infinity;
3405
+ let minY = Infinity;
3406
+ let maxX = -Infinity;
3407
+ let maxY = -Infinity;
3408
+ let found = false;
3409
+ for (const el of elements) {
3410
+ const b = getElementBounds(el);
3411
+ if (!b) continue;
3412
+ found = true;
3413
+ if (b.x < minX) minX = b.x;
3414
+ if (b.y < minY) minY = b.y;
3415
+ if (b.x + b.w > maxX) maxX = b.x + b.w;
3416
+ if (b.y + b.h > maxY) maxY = b.y + b.h;
3417
+ }
3418
+ if (!found) return null;
3419
+ return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
3420
+ }
3421
+
3250
3422
  // src/tools/tool-manager.ts
3251
3423
  var ToolManager = class {
3252
3424
  tools = /* @__PURE__ */ new Map();
@@ -3463,6 +3635,7 @@ var HistoryRecorder = class {
3463
3635
  }
3464
3636
  recording = true;
3465
3637
  transaction = null;
3638
+ generation = 0;
3466
3639
  updateSnapshots = /* @__PURE__ */ new Map();
3467
3640
  unsubscribers;
3468
3641
  pause() {
@@ -3472,8 +3645,15 @@ var HistoryRecorder = class {
3472
3645
  this.recording = true;
3473
3646
  }
3474
3647
  begin() {
3648
+ if (this.transaction !== null) {
3649
+ this.commit();
3650
+ }
3475
3651
  this.transaction = [];
3476
3652
  this.updateSnapshots.clear();
3653
+ this.generation += 1;
3654
+ }
3655
+ get currentTransactionId() {
3656
+ return this.transaction !== null ? this.generation : null;
3477
3657
  }
3478
3658
  commit() {
3479
3659
  if (!this.transaction) return;
@@ -4660,7 +4840,8 @@ var Viewport = class {
4660
4840
  toolManager: this.toolManager,
4661
4841
  toolContext: this.toolContext,
4662
4842
  historyRecorder: this.historyRecorder,
4663
- historyStack: this.history
4843
+ historyStack: this.history,
4844
+ fitToContent: () => this.fitToContent()
4664
4845
  });
4665
4846
  this.domNodeManager = new DomNodeManager({
4666
4847
  domLayer: this.domLayer,
@@ -4770,6 +4951,13 @@ var Viewport = class {
4770
4951
  this._snapToGrid = enabled;
4771
4952
  this.toolContext.snapToGrid = enabled;
4772
4953
  }
4954
+ fitToContent(padding = 40) {
4955
+ if (this.wrapper.clientWidth === 0 || this.wrapper.clientHeight === 0) return;
4956
+ const visibleElements = this.store.getAll().filter((el) => this.layerManager.isLayerVisible(el.layerId));
4957
+ const bbox = getElementsBoundingBox(visibleElements);
4958
+ if (!bbox) return;
4959
+ this.camera.fitToContent(bbox, this.wrapper.clientWidth, this.wrapper.clientHeight, padding);
4960
+ }
4773
4961
  requestRender() {
4774
4962
  this.renderLoop.requestRender();
4775
4963
  }
@@ -4788,6 +4976,7 @@ var Viewport = class {
4788
4976
  return exportImage(this.store, options, this.layerManager);
4789
4977
  }
4790
4978
  loadState(state) {
4979
+ this.inputHandler.flushPendingHistory();
4791
4980
  this.historyRecorder.pause();
4792
4981
  this.noteEditor.destroy(this.store);
4793
4982
  this.domNodeManager.clearDomNodes();
@@ -4823,7 +5012,11 @@ var Viewport = class {
4823
5012
  loadJSON(json) {
4824
5013
  this.loadState(parseState(json));
4825
5014
  }
5015
+ setTool(name) {
5016
+ this.toolManager.setTool(name, this.toolContext);
5017
+ }
4826
5018
  undo() {
5019
+ this.inputHandler.flushPendingHistory();
4827
5020
  this.historyRecorder.pause();
4828
5021
  const result = this.history.undo(this.store);
4829
5022
  this.historyRecorder.resume();
@@ -4831,6 +5024,7 @@ var Viewport = class {
4831
5024
  return result;
4832
5025
  }
4833
5026
  redo() {
5027
+ this.inputHandler.flushPendingHistory();
4834
5028
  this.historyRecorder.pause();
4835
5029
  const result = this.history.redo(this.store);
4836
5030
  this.historyRecorder.resume();
@@ -5150,26 +5344,6 @@ var Viewport = class {
5150
5344
  }
5151
5345
  };
5152
5346
 
5153
- // src/elements/bounds.ts
5154
- function getElementsBoundingBox(elements) {
5155
- let minX = Infinity;
5156
- let minY = Infinity;
5157
- let maxX = -Infinity;
5158
- let maxY = -Infinity;
5159
- let found = false;
5160
- for (const el of elements) {
5161
- const b = getElementBounds(el);
5162
- if (!b) continue;
5163
- found = true;
5164
- if (b.x < minX) minX = b.x;
5165
- if (b.y < minY) minY = b.y;
5166
- if (b.x + b.w > maxX) maxX = b.x + b.w;
5167
- if (b.y + b.h > maxY) maxY = b.y + b.h;
5168
- }
5169
- if (!found) return null;
5170
- return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
5171
- }
5172
-
5173
5347
  // src/tools/hand-tool.ts
5174
5348
  var HandTool = class {
5175
5349
  name = "hand";
@@ -5661,23 +5835,7 @@ var SelectTool = class {
5661
5835
  });
5662
5836
  }
5663
5837
  }
5664
- const movedNonArrowIds = /* @__PURE__ */ new Set();
5665
- for (const id of this._selectedIds) {
5666
- const el = ctx.store.getById(id);
5667
- if (el && el.type !== "arrow") movedNonArrowIds.add(id);
5668
- }
5669
- if (movedNonArrowIds.size > 0) {
5670
- const updatedArrows = /* @__PURE__ */ new Set();
5671
- for (const id of movedNonArrowIds) {
5672
- const boundArrows = findBoundArrows(id, ctx.store);
5673
- for (const ba of boundArrows) {
5674
- if (updatedArrows.has(ba.id)) continue;
5675
- updatedArrows.add(ba.id);
5676
- const updates = updateBoundArrow(ba, ctx.store);
5677
- if (updates) ctx.store.update(ba.id, updates);
5678
- }
5679
- }
5680
- }
5838
+ this.updateArrowsBoundTo(this._selectedIds, ctx);
5681
5839
  ctx.requestRender();
5682
5840
  return;
5683
5841
  }
@@ -5728,6 +5886,49 @@ var SelectTool = class {
5728
5886
  }
5729
5887
  }
5730
5888
  }
5889
+ updateArrowsBoundTo(ids, ctx) {
5890
+ const movedNonArrowIds = /* @__PURE__ */ new Set();
5891
+ for (const id of ids) {
5892
+ const el = ctx.store.getById(id);
5893
+ if (el && el.type !== "arrow") movedNonArrowIds.add(id);
5894
+ }
5895
+ if (movedNonArrowIds.size === 0) return;
5896
+ const updatedArrows = /* @__PURE__ */ new Set();
5897
+ for (const id of movedNonArrowIds) {
5898
+ const boundArrows = findBoundArrows(id, ctx.store);
5899
+ for (const ba of boundArrows) {
5900
+ if (updatedArrows.has(ba.id)) continue;
5901
+ updatedArrows.add(ba.id);
5902
+ const updates = updateBoundArrow(ba, ctx.store);
5903
+ if (updates) ctx.store.update(ba.id, updates);
5904
+ }
5905
+ }
5906
+ }
5907
+ nudgeSelection(dx, dy, ctx) {
5908
+ let moved = false;
5909
+ for (const id of this._selectedIds) {
5910
+ const el = ctx.store.getById(id);
5911
+ if (!el || el.locked) continue;
5912
+ if (el.type === "arrow") {
5913
+ if (el.fromBinding || el.toBinding) continue;
5914
+ ctx.store.update(id, {
5915
+ position: { x: el.position.x + dx, y: el.position.y + dy },
5916
+ from: { x: el.from.x + dx, y: el.from.y + dy },
5917
+ to: { x: el.to.x + dx, y: el.to.y + dy }
5918
+ });
5919
+ } else {
5920
+ ctx.store.update(id, {
5921
+ position: { x: el.position.x + dx, y: el.position.y + dy }
5922
+ });
5923
+ }
5924
+ moved = true;
5925
+ }
5926
+ if (moved) {
5927
+ this.updateArrowsBoundTo(this._selectedIds, ctx);
5928
+ ctx.requestRender();
5929
+ }
5930
+ return moved;
5931
+ }
5731
5932
  updateHoverCursor(world, ctx) {
5732
5933
  const arrowHit = hitTestArrowHandles(world, this._selectedIds, ctx);
5733
5934
  if (arrowHit) {
@@ -5805,11 +6006,7 @@ var SelectTool = class {
5805
6006
  position: { x, y },
5806
6007
  size: { w, h }
5807
6008
  });
5808
- const boundArrows = findBoundArrows(this.mode.elementId, ctx.store);
5809
- for (const ba of boundArrows) {
5810
- const updates = updateBoundArrow(ba, ctx.store);
5811
- if (updates) ctx.store.update(ba.id, updates);
5812
- }
6009
+ this.updateArrowsBoundTo([this.mode.elementId], ctx);
5813
6010
  ctx.requestRender();
5814
6011
  }
5815
6012
  hitTestResizeHandle(world, ctx) {
@@ -6856,7 +7053,7 @@ var TemplateTool = class {
6856
7053
  };
6857
7054
 
6858
7055
  // src/index.ts
6859
- var VERSION = "0.16.0";
7056
+ var VERSION = "0.18.0";
6860
7057
  // Annotate the CommonJS export names for ESM import in node:
6861
7058
  0 && (module.exports = {
6862
7059
  AddElementCommand,
@@ -6944,3 +7141,4 @@ var VERSION = "0.16.0";
6944
7141
  unbindArrow,
6945
7142
  updateBoundArrow
6946
7143
  });
7144
+ //# sourceMappingURL=index.cjs.map