@fieldnotes/core 0.13.0 → 0.14.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 +389 -46
- package/dist/index.d.cts +66 -4
- package/dist/index.d.ts +66 -4
- package/dist/index.js +386 -46
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -29,6 +29,7 @@ __export(index_exports, {
|
|
|
29
29
|
CreateLayerCommand: () => CreateLayerCommand,
|
|
30
30
|
DEFAULT_FONT_SIZE_PRESETS: () => DEFAULT_FONT_SIZE_PRESETS,
|
|
31
31
|
DEFAULT_NOTE_FONT_SIZE: () => DEFAULT_NOTE_FONT_SIZE,
|
|
32
|
+
DoubleTapDetector: () => DoubleTapDetector,
|
|
32
33
|
ElementRenderer: () => ElementRenderer,
|
|
33
34
|
ElementStore: () => ElementStore,
|
|
34
35
|
EraserTool: () => EraserTool,
|
|
@@ -37,6 +38,7 @@ __export(index_exports, {
|
|
|
37
38
|
HistoryRecorder: () => HistoryRecorder,
|
|
38
39
|
HistoryStack: () => HistoryStack,
|
|
39
40
|
ImageTool: () => ImageTool,
|
|
41
|
+
InputFilter: () => InputFilter,
|
|
40
42
|
InputHandler: () => InputHandler,
|
|
41
43
|
LayerManager: () => LayerManager,
|
|
42
44
|
MeasureTool: () => MeasureTool,
|
|
@@ -82,6 +84,7 @@ __export(index_exports, {
|
|
|
82
84
|
getEdgeIntersection: () => getEdgeIntersection,
|
|
83
85
|
getElementBounds: () => getElementBounds,
|
|
84
86
|
getElementCenter: () => getElementCenter,
|
|
87
|
+
getElementsBoundingBox: () => getElementsBoundingBox,
|
|
85
88
|
getHexCellsInCone: () => getHexCellsInCone,
|
|
86
89
|
getHexCellsInLine: () => getHexCellsInLine,
|
|
87
90
|
getHexCellsInRadius: () => getHexCellsInRadius,
|
|
@@ -391,16 +394,24 @@ function sanitizeAttributes(el, tag) {
|
|
|
391
394
|
|
|
392
395
|
// src/core/state-serializer.ts
|
|
393
396
|
var CURRENT_VERSION = 2;
|
|
394
|
-
function exportState(elements, camera, layers = []) {
|
|
395
|
-
|
|
397
|
+
function exportState(elements, camera, layers = [], activeLayerId) {
|
|
398
|
+
const state = {
|
|
396
399
|
version: CURRENT_VERSION,
|
|
397
400
|
camera: {
|
|
398
401
|
position: { ...camera.position },
|
|
399
402
|
zoom: camera.zoom
|
|
400
403
|
},
|
|
401
|
-
elements: elements.map((el) =>
|
|
404
|
+
elements: elements.map((el) => {
|
|
405
|
+
const clone = structuredClone(el);
|
|
406
|
+
if (clone.type === "arrow") {
|
|
407
|
+
delete clone.cachedControlPoint;
|
|
408
|
+
}
|
|
409
|
+
return clone;
|
|
410
|
+
}),
|
|
402
411
|
layers: layers.map((l) => ({ ...l }))
|
|
403
412
|
};
|
|
413
|
+
if (activeLayerId) state.activeLayerId = activeLayerId;
|
|
414
|
+
return state;
|
|
404
415
|
}
|
|
405
416
|
function parseState(json) {
|
|
406
417
|
const data = JSON.parse(json);
|
|
@@ -558,12 +569,14 @@ var AutoSave = class {
|
|
|
558
569
|
this.key = options.key ?? DEFAULT_KEY;
|
|
559
570
|
this.debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
560
571
|
this.layerManager = options.layerManager;
|
|
572
|
+
this.onError = options.onError;
|
|
561
573
|
}
|
|
562
574
|
key;
|
|
563
575
|
debounceMs;
|
|
564
576
|
layerManager;
|
|
565
577
|
timerId = null;
|
|
566
578
|
unsubscribers = [];
|
|
579
|
+
onError;
|
|
567
580
|
start() {
|
|
568
581
|
const schedule = () => this.scheduleSave();
|
|
569
582
|
this.unsubscribers = [
|
|
@@ -611,8 +624,9 @@ var AutoSave = class {
|
|
|
611
624
|
const state = exportState(this.store.snapshot(), this.camera, layers);
|
|
612
625
|
try {
|
|
613
626
|
localStorage.setItem(this.key, JSON.stringify(state));
|
|
614
|
-
} catch {
|
|
627
|
+
} catch (e) {
|
|
615
628
|
console.warn("Auto-save failed: storage quota exceeded. State too large for localStorage.");
|
|
629
|
+
this.onError?.(e instanceof Error ? e : new Error(String(e)));
|
|
616
630
|
}
|
|
617
631
|
}
|
|
618
632
|
};
|
|
@@ -681,6 +695,15 @@ var Camera = class {
|
|
|
681
695
|
h: bottomRight.y - topLeft.y
|
|
682
696
|
};
|
|
683
697
|
}
|
|
698
|
+
fitToContent(boundingBox, canvasWidth, canvasHeight, padding = 40) {
|
|
699
|
+
if (boundingBox.w === 0 && boundingBox.h === 0) return;
|
|
700
|
+
const scaleX = canvasWidth / (boundingBox.w + 2 * padding);
|
|
701
|
+
const scaleY = canvasHeight / (boundingBox.h + 2 * padding);
|
|
702
|
+
this.z = Math.min(this.maxZoom, Math.max(this.minZoom, Math.min(scaleX, scaleY)));
|
|
703
|
+
this.x = (canvasWidth - boundingBox.w * this.z) / 2 - boundingBox.x * this.z;
|
|
704
|
+
this.y = (canvasHeight - boundingBox.h * this.z) / 2 - boundingBox.y * this.z;
|
|
705
|
+
this.notifyPanAndZoom();
|
|
706
|
+
}
|
|
684
707
|
toCSSTransform() {
|
|
685
708
|
return `translate3d(${this.x}px, ${this.y}px, 0) scale(${this.z})`;
|
|
686
709
|
}
|
|
@@ -836,6 +859,67 @@ var Background = class {
|
|
|
836
859
|
}
|
|
837
860
|
};
|
|
838
861
|
|
|
862
|
+
// src/canvas/input-filter.ts
|
|
863
|
+
var InputFilter = class _InputFilter {
|
|
864
|
+
activePenId = null;
|
|
865
|
+
pendingTap = null;
|
|
866
|
+
static MIN_MOVE_DISTANCE = 3;
|
|
867
|
+
filterDown(e) {
|
|
868
|
+
if (e.pointerType === "pen") {
|
|
869
|
+
this.activePenId = e.pointerId;
|
|
870
|
+
return { event: e, action: "dispatch" };
|
|
871
|
+
}
|
|
872
|
+
if (e.pointerType === "touch" && this.activePenId !== null) {
|
|
873
|
+
return { event: e, action: "suppress" };
|
|
874
|
+
}
|
|
875
|
+
if (e.pointerType === "touch") {
|
|
876
|
+
this.pendingTap = { pointerId: e.pointerId, x: e.clientX, y: e.clientY };
|
|
877
|
+
return { event: e, action: "defer" };
|
|
878
|
+
}
|
|
879
|
+
return { event: e, action: "dispatch" };
|
|
880
|
+
}
|
|
881
|
+
filterMove(e) {
|
|
882
|
+
if (e.pointerType === "touch" && this.activePenId !== null) {
|
|
883
|
+
return { event: e, action: "suppress" };
|
|
884
|
+
}
|
|
885
|
+
if (this.pendingTap && e.pointerId === this.pendingTap.pointerId) {
|
|
886
|
+
const dx = e.clientX - this.pendingTap.x;
|
|
887
|
+
const dy = e.clientY - this.pendingTap.y;
|
|
888
|
+
if (dx * dx + dy * dy > _InputFilter.MIN_MOVE_DISTANCE * _InputFilter.MIN_MOVE_DISTANCE) {
|
|
889
|
+
this.pendingTap = null;
|
|
890
|
+
return { event: e, action: "dispatch" };
|
|
891
|
+
}
|
|
892
|
+
return { event: e, action: "suppress" };
|
|
893
|
+
}
|
|
894
|
+
return { event: e, action: "dispatch" };
|
|
895
|
+
}
|
|
896
|
+
filterUp(e) {
|
|
897
|
+
if (e.pointerId === this.activePenId) {
|
|
898
|
+
this.activePenId = null;
|
|
899
|
+
return { event: e, action: "dispatch" };
|
|
900
|
+
}
|
|
901
|
+
if (e.pointerType === "touch" && this.activePenId !== null) {
|
|
902
|
+
return { event: e, action: "suppress" };
|
|
903
|
+
}
|
|
904
|
+
if (this.pendingTap && e.pointerId === this.pendingTap.pointerId) {
|
|
905
|
+
const tap = { x: this.pendingTap.x, y: this.pendingTap.y };
|
|
906
|
+
this.pendingTap = null;
|
|
907
|
+
return { event: e, action: "dispatch", pendingTap: tap };
|
|
908
|
+
}
|
|
909
|
+
return { event: e, action: "dispatch" };
|
|
910
|
+
}
|
|
911
|
+
reset() {
|
|
912
|
+
this.activePenId = null;
|
|
913
|
+
this.pendingTap = null;
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
|
|
917
|
+
// src/elements/create-id.ts
|
|
918
|
+
var counter = 0;
|
|
919
|
+
function createId(prefix) {
|
|
920
|
+
return `${prefix}_${Date.now().toString(36)}_${(counter++).toString(36)}`;
|
|
921
|
+
}
|
|
922
|
+
|
|
839
923
|
// src/canvas/input-handler.ts
|
|
840
924
|
var ZOOM_SENSITIVITY = 1e-3;
|
|
841
925
|
var MIDDLE_BUTTON = 1;
|
|
@@ -861,13 +945,21 @@ var InputHandler = class {
|
|
|
861
945
|
historyRecorder;
|
|
862
946
|
historyStack;
|
|
863
947
|
isToolActive = false;
|
|
948
|
+
lastPointerEvent = null;
|
|
949
|
+
inputFilter = new InputFilter();
|
|
950
|
+
deferredDown = null;
|
|
864
951
|
abortController = new AbortController();
|
|
952
|
+
clipboard = [];
|
|
953
|
+
pasteCount = 0;
|
|
865
954
|
setToolManager(toolManager, toolContext) {
|
|
866
955
|
this.toolManager = toolManager;
|
|
867
956
|
this.toolContext = toolContext;
|
|
868
957
|
}
|
|
869
958
|
destroy() {
|
|
870
959
|
this.abortController.abort();
|
|
960
|
+
this.inputFilter.reset();
|
|
961
|
+
this.deferredDown = null;
|
|
962
|
+
this.lastPointerEvent = null;
|
|
871
963
|
}
|
|
872
964
|
bind() {
|
|
873
965
|
const opts = { signal: this.abortController.signal };
|
|
@@ -903,11 +995,18 @@ var InputHandler = class {
|
|
|
903
995
|
this.lastPointer = { x: e.clientX, y: e.clientY };
|
|
904
996
|
return;
|
|
905
997
|
}
|
|
906
|
-
if (this.activePointers.size === 1 && e.button === 0) {
|
|
998
|
+
if (this.activePointers.size === 1 && (e.button === 0 || e.pointerType === "touch" || e.pointerType === "pen")) {
|
|
999
|
+
const result = this.inputFilter.filterDown(e);
|
|
1000
|
+
if (result.action === "suppress") return;
|
|
1001
|
+
if (result.action === "defer") {
|
|
1002
|
+
this.deferredDown = e;
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
907
1005
|
this.dispatchToolDown(e);
|
|
908
1006
|
}
|
|
909
1007
|
};
|
|
910
1008
|
onPointerMove = (e) => {
|
|
1009
|
+
this.lastPointerEvent = e;
|
|
911
1010
|
if (this.activePointers.has(e.pointerId)) {
|
|
912
1011
|
this.activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
|
|
913
1012
|
}
|
|
@@ -922,13 +1021,26 @@ var InputHandler = class {
|
|
|
922
1021
|
this.camera.pan(dx, dy);
|
|
923
1022
|
return;
|
|
924
1023
|
}
|
|
925
|
-
if (this.
|
|
1024
|
+
if (e.pointerType === "pen" && !this.activePointers.has(e.pointerId)) {
|
|
1025
|
+
this.dispatchToolHover(e);
|
|
1026
|
+
} else if (this.isToolActive) {
|
|
926
1027
|
this.dispatchToolMove(e);
|
|
1028
|
+
} else if (this.deferredDown) {
|
|
1029
|
+
const result = this.inputFilter.filterMove(e);
|
|
1030
|
+
if (result.action === "dispatch") {
|
|
1031
|
+
this.dispatchToolDown(this.deferredDown);
|
|
1032
|
+
this.deferredDown = null;
|
|
1033
|
+
this.dispatchToolMove(e);
|
|
1034
|
+
}
|
|
927
1035
|
} else if (this.activePointers.size === 0) {
|
|
928
1036
|
this.dispatchToolHover(e);
|
|
929
1037
|
}
|
|
930
1038
|
};
|
|
931
1039
|
onPointerUp = (e) => {
|
|
1040
|
+
try {
|
|
1041
|
+
this.element.releasePointerCapture(e.pointerId);
|
|
1042
|
+
} catch {
|
|
1043
|
+
}
|
|
932
1044
|
this.activePointers.delete(e.pointerId);
|
|
933
1045
|
if (this.activePointers.size < 2) {
|
|
934
1046
|
this.lastPinchDistance = 0;
|
|
@@ -936,9 +1048,16 @@ var InputHandler = class {
|
|
|
936
1048
|
if (this.isPanning && this.activePointers.size === 0) {
|
|
937
1049
|
this.isPanning = false;
|
|
938
1050
|
}
|
|
1051
|
+
const upResult = this.inputFilter.filterUp(e);
|
|
939
1052
|
if (this.isToolActive) {
|
|
940
1053
|
this.dispatchToolUp(e);
|
|
941
1054
|
this.isToolActive = false;
|
|
1055
|
+
} else if (this.deferredDown && upResult.pendingTap) {
|
|
1056
|
+
this.dispatchToolDown(this.deferredDown);
|
|
1057
|
+
this.dispatchToolUp(e);
|
|
1058
|
+
this.deferredDown = null;
|
|
1059
|
+
} else {
|
|
1060
|
+
this.deferredDown = null;
|
|
942
1061
|
}
|
|
943
1062
|
};
|
|
944
1063
|
onKeyDown = (e) => {
|
|
@@ -957,13 +1076,30 @@ var InputHandler = class {
|
|
|
957
1076
|
e.preventDefault();
|
|
958
1077
|
this.handleRedo();
|
|
959
1078
|
}
|
|
1079
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "c") {
|
|
1080
|
+
e.preventDefault();
|
|
1081
|
+
this.handleCopy();
|
|
1082
|
+
}
|
|
1083
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "v") {
|
|
1084
|
+
e.preventDefault();
|
|
1085
|
+
this.handlePaste();
|
|
1086
|
+
}
|
|
960
1087
|
};
|
|
961
1088
|
onKeyUp = (e) => {
|
|
962
1089
|
if (e.key === " ") {
|
|
963
1090
|
this.spaceHeld = false;
|
|
1091
|
+
if (this.activePointers.size === 0) {
|
|
1092
|
+
if (this.lastPointerEvent) {
|
|
1093
|
+
this.dispatchToolHover(this.lastPointerEvent);
|
|
1094
|
+
} else {
|
|
1095
|
+
this.toolContext?.setCursor?.("default");
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
964
1098
|
}
|
|
965
1099
|
};
|
|
966
1100
|
startPinch() {
|
|
1101
|
+
this.inputFilter.reset();
|
|
1102
|
+
this.deferredDown = null;
|
|
967
1103
|
this.isPanning = true;
|
|
968
1104
|
const [a, b] = this.getPinchPoints();
|
|
969
1105
|
this.lastPinchDistance = this.distance(a, b);
|
|
@@ -1003,7 +1139,9 @@ var InputHandler = class {
|
|
|
1003
1139
|
return {
|
|
1004
1140
|
x: e.clientX - rect.left,
|
|
1005
1141
|
y: e.clientY - rect.top,
|
|
1006
|
-
pressure: e.pressure
|
|
1142
|
+
pressure: e.pressure,
|
|
1143
|
+
pointerType: e.pointerType === "touch" || e.pointerType === "pen" ? e.pointerType : "mouse",
|
|
1144
|
+
shiftKey: e.shiftKey
|
|
1007
1145
|
};
|
|
1008
1146
|
}
|
|
1009
1147
|
dispatchToolDown(e) {
|
|
@@ -1056,11 +1194,120 @@ var InputHandler = class {
|
|
|
1056
1194
|
this.historyRecorder?.resume();
|
|
1057
1195
|
this.toolContext.requestRender();
|
|
1058
1196
|
}
|
|
1197
|
+
handleCopy() {
|
|
1198
|
+
if (!this.toolManager || !this.toolContext || this.isToolActive) return;
|
|
1199
|
+
const tool = this.toolManager.activeTool;
|
|
1200
|
+
if (tool?.name !== "select") return;
|
|
1201
|
+
const selectTool = tool;
|
|
1202
|
+
const ids = selectTool.selectedIds;
|
|
1203
|
+
if (ids.length === 0) return;
|
|
1204
|
+
this.clipboard = [];
|
|
1205
|
+
for (const id of ids) {
|
|
1206
|
+
const el = this.toolContext.store.getById(id);
|
|
1207
|
+
if (el) this.clipboard.push(structuredClone(el));
|
|
1208
|
+
}
|
|
1209
|
+
this.pasteCount = 0;
|
|
1210
|
+
}
|
|
1211
|
+
handlePaste() {
|
|
1212
|
+
if (!this.toolManager || !this.toolContext || this.clipboard.length === 0 || this.isToolActive)
|
|
1213
|
+
return;
|
|
1214
|
+
const tool = this.toolManager.activeTool;
|
|
1215
|
+
if (tool?.name !== "select") return;
|
|
1216
|
+
const selectTool = tool;
|
|
1217
|
+
this.pasteCount++;
|
|
1218
|
+
const offset = this.pasteCount * 20;
|
|
1219
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
1220
|
+
for (const el of this.clipboard) {
|
|
1221
|
+
idMap.set(el.id, createId(el.type));
|
|
1222
|
+
}
|
|
1223
|
+
const newIds = [];
|
|
1224
|
+
this.historyRecorder?.begin();
|
|
1225
|
+
for (const el of this.clipboard) {
|
|
1226
|
+
const clone = structuredClone(el);
|
|
1227
|
+
const newId = idMap.get(el.id);
|
|
1228
|
+
if (!newId) continue;
|
|
1229
|
+
clone.id = newId;
|
|
1230
|
+
clone.position = { x: clone.position.x + offset, y: clone.position.y + offset };
|
|
1231
|
+
if (clone.type === "arrow") {
|
|
1232
|
+
const arrow = clone;
|
|
1233
|
+
arrow.from = { x: arrow.from.x + offset, y: arrow.from.y + offset };
|
|
1234
|
+
arrow.to = { x: arrow.to.x + offset, y: arrow.to.y + offset };
|
|
1235
|
+
delete arrow.cachedControlPoint;
|
|
1236
|
+
if (arrow.fromBinding) {
|
|
1237
|
+
const newTarget = idMap.get(arrow.fromBinding.elementId);
|
|
1238
|
+
if (newTarget) {
|
|
1239
|
+
arrow.fromBinding = { elementId: newTarget };
|
|
1240
|
+
} else {
|
|
1241
|
+
delete arrow.fromBinding;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
if (arrow.toBinding) {
|
|
1245
|
+
const newTarget = idMap.get(arrow.toBinding.elementId);
|
|
1246
|
+
if (newTarget) {
|
|
1247
|
+
arrow.toBinding = { elementId: newTarget };
|
|
1248
|
+
} else {
|
|
1249
|
+
delete arrow.toBinding;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
if (this.toolContext.activeLayerId) {
|
|
1254
|
+
clone.layerId = this.toolContext.activeLayerId;
|
|
1255
|
+
}
|
|
1256
|
+
this.toolContext.store.add(clone);
|
|
1257
|
+
newIds.push(clone.id);
|
|
1258
|
+
}
|
|
1259
|
+
this.historyRecorder?.commit();
|
|
1260
|
+
selectTool.setSelection(newIds);
|
|
1261
|
+
this.toolContext.requestRender();
|
|
1262
|
+
}
|
|
1059
1263
|
cancelToolIfActive(e) {
|
|
1060
1264
|
if (this.isToolActive) {
|
|
1061
1265
|
this.dispatchToolUp(e);
|
|
1062
1266
|
this.isToolActive = false;
|
|
1063
1267
|
}
|
|
1268
|
+
this.deferredDown = null;
|
|
1269
|
+
}
|
|
1270
|
+
};
|
|
1271
|
+
|
|
1272
|
+
// src/canvas/double-tap-detector.ts
|
|
1273
|
+
var DEFAULT_TIMEOUT = 300;
|
|
1274
|
+
var DEFAULT_MAX_DISTANCE = 20;
|
|
1275
|
+
var DoubleTapDetector = class {
|
|
1276
|
+
timeout;
|
|
1277
|
+
maxDistance;
|
|
1278
|
+
lastTapTime = 0;
|
|
1279
|
+
lastTapX = 0;
|
|
1280
|
+
lastTapY = 0;
|
|
1281
|
+
hasPendingTap = false;
|
|
1282
|
+
constructor(options) {
|
|
1283
|
+
this.timeout = options?.timeout ?? DEFAULT_TIMEOUT;
|
|
1284
|
+
this.maxDistance = options?.maxDistance ?? DEFAULT_MAX_DISTANCE;
|
|
1285
|
+
}
|
|
1286
|
+
feed(e) {
|
|
1287
|
+
const now = Date.now();
|
|
1288
|
+
const x = e.clientX;
|
|
1289
|
+
const y = e.clientY;
|
|
1290
|
+
if (this.hasPendingTap) {
|
|
1291
|
+
const elapsed = now - this.lastTapTime;
|
|
1292
|
+
const dx = x - this.lastTapX;
|
|
1293
|
+
const dy = y - this.lastTapY;
|
|
1294
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
1295
|
+
if (elapsed <= this.timeout && dist <= this.maxDistance) {
|
|
1296
|
+
this.reset();
|
|
1297
|
+
return true;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
this.lastTapTime = now;
|
|
1301
|
+
this.lastTapX = x;
|
|
1302
|
+
this.lastTapY = y;
|
|
1303
|
+
this.hasPendingTap = true;
|
|
1304
|
+
return false;
|
|
1305
|
+
}
|
|
1306
|
+
reset() {
|
|
1307
|
+
this.hasPendingTap = false;
|
|
1308
|
+
this.lastTapTime = 0;
|
|
1309
|
+
this.lastTapX = 0;
|
|
1310
|
+
this.lastTapY = 0;
|
|
1064
1311
|
}
|
|
1065
1312
|
};
|
|
1066
1313
|
|
|
@@ -1308,19 +1555,23 @@ var ElementStore = class {
|
|
|
1308
1555
|
bus = new EventBus();
|
|
1309
1556
|
layerOrderMap = /* @__PURE__ */ new Map();
|
|
1310
1557
|
spatialIndex = new Quadtree({ x: -1e5, y: -1e5, w: 2e5, h: 2e5 });
|
|
1558
|
+
sortedCache = null;
|
|
1311
1559
|
get count() {
|
|
1312
1560
|
return this.elements.size;
|
|
1313
1561
|
}
|
|
1314
1562
|
setLayerOrder(order) {
|
|
1315
1563
|
this.layerOrderMap = new Map(order);
|
|
1564
|
+
this.sortedCache = null;
|
|
1316
1565
|
}
|
|
1317
1566
|
getAll() {
|
|
1318
|
-
|
|
1567
|
+
if (this.sortedCache) return this.sortedCache;
|
|
1568
|
+
this.sortedCache = [...this.elements.values()].sort((a, b) => {
|
|
1319
1569
|
const layerA = this.layerOrderMap.get(a.layerId) ?? 0;
|
|
1320
1570
|
const layerB = this.layerOrderMap.get(b.layerId) ?? 0;
|
|
1321
1571
|
if (layerA !== layerB) return layerA - layerB;
|
|
1322
1572
|
return a.zIndex - b.zIndex;
|
|
1323
1573
|
});
|
|
1574
|
+
return this.sortedCache;
|
|
1324
1575
|
}
|
|
1325
1576
|
getById(id) {
|
|
1326
1577
|
return this.elements.get(id);
|
|
@@ -1331,6 +1582,7 @@ var ElementStore = class {
|
|
|
1331
1582
|
);
|
|
1332
1583
|
}
|
|
1333
1584
|
add(element) {
|
|
1585
|
+
this.sortedCache = null;
|
|
1334
1586
|
this.elements.set(element.id, element);
|
|
1335
1587
|
const bounds = getElementBounds(element);
|
|
1336
1588
|
if (bounds) this.spatialIndex.insert(element.id, bounds);
|
|
@@ -1339,11 +1591,15 @@ var ElementStore = class {
|
|
|
1339
1591
|
update(id, partial) {
|
|
1340
1592
|
const existing = this.elements.get(id);
|
|
1341
1593
|
if (!existing) return;
|
|
1594
|
+
this.sortedCache = null;
|
|
1342
1595
|
const updated = { ...existing, ...partial, id: existing.id, type: existing.type };
|
|
1343
1596
|
if (updated.type === "arrow") {
|
|
1344
1597
|
const arrow = updated;
|
|
1345
1598
|
arrow.cachedControlPoint = getArrowControlPoint(arrow.from, arrow.to, arrow.bend);
|
|
1346
1599
|
}
|
|
1600
|
+
if (updated.type === "note" && "text" in partial) {
|
|
1601
|
+
updated.text = sanitizeNoteHtml(updated.text);
|
|
1602
|
+
}
|
|
1347
1603
|
this.elements.set(id, updated);
|
|
1348
1604
|
const newBounds = getElementBounds(updated);
|
|
1349
1605
|
if (newBounds) {
|
|
@@ -1354,11 +1610,13 @@ var ElementStore = class {
|
|
|
1354
1610
|
remove(id) {
|
|
1355
1611
|
const element = this.elements.get(id);
|
|
1356
1612
|
if (!element) return;
|
|
1613
|
+
this.sortedCache = null;
|
|
1357
1614
|
this.elements.delete(id);
|
|
1358
1615
|
this.spatialIndex.remove(id);
|
|
1359
1616
|
this.bus.emit("remove", element);
|
|
1360
1617
|
}
|
|
1361
1618
|
clear() {
|
|
1619
|
+
this.sortedCache = null;
|
|
1362
1620
|
this.elements.clear();
|
|
1363
1621
|
this.spatialIndex.clear();
|
|
1364
1622
|
this.bus.emit("clear", null);
|
|
@@ -1367,6 +1625,7 @@ var ElementStore = class {
|
|
|
1367
1625
|
return this.getAll().map((el) => ({ ...el }));
|
|
1368
1626
|
}
|
|
1369
1627
|
loadSnapshot(elements) {
|
|
1628
|
+
this.sortedCache = null;
|
|
1370
1629
|
this.elements.clear();
|
|
1371
1630
|
this.spatialIndex.clear();
|
|
1372
1631
|
for (const el of elements) {
|
|
@@ -1374,6 +1633,10 @@ var ElementStore = class {
|
|
|
1374
1633
|
const bounds = getElementBounds(el);
|
|
1375
1634
|
if (bounds) this.spatialIndex.insert(el.id, bounds);
|
|
1376
1635
|
}
|
|
1636
|
+
this.bus.emit("clear", null);
|
|
1637
|
+
for (const el of elements) {
|
|
1638
|
+
this.bus.emit("add", el);
|
|
1639
|
+
}
|
|
1377
1640
|
}
|
|
1378
1641
|
queryRect(rect) {
|
|
1379
1642
|
const ids = this.spatialIndex.query(rect);
|
|
@@ -2423,12 +2686,6 @@ var ElementRenderer = class {
|
|
|
2423
2686
|
}
|
|
2424
2687
|
};
|
|
2425
2688
|
|
|
2426
|
-
// src/elements/create-id.ts
|
|
2427
|
-
var counter = 0;
|
|
2428
|
-
function createId(prefix) {
|
|
2429
|
-
return `${prefix}_${Date.now().toString(36)}_${(counter++).toString(36)}`;
|
|
2430
|
-
}
|
|
2431
|
-
|
|
2432
2689
|
// src/elements/element-factory.ts
|
|
2433
2690
|
var DEFAULT_NOTE_FONT_SIZE = 18;
|
|
2434
2691
|
function createStroke(input) {
|
|
@@ -2454,7 +2711,7 @@ function createNote(input) {
|
|
|
2454
2711
|
locked: input.locked ?? false,
|
|
2455
2712
|
layerId: input.layerId ?? "",
|
|
2456
2713
|
size: input.size ?? { w: 200, h: 100 },
|
|
2457
|
-
text: input.text ?? "",
|
|
2714
|
+
text: sanitizeNoteHtml(input.text ?? ""),
|
|
2458
2715
|
backgroundColor: input.backgroundColor ?? "#ffeb3b",
|
|
2459
2716
|
textColor: input.textColor ?? "#000000",
|
|
2460
2717
|
fontSize: input.fontSize ?? DEFAULT_NOTE_FONT_SIZE
|
|
@@ -2503,6 +2760,7 @@ function createHtmlElement(input) {
|
|
|
2503
2760
|
size: input.size
|
|
2504
2761
|
};
|
|
2505
2762
|
if (input.domId) el.domId = input.domId;
|
|
2763
|
+
if (input.interactive) el.interactive = input.interactive;
|
|
2506
2764
|
return el;
|
|
2507
2765
|
}
|
|
2508
2766
|
function createShape(input) {
|
|
@@ -2615,7 +2873,7 @@ function getActiveFormats() {
|
|
|
2615
2873
|
}
|
|
2616
2874
|
|
|
2617
2875
|
// src/elements/note-toolbar.ts
|
|
2618
|
-
var TOOLBAR_HEIGHT =
|
|
2876
|
+
var TOOLBAR_HEIGHT = 52;
|
|
2619
2877
|
var TOOLBAR_GAP = 4;
|
|
2620
2878
|
var FORMAT_BUTTONS = [
|
|
2621
2879
|
{ label: "B", format: "bold", command: "bold" },
|
|
@@ -2702,9 +2960,9 @@ var NoteToolbar = class {
|
|
|
2702
2960
|
fontWeight: config.format === "bold" ? "bold" : "normal",
|
|
2703
2961
|
fontStyle: config.format === "italic" ? "italic" : "normal",
|
|
2704
2962
|
textDecoration: config.format === "underline" ? "underline" : config.format === "strikethrough" ? "line-through" : "none",
|
|
2705
|
-
minWidth: "
|
|
2706
|
-
height: "
|
|
2707
|
-
lineHeight: "
|
|
2963
|
+
minWidth: "44px",
|
|
2964
|
+
height: "44px",
|
|
2965
|
+
lineHeight: "44px"
|
|
2708
2966
|
});
|
|
2709
2967
|
btn.addEventListener("pointerdown", (e) => {
|
|
2710
2968
|
e.preventDefault();
|
|
@@ -2722,7 +2980,7 @@ var NoteToolbar = class {
|
|
|
2722
2980
|
cursor: "pointer",
|
|
2723
2981
|
padding: "2px",
|
|
2724
2982
|
fontSize: "12px",
|
|
2725
|
-
height: "
|
|
2983
|
+
height: "44px",
|
|
2726
2984
|
marginLeft: "4px"
|
|
2727
2985
|
});
|
|
2728
2986
|
for (const preset of this.fontSizePresets) {
|
|
@@ -3720,10 +3978,13 @@ var DomNodeManager = class {
|
|
|
3720
3978
|
wordWrap: "break-word"
|
|
3721
3979
|
});
|
|
3722
3980
|
node.innerHTML = element.text || "";
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3981
|
+
const detector = new DoubleTapDetector();
|
|
3982
|
+
node.addEventListener("pointerup", (e) => {
|
|
3983
|
+
if (detector.feed(e)) {
|
|
3984
|
+
e.stopPropagation();
|
|
3985
|
+
const id = node.dataset["elementId"];
|
|
3986
|
+
if (id) this.onEditRequest(id);
|
|
3987
|
+
}
|
|
3727
3988
|
});
|
|
3728
3989
|
}
|
|
3729
3990
|
if (!this.isEditingElement(element.id)) {
|
|
@@ -3736,15 +3997,19 @@ var DomNodeManager = class {
|
|
|
3736
3997
|
node.style.fontSize = `${element.fontSize ?? DEFAULT_NOTE_FONT_SIZE}px`;
|
|
3737
3998
|
}
|
|
3738
3999
|
}
|
|
3739
|
-
if (element.type === "html"
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
4000
|
+
if (element.type === "html") {
|
|
4001
|
+
if (!node.dataset["initialized"]) {
|
|
4002
|
+
const content = this.htmlContent.get(element.id);
|
|
4003
|
+
if (content) {
|
|
4004
|
+
node.dataset["initialized"] = "true";
|
|
4005
|
+
Object.assign(node.style, {
|
|
4006
|
+
overflow: "hidden",
|
|
4007
|
+
pointerEvents: element.interactive ? "auto" : "none"
|
|
4008
|
+
});
|
|
4009
|
+
node.appendChild(content);
|
|
4010
|
+
}
|
|
4011
|
+
} else {
|
|
4012
|
+
node.style.pointerEvents = element.interactive ? "auto" : "none";
|
|
3748
4013
|
}
|
|
3749
4014
|
}
|
|
3750
4015
|
if (element.type === "text") {
|
|
@@ -3766,10 +4031,13 @@ var DomNodeManager = class {
|
|
|
3766
4031
|
lineHeight: "1.4"
|
|
3767
4032
|
});
|
|
3768
4033
|
node.textContent = element.text || "";
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
4034
|
+
const detector = new DoubleTapDetector();
|
|
4035
|
+
node.addEventListener("pointerup", (e) => {
|
|
4036
|
+
if (detector.feed(e)) {
|
|
4037
|
+
e.stopPropagation();
|
|
4038
|
+
const id = node.dataset["elementId"];
|
|
4039
|
+
if (id) this.onEditRequest(id);
|
|
4040
|
+
}
|
|
3773
4041
|
});
|
|
3774
4042
|
}
|
|
3775
4043
|
if (!this.isEditingElement(element.id)) {
|
|
@@ -4258,7 +4526,8 @@ var Viewport = class {
|
|
|
4258
4526
|
this.toolContext.activeLayerId = this.layerManager.activeLayerId;
|
|
4259
4527
|
this.requestRender();
|
|
4260
4528
|
});
|
|
4261
|
-
this.wrapper.addEventListener("
|
|
4529
|
+
this.wrapper.addEventListener("pointerdown", this.onTapDown);
|
|
4530
|
+
this.wrapper.addEventListener("pointerup", this.onDoubleTap);
|
|
4262
4531
|
this.wrapper.addEventListener("dragover", this.onDragOver);
|
|
4263
4532
|
this.wrapper.addEventListener("drop", this.onDrop);
|
|
4264
4533
|
this.observeResize();
|
|
@@ -4289,6 +4558,9 @@ var Viewport = class {
|
|
|
4289
4558
|
domNodeManager;
|
|
4290
4559
|
interactMode;
|
|
4291
4560
|
gridChangeListeners = /* @__PURE__ */ new Set();
|
|
4561
|
+
doubleTapDetector = new DoubleTapDetector();
|
|
4562
|
+
tapDownX = 0;
|
|
4563
|
+
tapDownY = 0;
|
|
4292
4564
|
get ctx() {
|
|
4293
4565
|
return this.canvasEl.getContext("2d");
|
|
4294
4566
|
}
|
|
@@ -4303,7 +4575,12 @@ var Viewport = class {
|
|
|
4303
4575
|
this.renderLoop.requestRender();
|
|
4304
4576
|
}
|
|
4305
4577
|
exportState() {
|
|
4306
|
-
return exportState(
|
|
4578
|
+
return exportState(
|
|
4579
|
+
this.store.snapshot(),
|
|
4580
|
+
this.camera,
|
|
4581
|
+
this.layerManager.snapshot(),
|
|
4582
|
+
this.layerManager.activeLayerId
|
|
4583
|
+
);
|
|
4307
4584
|
}
|
|
4308
4585
|
exportJSON() {
|
|
4309
4586
|
return JSON.stringify(this.exportState());
|
|
@@ -4319,6 +4596,9 @@ var Viewport = class {
|
|
|
4319
4596
|
if (state.layers && state.layers.length > 0) {
|
|
4320
4597
|
this.layerManager.loadSnapshot(state.layers);
|
|
4321
4598
|
}
|
|
4599
|
+
if (state.activeLayerId) {
|
|
4600
|
+
this.layerManager.setActiveLayer(state.activeLayerId);
|
|
4601
|
+
}
|
|
4322
4602
|
this.domNodeManager.reattachHtmlContent(this.store);
|
|
4323
4603
|
this.history.clear();
|
|
4324
4604
|
this.historyRecorder.resume();
|
|
@@ -4426,7 +4706,8 @@ var Viewport = class {
|
|
|
4426
4706
|
this.interactMode.destroy();
|
|
4427
4707
|
this.noteEditor.destroy(this.store);
|
|
4428
4708
|
this.historyRecorder.destroy();
|
|
4429
|
-
this.wrapper.removeEventListener("
|
|
4709
|
+
this.wrapper.removeEventListener("pointerdown", this.onTapDown);
|
|
4710
|
+
this.wrapper.removeEventListener("pointerup", this.onDoubleTap);
|
|
4430
4711
|
this.wrapper.removeEventListener("dragover", this.onDragOver);
|
|
4431
4712
|
this.wrapper.removeEventListener("drop", this.onDrop);
|
|
4432
4713
|
this.inputHandler.destroy();
|
|
@@ -4464,7 +4745,17 @@ var Viewport = class {
|
|
|
4464
4745
|
}
|
|
4465
4746
|
}
|
|
4466
4747
|
}
|
|
4467
|
-
|
|
4748
|
+
onTapDown = (e) => {
|
|
4749
|
+
this.tapDownX = e.clientX;
|
|
4750
|
+
this.tapDownY = e.clientY;
|
|
4751
|
+
};
|
|
4752
|
+
onDoubleTap = (e) => {
|
|
4753
|
+
const dx = e.clientX - this.tapDownX;
|
|
4754
|
+
const dy = e.clientY - this.tapDownY;
|
|
4755
|
+
const moved = Math.sqrt(dx * dx + dy * dy);
|
|
4756
|
+
if (moved > 10) return;
|
|
4757
|
+
if (!this.doubleTapDetector.feed(e)) return;
|
|
4758
|
+
if (typeof document.elementFromPoint !== "function") return;
|
|
4468
4759
|
const el = document.elementFromPoint(e.clientX, e.clientY);
|
|
4469
4760
|
const nodeEl = el?.closest("[data-element-id]");
|
|
4470
4761
|
if (nodeEl) {
|
|
@@ -4561,7 +4852,10 @@ var Viewport = class {
|
|
|
4561
4852
|
position: "relative",
|
|
4562
4853
|
width: "100%",
|
|
4563
4854
|
height: "100%",
|
|
4564
|
-
overflow: "hidden"
|
|
4855
|
+
overflow: "hidden",
|
|
4856
|
+
overscrollBehavior: "none",
|
|
4857
|
+
userSelect: "none",
|
|
4858
|
+
webkitUserSelect: "none"
|
|
4565
4859
|
});
|
|
4566
4860
|
return el;
|
|
4567
4861
|
}
|
|
@@ -4624,6 +4918,26 @@ var Viewport = class {
|
|
|
4624
4918
|
}
|
|
4625
4919
|
};
|
|
4626
4920
|
|
|
4921
|
+
// src/elements/bounds.ts
|
|
4922
|
+
function getElementsBoundingBox(elements) {
|
|
4923
|
+
let minX = Infinity;
|
|
4924
|
+
let minY = Infinity;
|
|
4925
|
+
let maxX = -Infinity;
|
|
4926
|
+
let maxY = -Infinity;
|
|
4927
|
+
let found = false;
|
|
4928
|
+
for (const el of elements) {
|
|
4929
|
+
const b = getElementBounds(el);
|
|
4930
|
+
if (!b) continue;
|
|
4931
|
+
found = true;
|
|
4932
|
+
if (b.x < minX) minX = b.x;
|
|
4933
|
+
if (b.y < minY) minY = b.y;
|
|
4934
|
+
if (b.x + b.w > maxX) maxX = b.x + b.w;
|
|
4935
|
+
if (b.y + b.h > maxY) maxY = b.y + b.h;
|
|
4936
|
+
}
|
|
4937
|
+
if (!found) return null;
|
|
4938
|
+
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
4939
|
+
}
|
|
4940
|
+
|
|
4627
4941
|
// src/tools/hand-tool.ts
|
|
4628
4942
|
var HandTool = class {
|
|
4629
4943
|
name = "hand";
|
|
@@ -4976,9 +5290,15 @@ var SelectTool = class {
|
|
|
4976
5290
|
lastWorld = { x: 0, y: 0 };
|
|
4977
5291
|
currentWorld = { x: 0, y: 0 };
|
|
4978
5292
|
ctx = null;
|
|
5293
|
+
pendingSingleSelectId = null;
|
|
5294
|
+
hasDragged = false;
|
|
4979
5295
|
get selectedIds() {
|
|
4980
5296
|
return [...this._selectedIds];
|
|
4981
5297
|
}
|
|
5298
|
+
setSelection(ids) {
|
|
5299
|
+
this._selectedIds = ids;
|
|
5300
|
+
this.ctx?.requestRender();
|
|
5301
|
+
}
|
|
4982
5302
|
get isMarqueeActive() {
|
|
4983
5303
|
return this.mode.type === "marquee";
|
|
4984
5304
|
}
|
|
@@ -5027,13 +5347,27 @@ var SelectTool = class {
|
|
|
5027
5347
|
return;
|
|
5028
5348
|
}
|
|
5029
5349
|
}
|
|
5350
|
+
this.pendingSingleSelectId = null;
|
|
5351
|
+
this.hasDragged = false;
|
|
5030
5352
|
const hit = this.hitTest(world, ctx);
|
|
5031
5353
|
if (hit) {
|
|
5032
5354
|
const alreadySelected = this._selectedIds.includes(hit.id);
|
|
5033
|
-
if (
|
|
5034
|
-
|
|
5355
|
+
if (state.shiftKey) {
|
|
5356
|
+
if (alreadySelected) {
|
|
5357
|
+
this._selectedIds = this._selectedIds.filter((id) => id !== hit.id);
|
|
5358
|
+
this.mode = { type: "idle" };
|
|
5359
|
+
} else {
|
|
5360
|
+
this._selectedIds = [...this._selectedIds, hit.id];
|
|
5361
|
+
this.mode = hit.locked ? { type: "idle" } : { type: "dragging" };
|
|
5362
|
+
}
|
|
5363
|
+
} else {
|
|
5364
|
+
if (!alreadySelected) {
|
|
5365
|
+
this._selectedIds = [hit.id];
|
|
5366
|
+
} else if (this._selectedIds.length > 1) {
|
|
5367
|
+
this.pendingSingleSelectId = hit.id;
|
|
5368
|
+
}
|
|
5369
|
+
this.mode = hit.locked ? { type: "idle" } : { type: "dragging" };
|
|
5035
5370
|
}
|
|
5036
|
-
this.mode = hit.locked ? { type: "idle" } : { type: "dragging" };
|
|
5037
5371
|
} else {
|
|
5038
5372
|
this._selectedIds = [];
|
|
5039
5373
|
this.mode = { type: "marquee", start: world };
|
|
@@ -5059,6 +5393,7 @@ var SelectTool = class {
|
|
|
5059
5393
|
return;
|
|
5060
5394
|
}
|
|
5061
5395
|
if (this.mode.type === "dragging" && this._selectedIds.length > 0) {
|
|
5396
|
+
this.hasDragged = true;
|
|
5062
5397
|
ctx.setCursor?.("move");
|
|
5063
5398
|
const snapped = this.snap(world, ctx);
|
|
5064
5399
|
const dx = snapped.x - this.lastWorld.x;
|
|
@@ -5127,6 +5462,11 @@ var SelectTool = class {
|
|
|
5127
5462
|
}
|
|
5128
5463
|
ctx.requestRender();
|
|
5129
5464
|
}
|
|
5465
|
+
if (!this.hasDragged && this.pendingSingleSelectId !== null) {
|
|
5466
|
+
this._selectedIds = [this.pendingSingleSelectId];
|
|
5467
|
+
}
|
|
5468
|
+
this.pendingSingleSelectId = null;
|
|
5469
|
+
this.hasDragged = false;
|
|
5130
5470
|
this.mode = { type: "idle" };
|
|
5131
5471
|
ctx.setCursor?.("default");
|
|
5132
5472
|
}
|
|
@@ -6307,7 +6647,7 @@ var UpdateLayerCommand = class {
|
|
|
6307
6647
|
};
|
|
6308
6648
|
|
|
6309
6649
|
// src/index.ts
|
|
6310
|
-
var VERSION = "0.
|
|
6650
|
+
var VERSION = "0.14.0";
|
|
6311
6651
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6312
6652
|
0 && (module.exports = {
|
|
6313
6653
|
AddElementCommand,
|
|
@@ -6319,6 +6659,7 @@ var VERSION = "0.11.0";
|
|
|
6319
6659
|
CreateLayerCommand,
|
|
6320
6660
|
DEFAULT_FONT_SIZE_PRESETS,
|
|
6321
6661
|
DEFAULT_NOTE_FONT_SIZE,
|
|
6662
|
+
DoubleTapDetector,
|
|
6322
6663
|
ElementRenderer,
|
|
6323
6664
|
ElementStore,
|
|
6324
6665
|
EraserTool,
|
|
@@ -6327,6 +6668,7 @@ var VERSION = "0.11.0";
|
|
|
6327
6668
|
HistoryRecorder,
|
|
6328
6669
|
HistoryStack,
|
|
6329
6670
|
ImageTool,
|
|
6671
|
+
InputFilter,
|
|
6330
6672
|
InputHandler,
|
|
6331
6673
|
LayerManager,
|
|
6332
6674
|
MeasureTool,
|
|
@@ -6372,6 +6714,7 @@ var VERSION = "0.11.0";
|
|
|
6372
6714
|
getEdgeIntersection,
|
|
6373
6715
|
getElementBounds,
|
|
6374
6716
|
getElementCenter,
|
|
6717
|
+
getElementsBoundingBox,
|
|
6375
6718
|
getHexCellsInCone,
|
|
6376
6719
|
getHexCellsInLine,
|
|
6377
6720
|
getHexCellsInRadius,
|