@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.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) =>
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
1229
|
+
this.actions.copy();
|
|
973
1230
|
}
|
|
974
1231
|
if ((e.ctrlKey || e.metaKey) && e.key === "v") {
|
|
975
1232
|
e.preventDefault();
|
|
976
|
-
this.
|
|
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.
|
|
1241
|
+
this.actions.zOrder(e.ctrlKey || e.metaKey ? "front" : "forward");
|
|
981
1242
|
}
|
|
982
1243
|
if (e.key === "[") {
|
|
983
1244
|
e.preventDefault();
|
|
984
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|