@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/LICENSE +21 -0
- package/README.md +442 -436
- package/dist/index.cjs +374 -176
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +11 -10
- package/dist/index.d.ts +11 -10
- package/dist/index.js +374 -176
- package/dist/index.js.map +1 -0
- package/package.json +4 -2
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
1338
|
+
this.actions.copy();
|
|
1082
1339
|
}
|
|
1083
1340
|
if ((e.ctrlKey || e.metaKey) && e.key === "v") {
|
|
1084
1341
|
e.preventDefault();
|
|
1085
|
-
this.
|
|
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.
|
|
1350
|
+
this.actions.zOrder(e.ctrlKey || e.metaKey ? "front" : "forward");
|
|
1090
1351
|
}
|
|
1091
1352
|
if (e.key === "[") {
|
|
1092
1353
|
e.preventDefault();
|
|
1093
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|