@fieldnotes/core 0.11.2 → 0.12.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 +132 -13
- package/dist/index.d.cts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +132 -13
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -898,6 +898,12 @@ var InputFilter = class _InputFilter {
|
|
|
898
898
|
}
|
|
899
899
|
};
|
|
900
900
|
|
|
901
|
+
// src/elements/create-id.ts
|
|
902
|
+
var counter = 0;
|
|
903
|
+
function createId(prefix) {
|
|
904
|
+
return `${prefix}_${Date.now().toString(36)}_${(counter++).toString(36)}`;
|
|
905
|
+
}
|
|
906
|
+
|
|
901
907
|
// src/canvas/input-handler.ts
|
|
902
908
|
var ZOOM_SENSITIVITY = 1e-3;
|
|
903
909
|
var MIDDLE_BUTTON = 1;
|
|
@@ -927,6 +933,8 @@ var InputHandler = class {
|
|
|
927
933
|
inputFilter = new InputFilter();
|
|
928
934
|
deferredDown = null;
|
|
929
935
|
abortController = new AbortController();
|
|
936
|
+
clipboard = [];
|
|
937
|
+
pasteCount = 0;
|
|
930
938
|
setToolManager(toolManager, toolContext) {
|
|
931
939
|
this.toolManager = toolManager;
|
|
932
940
|
this.toolContext = toolContext;
|
|
@@ -1050,6 +1058,14 @@ var InputHandler = class {
|
|
|
1050
1058
|
e.preventDefault();
|
|
1051
1059
|
this.handleRedo();
|
|
1052
1060
|
}
|
|
1061
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "c") {
|
|
1062
|
+
e.preventDefault();
|
|
1063
|
+
this.handleCopy();
|
|
1064
|
+
}
|
|
1065
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "v") {
|
|
1066
|
+
e.preventDefault();
|
|
1067
|
+
this.handlePaste();
|
|
1068
|
+
}
|
|
1053
1069
|
};
|
|
1054
1070
|
onKeyUp = (e) => {
|
|
1055
1071
|
if (e.key === " ") {
|
|
@@ -1106,7 +1122,8 @@ var InputHandler = class {
|
|
|
1106
1122
|
x: e.clientX - rect.left,
|
|
1107
1123
|
y: e.clientY - rect.top,
|
|
1108
1124
|
pressure: e.pressure,
|
|
1109
|
-
pointerType: e.pointerType === "touch" || e.pointerType === "pen" ? e.pointerType : "mouse"
|
|
1125
|
+
pointerType: e.pointerType === "touch" || e.pointerType === "pen" ? e.pointerType : "mouse",
|
|
1126
|
+
shiftKey: e.shiftKey
|
|
1110
1127
|
};
|
|
1111
1128
|
}
|
|
1112
1129
|
dispatchToolDown(e) {
|
|
@@ -1159,6 +1176,72 @@ var InputHandler = class {
|
|
|
1159
1176
|
this.historyRecorder?.resume();
|
|
1160
1177
|
this.toolContext.requestRender();
|
|
1161
1178
|
}
|
|
1179
|
+
handleCopy() {
|
|
1180
|
+
if (!this.toolManager || !this.toolContext || this.isToolActive) return;
|
|
1181
|
+
const tool = this.toolManager.activeTool;
|
|
1182
|
+
if (tool?.name !== "select") return;
|
|
1183
|
+
const selectTool = tool;
|
|
1184
|
+
const ids = selectTool.selectedIds;
|
|
1185
|
+
if (ids.length === 0) return;
|
|
1186
|
+
this.clipboard = [];
|
|
1187
|
+
for (const id of ids) {
|
|
1188
|
+
const el = this.toolContext.store.getById(id);
|
|
1189
|
+
if (el) this.clipboard.push(structuredClone(el));
|
|
1190
|
+
}
|
|
1191
|
+
this.pasteCount = 0;
|
|
1192
|
+
}
|
|
1193
|
+
handlePaste() {
|
|
1194
|
+
if (!this.toolManager || !this.toolContext || this.clipboard.length === 0 || this.isToolActive)
|
|
1195
|
+
return;
|
|
1196
|
+
const tool = this.toolManager.activeTool;
|
|
1197
|
+
if (tool?.name !== "select") return;
|
|
1198
|
+
const selectTool = tool;
|
|
1199
|
+
this.pasteCount++;
|
|
1200
|
+
const offset = this.pasteCount * 20;
|
|
1201
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
1202
|
+
for (const el of this.clipboard) {
|
|
1203
|
+
idMap.set(el.id, createId(el.type));
|
|
1204
|
+
}
|
|
1205
|
+
const newIds = [];
|
|
1206
|
+
this.historyRecorder?.begin();
|
|
1207
|
+
for (const el of this.clipboard) {
|
|
1208
|
+
const clone = structuredClone(el);
|
|
1209
|
+
const newId = idMap.get(el.id);
|
|
1210
|
+
if (!newId) continue;
|
|
1211
|
+
clone.id = newId;
|
|
1212
|
+
clone.position = { x: clone.position.x + offset, y: clone.position.y + offset };
|
|
1213
|
+
if (clone.type === "arrow") {
|
|
1214
|
+
const arrow = clone;
|
|
1215
|
+
arrow.from = { x: arrow.from.x + offset, y: arrow.from.y + offset };
|
|
1216
|
+
arrow.to = { x: arrow.to.x + offset, y: arrow.to.y + offset };
|
|
1217
|
+
delete arrow.cachedControlPoint;
|
|
1218
|
+
if (arrow.fromBinding) {
|
|
1219
|
+
const newTarget = idMap.get(arrow.fromBinding.elementId);
|
|
1220
|
+
if (newTarget) {
|
|
1221
|
+
arrow.fromBinding = { elementId: newTarget };
|
|
1222
|
+
} else {
|
|
1223
|
+
delete arrow.fromBinding;
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
if (arrow.toBinding) {
|
|
1227
|
+
const newTarget = idMap.get(arrow.toBinding.elementId);
|
|
1228
|
+
if (newTarget) {
|
|
1229
|
+
arrow.toBinding = { elementId: newTarget };
|
|
1230
|
+
} else {
|
|
1231
|
+
delete arrow.toBinding;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
if (this.toolContext.activeLayerId) {
|
|
1236
|
+
clone.layerId = this.toolContext.activeLayerId;
|
|
1237
|
+
}
|
|
1238
|
+
this.toolContext.store.add(clone);
|
|
1239
|
+
newIds.push(clone.id);
|
|
1240
|
+
}
|
|
1241
|
+
this.historyRecorder?.commit();
|
|
1242
|
+
selectTool.setSelection(newIds);
|
|
1243
|
+
this.toolContext.requestRender();
|
|
1244
|
+
}
|
|
1162
1245
|
cancelToolIfActive(e) {
|
|
1163
1246
|
if (this.isToolActive) {
|
|
1164
1247
|
this.dispatchToolUp(e);
|
|
@@ -1412,19 +1495,23 @@ var ElementStore = class {
|
|
|
1412
1495
|
bus = new EventBus();
|
|
1413
1496
|
layerOrderMap = /* @__PURE__ */ new Map();
|
|
1414
1497
|
spatialIndex = new Quadtree({ x: -1e5, y: -1e5, w: 2e5, h: 2e5 });
|
|
1498
|
+
sortedCache = null;
|
|
1415
1499
|
get count() {
|
|
1416
1500
|
return this.elements.size;
|
|
1417
1501
|
}
|
|
1418
1502
|
setLayerOrder(order) {
|
|
1419
1503
|
this.layerOrderMap = new Map(order);
|
|
1504
|
+
this.sortedCache = null;
|
|
1420
1505
|
}
|
|
1421
1506
|
getAll() {
|
|
1422
|
-
|
|
1507
|
+
if (this.sortedCache) return this.sortedCache;
|
|
1508
|
+
this.sortedCache = [...this.elements.values()].sort((a, b) => {
|
|
1423
1509
|
const layerA = this.layerOrderMap.get(a.layerId) ?? 0;
|
|
1424
1510
|
const layerB = this.layerOrderMap.get(b.layerId) ?? 0;
|
|
1425
1511
|
if (layerA !== layerB) return layerA - layerB;
|
|
1426
1512
|
return a.zIndex - b.zIndex;
|
|
1427
1513
|
});
|
|
1514
|
+
return this.sortedCache;
|
|
1428
1515
|
}
|
|
1429
1516
|
getById(id) {
|
|
1430
1517
|
return this.elements.get(id);
|
|
@@ -1435,6 +1522,7 @@ var ElementStore = class {
|
|
|
1435
1522
|
);
|
|
1436
1523
|
}
|
|
1437
1524
|
add(element) {
|
|
1525
|
+
this.sortedCache = null;
|
|
1438
1526
|
this.elements.set(element.id, element);
|
|
1439
1527
|
const bounds = getElementBounds(element);
|
|
1440
1528
|
if (bounds) this.spatialIndex.insert(element.id, bounds);
|
|
@@ -1443,11 +1531,15 @@ var ElementStore = class {
|
|
|
1443
1531
|
update(id, partial) {
|
|
1444
1532
|
const existing = this.elements.get(id);
|
|
1445
1533
|
if (!existing) return;
|
|
1534
|
+
this.sortedCache = null;
|
|
1446
1535
|
const updated = { ...existing, ...partial, id: existing.id, type: existing.type };
|
|
1447
1536
|
if (updated.type === "arrow") {
|
|
1448
1537
|
const arrow = updated;
|
|
1449
1538
|
arrow.cachedControlPoint = getArrowControlPoint(arrow.from, arrow.to, arrow.bend);
|
|
1450
1539
|
}
|
|
1540
|
+
if (updated.type === "note" && "text" in partial) {
|
|
1541
|
+
updated.text = sanitizeNoteHtml(updated.text);
|
|
1542
|
+
}
|
|
1451
1543
|
this.elements.set(id, updated);
|
|
1452
1544
|
const newBounds = getElementBounds(updated);
|
|
1453
1545
|
if (newBounds) {
|
|
@@ -1458,11 +1550,13 @@ var ElementStore = class {
|
|
|
1458
1550
|
remove(id) {
|
|
1459
1551
|
const element = this.elements.get(id);
|
|
1460
1552
|
if (!element) return;
|
|
1553
|
+
this.sortedCache = null;
|
|
1461
1554
|
this.elements.delete(id);
|
|
1462
1555
|
this.spatialIndex.remove(id);
|
|
1463
1556
|
this.bus.emit("remove", element);
|
|
1464
1557
|
}
|
|
1465
1558
|
clear() {
|
|
1559
|
+
this.sortedCache = null;
|
|
1466
1560
|
this.elements.clear();
|
|
1467
1561
|
this.spatialIndex.clear();
|
|
1468
1562
|
this.bus.emit("clear", null);
|
|
@@ -1471,6 +1565,7 @@ var ElementStore = class {
|
|
|
1471
1565
|
return this.getAll().map((el) => ({ ...el }));
|
|
1472
1566
|
}
|
|
1473
1567
|
loadSnapshot(elements) {
|
|
1568
|
+
this.sortedCache = null;
|
|
1474
1569
|
this.elements.clear();
|
|
1475
1570
|
this.spatialIndex.clear();
|
|
1476
1571
|
for (const el of elements) {
|
|
@@ -1478,6 +1573,10 @@ var ElementStore = class {
|
|
|
1478
1573
|
const bounds = getElementBounds(el);
|
|
1479
1574
|
if (bounds) this.spatialIndex.insert(el.id, bounds);
|
|
1480
1575
|
}
|
|
1576
|
+
this.bus.emit("clear", null);
|
|
1577
|
+
for (const el of elements) {
|
|
1578
|
+
this.bus.emit("add", el);
|
|
1579
|
+
}
|
|
1481
1580
|
}
|
|
1482
1581
|
queryRect(rect) {
|
|
1483
1582
|
const ids = this.spatialIndex.query(rect);
|
|
@@ -2527,12 +2626,6 @@ var ElementRenderer = class {
|
|
|
2527
2626
|
}
|
|
2528
2627
|
};
|
|
2529
2628
|
|
|
2530
|
-
// src/elements/create-id.ts
|
|
2531
|
-
var counter = 0;
|
|
2532
|
-
function createId(prefix) {
|
|
2533
|
-
return `${prefix}_${Date.now().toString(36)}_${(counter++).toString(36)}`;
|
|
2534
|
-
}
|
|
2535
|
-
|
|
2536
2629
|
// src/elements/element-factory.ts
|
|
2537
2630
|
var DEFAULT_NOTE_FONT_SIZE = 18;
|
|
2538
2631
|
function createStroke(input) {
|
|
@@ -2558,7 +2651,7 @@ function createNote(input) {
|
|
|
2558
2651
|
locked: input.locked ?? false,
|
|
2559
2652
|
layerId: input.layerId ?? "",
|
|
2560
2653
|
size: input.size ?? { w: 200, h: 100 },
|
|
2561
|
-
text: input.text ?? "",
|
|
2654
|
+
text: sanitizeNoteHtml(input.text ?? ""),
|
|
2562
2655
|
backgroundColor: input.backgroundColor ?? "#ffeb3b",
|
|
2563
2656
|
textColor: input.textColor ?? "#000000",
|
|
2564
2657
|
fontSize: input.fontSize ?? DEFAULT_NOTE_FONT_SIZE
|
|
@@ -5083,9 +5176,15 @@ var SelectTool = class {
|
|
|
5083
5176
|
lastWorld = { x: 0, y: 0 };
|
|
5084
5177
|
currentWorld = { x: 0, y: 0 };
|
|
5085
5178
|
ctx = null;
|
|
5179
|
+
pendingSingleSelectId = null;
|
|
5180
|
+
hasDragged = false;
|
|
5086
5181
|
get selectedIds() {
|
|
5087
5182
|
return [...this._selectedIds];
|
|
5088
5183
|
}
|
|
5184
|
+
setSelection(ids) {
|
|
5185
|
+
this._selectedIds = ids;
|
|
5186
|
+
this.ctx?.requestRender();
|
|
5187
|
+
}
|
|
5089
5188
|
get isMarqueeActive() {
|
|
5090
5189
|
return this.mode.type === "marquee";
|
|
5091
5190
|
}
|
|
@@ -5134,13 +5233,27 @@ var SelectTool = class {
|
|
|
5134
5233
|
return;
|
|
5135
5234
|
}
|
|
5136
5235
|
}
|
|
5236
|
+
this.pendingSingleSelectId = null;
|
|
5237
|
+
this.hasDragged = false;
|
|
5137
5238
|
const hit = this.hitTest(world, ctx);
|
|
5138
5239
|
if (hit) {
|
|
5139
5240
|
const alreadySelected = this._selectedIds.includes(hit.id);
|
|
5140
|
-
if (
|
|
5141
|
-
|
|
5241
|
+
if (state.shiftKey) {
|
|
5242
|
+
if (alreadySelected) {
|
|
5243
|
+
this._selectedIds = this._selectedIds.filter((id) => id !== hit.id);
|
|
5244
|
+
this.mode = { type: "idle" };
|
|
5245
|
+
} else {
|
|
5246
|
+
this._selectedIds = [...this._selectedIds, hit.id];
|
|
5247
|
+
this.mode = hit.locked ? { type: "idle" } : { type: "dragging" };
|
|
5248
|
+
}
|
|
5249
|
+
} else {
|
|
5250
|
+
if (!alreadySelected) {
|
|
5251
|
+
this._selectedIds = [hit.id];
|
|
5252
|
+
} else if (this._selectedIds.length > 1) {
|
|
5253
|
+
this.pendingSingleSelectId = hit.id;
|
|
5254
|
+
}
|
|
5255
|
+
this.mode = hit.locked ? { type: "idle" } : { type: "dragging" };
|
|
5142
5256
|
}
|
|
5143
|
-
this.mode = hit.locked ? { type: "idle" } : { type: "dragging" };
|
|
5144
5257
|
} else {
|
|
5145
5258
|
this._selectedIds = [];
|
|
5146
5259
|
this.mode = { type: "marquee", start: world };
|
|
@@ -5166,6 +5279,7 @@ var SelectTool = class {
|
|
|
5166
5279
|
return;
|
|
5167
5280
|
}
|
|
5168
5281
|
if (this.mode.type === "dragging" && this._selectedIds.length > 0) {
|
|
5282
|
+
this.hasDragged = true;
|
|
5169
5283
|
ctx.setCursor?.("move");
|
|
5170
5284
|
const snapped = this.snap(world, ctx);
|
|
5171
5285
|
const dx = snapped.x - this.lastWorld.x;
|
|
@@ -5234,6 +5348,11 @@ var SelectTool = class {
|
|
|
5234
5348
|
}
|
|
5235
5349
|
ctx.requestRender();
|
|
5236
5350
|
}
|
|
5351
|
+
if (!this.hasDragged && this.pendingSingleSelectId !== null) {
|
|
5352
|
+
this._selectedIds = [this.pendingSingleSelectId];
|
|
5353
|
+
}
|
|
5354
|
+
this.pendingSingleSelectId = null;
|
|
5355
|
+
this.hasDragged = false;
|
|
5237
5356
|
this.mode = { type: "idle" };
|
|
5238
5357
|
ctx.setCursor?.("default");
|
|
5239
5358
|
}
|
|
@@ -6414,7 +6533,7 @@ var UpdateLayerCommand = class {
|
|
|
6414
6533
|
};
|
|
6415
6534
|
|
|
6416
6535
|
// src/index.ts
|
|
6417
|
-
var VERSION = "0.
|
|
6536
|
+
var VERSION = "0.12.0";
|
|
6418
6537
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6419
6538
|
0 && (module.exports = {
|
|
6420
6539
|
AddElementCommand,
|
package/dist/index.d.cts
CHANGED
|
@@ -200,6 +200,7 @@ declare class ElementStore {
|
|
|
200
200
|
private bus;
|
|
201
201
|
private layerOrderMap;
|
|
202
202
|
private spatialIndex;
|
|
203
|
+
private sortedCache;
|
|
203
204
|
get count(): number;
|
|
204
205
|
setLayerOrder(order: Map<string, number>): void;
|
|
205
206
|
getAll(): CanvasElement[];
|
|
@@ -239,6 +240,7 @@ interface PointerState {
|
|
|
239
240
|
y: number;
|
|
240
241
|
pressure: number;
|
|
241
242
|
pointerType: 'mouse' | 'touch' | 'pen';
|
|
243
|
+
shiftKey: boolean;
|
|
242
244
|
}
|
|
243
245
|
interface Tool {
|
|
244
246
|
readonly name: string;
|
|
@@ -428,6 +430,8 @@ declare class InputHandler {
|
|
|
428
430
|
private readonly inputFilter;
|
|
429
431
|
private deferredDown;
|
|
430
432
|
private readonly abortController;
|
|
433
|
+
private clipboard;
|
|
434
|
+
private pasteCount;
|
|
431
435
|
constructor(element: HTMLElement, camera: Camera, options?: InputHandlerOptions);
|
|
432
436
|
setToolManager(toolManager: ToolManager, toolContext: ToolContext): void;
|
|
433
437
|
destroy(): void;
|
|
@@ -451,6 +455,8 @@ declare class InputHandler {
|
|
|
451
455
|
private deleteSelected;
|
|
452
456
|
private handleUndo;
|
|
453
457
|
private handleRedo;
|
|
458
|
+
private handleCopy;
|
|
459
|
+
private handlePaste;
|
|
454
460
|
private cancelToolIfActive;
|
|
455
461
|
}
|
|
456
462
|
|
|
@@ -899,7 +905,10 @@ declare class SelectTool implements Tool {
|
|
|
899
905
|
private lastWorld;
|
|
900
906
|
private currentWorld;
|
|
901
907
|
private ctx;
|
|
908
|
+
private pendingSingleSelectId;
|
|
909
|
+
private hasDragged;
|
|
902
910
|
get selectedIds(): string[];
|
|
911
|
+
setSelection(ids: string[]): void;
|
|
903
912
|
get isMarqueeActive(): boolean;
|
|
904
913
|
onActivate(ctx: ToolContext): void;
|
|
905
914
|
onDeactivate(ctx: ToolContext): void;
|
|
@@ -1144,6 +1153,6 @@ declare class UpdateLayerCommand implements Command {
|
|
|
1144
1153
|
undo(_store: ElementStore): void;
|
|
1145
1154
|
}
|
|
1146
1155
|
|
|
1147
|
-
declare const VERSION = "0.
|
|
1156
|
+
declare const VERSION = "0.12.0";
|
|
1148
1157
|
|
|
1149
1158
|
export { type ActiveFormats, AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, CreateLayerCommand, DEFAULT_FONT_SIZE_PRESETS, DEFAULT_NOTE_FONT_SIZE, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, type ExportImageOptions, type FilterAction, type FilteredEvent, type FilteredUpEvent, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputFilter, InputHandler, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, NoteEditor, type NoteEditorOptions, type NoteElement, NoteTool, type NoteToolOptions, NoteToolbar, PencilTool, type PencilToolOptions, type Point, type PointerState, Quadtree, RemoveElementCommand, RemoveLayerCommand, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type Size, type StrokeElement, type StrokePoint, type StyledRun, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, UpdateLayerCommand, VERSION, Viewport, type ViewportOptions, boundsIntersect, clearStaleBindings, createArrow, createGrid, createHtmlElement, createId, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportState, findBindTarget, findBoundArrows, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getEdgeIntersection, getElementBounds, getElementCenter, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isBindable, isNearBezier, parseState, sanitizeNoteHtml, setFontSize, smartSnap, snapPoint, snapToHexCenter, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline, unbindArrow, updateBoundArrow };
|
package/dist/index.d.ts
CHANGED
|
@@ -200,6 +200,7 @@ declare class ElementStore {
|
|
|
200
200
|
private bus;
|
|
201
201
|
private layerOrderMap;
|
|
202
202
|
private spatialIndex;
|
|
203
|
+
private sortedCache;
|
|
203
204
|
get count(): number;
|
|
204
205
|
setLayerOrder(order: Map<string, number>): void;
|
|
205
206
|
getAll(): CanvasElement[];
|
|
@@ -239,6 +240,7 @@ interface PointerState {
|
|
|
239
240
|
y: number;
|
|
240
241
|
pressure: number;
|
|
241
242
|
pointerType: 'mouse' | 'touch' | 'pen';
|
|
243
|
+
shiftKey: boolean;
|
|
242
244
|
}
|
|
243
245
|
interface Tool {
|
|
244
246
|
readonly name: string;
|
|
@@ -428,6 +430,8 @@ declare class InputHandler {
|
|
|
428
430
|
private readonly inputFilter;
|
|
429
431
|
private deferredDown;
|
|
430
432
|
private readonly abortController;
|
|
433
|
+
private clipboard;
|
|
434
|
+
private pasteCount;
|
|
431
435
|
constructor(element: HTMLElement, camera: Camera, options?: InputHandlerOptions);
|
|
432
436
|
setToolManager(toolManager: ToolManager, toolContext: ToolContext): void;
|
|
433
437
|
destroy(): void;
|
|
@@ -451,6 +455,8 @@ declare class InputHandler {
|
|
|
451
455
|
private deleteSelected;
|
|
452
456
|
private handleUndo;
|
|
453
457
|
private handleRedo;
|
|
458
|
+
private handleCopy;
|
|
459
|
+
private handlePaste;
|
|
454
460
|
private cancelToolIfActive;
|
|
455
461
|
}
|
|
456
462
|
|
|
@@ -899,7 +905,10 @@ declare class SelectTool implements Tool {
|
|
|
899
905
|
private lastWorld;
|
|
900
906
|
private currentWorld;
|
|
901
907
|
private ctx;
|
|
908
|
+
private pendingSingleSelectId;
|
|
909
|
+
private hasDragged;
|
|
902
910
|
get selectedIds(): string[];
|
|
911
|
+
setSelection(ids: string[]): void;
|
|
903
912
|
get isMarqueeActive(): boolean;
|
|
904
913
|
onActivate(ctx: ToolContext): void;
|
|
905
914
|
onDeactivate(ctx: ToolContext): void;
|
|
@@ -1144,6 +1153,6 @@ declare class UpdateLayerCommand implements Command {
|
|
|
1144
1153
|
undo(_store: ElementStore): void;
|
|
1145
1154
|
}
|
|
1146
1155
|
|
|
1147
|
-
declare const VERSION = "0.
|
|
1156
|
+
declare const VERSION = "0.12.0";
|
|
1148
1157
|
|
|
1149
1158
|
export { type ActiveFormats, AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, CreateLayerCommand, DEFAULT_FONT_SIZE_PRESETS, DEFAULT_NOTE_FONT_SIZE, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, type ExportImageOptions, type FilterAction, type FilteredEvent, type FilteredUpEvent, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputFilter, InputHandler, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, NoteEditor, type NoteEditorOptions, type NoteElement, NoteTool, type NoteToolOptions, NoteToolbar, PencilTool, type PencilToolOptions, type Point, type PointerState, Quadtree, RemoveElementCommand, RemoveLayerCommand, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type Size, type StrokeElement, type StrokePoint, type StyledRun, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, UpdateLayerCommand, VERSION, Viewport, type ViewportOptions, boundsIntersect, clearStaleBindings, createArrow, createGrid, createHtmlElement, createId, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportState, findBindTarget, findBoundArrows, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getEdgeIntersection, getElementBounds, getElementCenter, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isBindable, isNearBezier, parseState, sanitizeNoteHtml, setFontSize, smartSnap, snapPoint, snapToHexCenter, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline, unbindArrow, updateBoundArrow };
|
package/dist/index.js
CHANGED
|
@@ -791,6 +791,12 @@ var InputFilter = class _InputFilter {
|
|
|
791
791
|
}
|
|
792
792
|
};
|
|
793
793
|
|
|
794
|
+
// src/elements/create-id.ts
|
|
795
|
+
var counter = 0;
|
|
796
|
+
function createId(prefix) {
|
|
797
|
+
return `${prefix}_${Date.now().toString(36)}_${(counter++).toString(36)}`;
|
|
798
|
+
}
|
|
799
|
+
|
|
794
800
|
// src/canvas/input-handler.ts
|
|
795
801
|
var ZOOM_SENSITIVITY = 1e-3;
|
|
796
802
|
var MIDDLE_BUTTON = 1;
|
|
@@ -820,6 +826,8 @@ var InputHandler = class {
|
|
|
820
826
|
inputFilter = new InputFilter();
|
|
821
827
|
deferredDown = null;
|
|
822
828
|
abortController = new AbortController();
|
|
829
|
+
clipboard = [];
|
|
830
|
+
pasteCount = 0;
|
|
823
831
|
setToolManager(toolManager, toolContext) {
|
|
824
832
|
this.toolManager = toolManager;
|
|
825
833
|
this.toolContext = toolContext;
|
|
@@ -943,6 +951,14 @@ var InputHandler = class {
|
|
|
943
951
|
e.preventDefault();
|
|
944
952
|
this.handleRedo();
|
|
945
953
|
}
|
|
954
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "c") {
|
|
955
|
+
e.preventDefault();
|
|
956
|
+
this.handleCopy();
|
|
957
|
+
}
|
|
958
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "v") {
|
|
959
|
+
e.preventDefault();
|
|
960
|
+
this.handlePaste();
|
|
961
|
+
}
|
|
946
962
|
};
|
|
947
963
|
onKeyUp = (e) => {
|
|
948
964
|
if (e.key === " ") {
|
|
@@ -999,7 +1015,8 @@ var InputHandler = class {
|
|
|
999
1015
|
x: e.clientX - rect.left,
|
|
1000
1016
|
y: e.clientY - rect.top,
|
|
1001
1017
|
pressure: e.pressure,
|
|
1002
|
-
pointerType: e.pointerType === "touch" || e.pointerType === "pen" ? e.pointerType : "mouse"
|
|
1018
|
+
pointerType: e.pointerType === "touch" || e.pointerType === "pen" ? e.pointerType : "mouse",
|
|
1019
|
+
shiftKey: e.shiftKey
|
|
1003
1020
|
};
|
|
1004
1021
|
}
|
|
1005
1022
|
dispatchToolDown(e) {
|
|
@@ -1052,6 +1069,72 @@ var InputHandler = class {
|
|
|
1052
1069
|
this.historyRecorder?.resume();
|
|
1053
1070
|
this.toolContext.requestRender();
|
|
1054
1071
|
}
|
|
1072
|
+
handleCopy() {
|
|
1073
|
+
if (!this.toolManager || !this.toolContext || this.isToolActive) return;
|
|
1074
|
+
const tool = this.toolManager.activeTool;
|
|
1075
|
+
if (tool?.name !== "select") return;
|
|
1076
|
+
const selectTool = tool;
|
|
1077
|
+
const ids = selectTool.selectedIds;
|
|
1078
|
+
if (ids.length === 0) return;
|
|
1079
|
+
this.clipboard = [];
|
|
1080
|
+
for (const id of ids) {
|
|
1081
|
+
const el = this.toolContext.store.getById(id);
|
|
1082
|
+
if (el) this.clipboard.push(structuredClone(el));
|
|
1083
|
+
}
|
|
1084
|
+
this.pasteCount = 0;
|
|
1085
|
+
}
|
|
1086
|
+
handlePaste() {
|
|
1087
|
+
if (!this.toolManager || !this.toolContext || this.clipboard.length === 0 || this.isToolActive)
|
|
1088
|
+
return;
|
|
1089
|
+
const tool = this.toolManager.activeTool;
|
|
1090
|
+
if (tool?.name !== "select") return;
|
|
1091
|
+
const selectTool = tool;
|
|
1092
|
+
this.pasteCount++;
|
|
1093
|
+
const offset = this.pasteCount * 20;
|
|
1094
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
1095
|
+
for (const el of this.clipboard) {
|
|
1096
|
+
idMap.set(el.id, createId(el.type));
|
|
1097
|
+
}
|
|
1098
|
+
const newIds = [];
|
|
1099
|
+
this.historyRecorder?.begin();
|
|
1100
|
+
for (const el of this.clipboard) {
|
|
1101
|
+
const clone = structuredClone(el);
|
|
1102
|
+
const newId = idMap.get(el.id);
|
|
1103
|
+
if (!newId) continue;
|
|
1104
|
+
clone.id = newId;
|
|
1105
|
+
clone.position = { x: clone.position.x + offset, y: clone.position.y + offset };
|
|
1106
|
+
if (clone.type === "arrow") {
|
|
1107
|
+
const arrow = clone;
|
|
1108
|
+
arrow.from = { x: arrow.from.x + offset, y: arrow.from.y + offset };
|
|
1109
|
+
arrow.to = { x: arrow.to.x + offset, y: arrow.to.y + offset };
|
|
1110
|
+
delete arrow.cachedControlPoint;
|
|
1111
|
+
if (arrow.fromBinding) {
|
|
1112
|
+
const newTarget = idMap.get(arrow.fromBinding.elementId);
|
|
1113
|
+
if (newTarget) {
|
|
1114
|
+
arrow.fromBinding = { elementId: newTarget };
|
|
1115
|
+
} else {
|
|
1116
|
+
delete arrow.fromBinding;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
if (arrow.toBinding) {
|
|
1120
|
+
const newTarget = idMap.get(arrow.toBinding.elementId);
|
|
1121
|
+
if (newTarget) {
|
|
1122
|
+
arrow.toBinding = { elementId: newTarget };
|
|
1123
|
+
} else {
|
|
1124
|
+
delete arrow.toBinding;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
if (this.toolContext.activeLayerId) {
|
|
1129
|
+
clone.layerId = this.toolContext.activeLayerId;
|
|
1130
|
+
}
|
|
1131
|
+
this.toolContext.store.add(clone);
|
|
1132
|
+
newIds.push(clone.id);
|
|
1133
|
+
}
|
|
1134
|
+
this.historyRecorder?.commit();
|
|
1135
|
+
selectTool.setSelection(newIds);
|
|
1136
|
+
this.toolContext.requestRender();
|
|
1137
|
+
}
|
|
1055
1138
|
cancelToolIfActive(e) {
|
|
1056
1139
|
if (this.isToolActive) {
|
|
1057
1140
|
this.dispatchToolUp(e);
|
|
@@ -1305,19 +1388,23 @@ var ElementStore = class {
|
|
|
1305
1388
|
bus = new EventBus();
|
|
1306
1389
|
layerOrderMap = /* @__PURE__ */ new Map();
|
|
1307
1390
|
spatialIndex = new Quadtree({ x: -1e5, y: -1e5, w: 2e5, h: 2e5 });
|
|
1391
|
+
sortedCache = null;
|
|
1308
1392
|
get count() {
|
|
1309
1393
|
return this.elements.size;
|
|
1310
1394
|
}
|
|
1311
1395
|
setLayerOrder(order) {
|
|
1312
1396
|
this.layerOrderMap = new Map(order);
|
|
1397
|
+
this.sortedCache = null;
|
|
1313
1398
|
}
|
|
1314
1399
|
getAll() {
|
|
1315
|
-
|
|
1400
|
+
if (this.sortedCache) return this.sortedCache;
|
|
1401
|
+
this.sortedCache = [...this.elements.values()].sort((a, b) => {
|
|
1316
1402
|
const layerA = this.layerOrderMap.get(a.layerId) ?? 0;
|
|
1317
1403
|
const layerB = this.layerOrderMap.get(b.layerId) ?? 0;
|
|
1318
1404
|
if (layerA !== layerB) return layerA - layerB;
|
|
1319
1405
|
return a.zIndex - b.zIndex;
|
|
1320
1406
|
});
|
|
1407
|
+
return this.sortedCache;
|
|
1321
1408
|
}
|
|
1322
1409
|
getById(id) {
|
|
1323
1410
|
return this.elements.get(id);
|
|
@@ -1328,6 +1415,7 @@ var ElementStore = class {
|
|
|
1328
1415
|
);
|
|
1329
1416
|
}
|
|
1330
1417
|
add(element) {
|
|
1418
|
+
this.sortedCache = null;
|
|
1331
1419
|
this.elements.set(element.id, element);
|
|
1332
1420
|
const bounds = getElementBounds(element);
|
|
1333
1421
|
if (bounds) this.spatialIndex.insert(element.id, bounds);
|
|
@@ -1336,11 +1424,15 @@ var ElementStore = class {
|
|
|
1336
1424
|
update(id, partial) {
|
|
1337
1425
|
const existing = this.elements.get(id);
|
|
1338
1426
|
if (!existing) return;
|
|
1427
|
+
this.sortedCache = null;
|
|
1339
1428
|
const updated = { ...existing, ...partial, id: existing.id, type: existing.type };
|
|
1340
1429
|
if (updated.type === "arrow") {
|
|
1341
1430
|
const arrow = updated;
|
|
1342
1431
|
arrow.cachedControlPoint = getArrowControlPoint(arrow.from, arrow.to, arrow.bend);
|
|
1343
1432
|
}
|
|
1433
|
+
if (updated.type === "note" && "text" in partial) {
|
|
1434
|
+
updated.text = sanitizeNoteHtml(updated.text);
|
|
1435
|
+
}
|
|
1344
1436
|
this.elements.set(id, updated);
|
|
1345
1437
|
const newBounds = getElementBounds(updated);
|
|
1346
1438
|
if (newBounds) {
|
|
@@ -1351,11 +1443,13 @@ var ElementStore = class {
|
|
|
1351
1443
|
remove(id) {
|
|
1352
1444
|
const element = this.elements.get(id);
|
|
1353
1445
|
if (!element) return;
|
|
1446
|
+
this.sortedCache = null;
|
|
1354
1447
|
this.elements.delete(id);
|
|
1355
1448
|
this.spatialIndex.remove(id);
|
|
1356
1449
|
this.bus.emit("remove", element);
|
|
1357
1450
|
}
|
|
1358
1451
|
clear() {
|
|
1452
|
+
this.sortedCache = null;
|
|
1359
1453
|
this.elements.clear();
|
|
1360
1454
|
this.spatialIndex.clear();
|
|
1361
1455
|
this.bus.emit("clear", null);
|
|
@@ -1364,6 +1458,7 @@ var ElementStore = class {
|
|
|
1364
1458
|
return this.getAll().map((el) => ({ ...el }));
|
|
1365
1459
|
}
|
|
1366
1460
|
loadSnapshot(elements) {
|
|
1461
|
+
this.sortedCache = null;
|
|
1367
1462
|
this.elements.clear();
|
|
1368
1463
|
this.spatialIndex.clear();
|
|
1369
1464
|
for (const el of elements) {
|
|
@@ -1371,6 +1466,10 @@ var ElementStore = class {
|
|
|
1371
1466
|
const bounds = getElementBounds(el);
|
|
1372
1467
|
if (bounds) this.spatialIndex.insert(el.id, bounds);
|
|
1373
1468
|
}
|
|
1469
|
+
this.bus.emit("clear", null);
|
|
1470
|
+
for (const el of elements) {
|
|
1471
|
+
this.bus.emit("add", el);
|
|
1472
|
+
}
|
|
1374
1473
|
}
|
|
1375
1474
|
queryRect(rect) {
|
|
1376
1475
|
const ids = this.spatialIndex.query(rect);
|
|
@@ -2420,12 +2519,6 @@ var ElementRenderer = class {
|
|
|
2420
2519
|
}
|
|
2421
2520
|
};
|
|
2422
2521
|
|
|
2423
|
-
// src/elements/create-id.ts
|
|
2424
|
-
var counter = 0;
|
|
2425
|
-
function createId(prefix) {
|
|
2426
|
-
return `${prefix}_${Date.now().toString(36)}_${(counter++).toString(36)}`;
|
|
2427
|
-
}
|
|
2428
|
-
|
|
2429
2522
|
// src/elements/element-factory.ts
|
|
2430
2523
|
var DEFAULT_NOTE_FONT_SIZE = 18;
|
|
2431
2524
|
function createStroke(input) {
|
|
@@ -2451,7 +2544,7 @@ function createNote(input) {
|
|
|
2451
2544
|
locked: input.locked ?? false,
|
|
2452
2545
|
layerId: input.layerId ?? "",
|
|
2453
2546
|
size: input.size ?? { w: 200, h: 100 },
|
|
2454
|
-
text: input.text ?? "",
|
|
2547
|
+
text: sanitizeNoteHtml(input.text ?? ""),
|
|
2455
2548
|
backgroundColor: input.backgroundColor ?? "#ffeb3b",
|
|
2456
2549
|
textColor: input.textColor ?? "#000000",
|
|
2457
2550
|
fontSize: input.fontSize ?? DEFAULT_NOTE_FONT_SIZE
|
|
@@ -4976,9 +5069,15 @@ var SelectTool = class {
|
|
|
4976
5069
|
lastWorld = { x: 0, y: 0 };
|
|
4977
5070
|
currentWorld = { x: 0, y: 0 };
|
|
4978
5071
|
ctx = null;
|
|
5072
|
+
pendingSingleSelectId = null;
|
|
5073
|
+
hasDragged = false;
|
|
4979
5074
|
get selectedIds() {
|
|
4980
5075
|
return [...this._selectedIds];
|
|
4981
5076
|
}
|
|
5077
|
+
setSelection(ids) {
|
|
5078
|
+
this._selectedIds = ids;
|
|
5079
|
+
this.ctx?.requestRender();
|
|
5080
|
+
}
|
|
4982
5081
|
get isMarqueeActive() {
|
|
4983
5082
|
return this.mode.type === "marquee";
|
|
4984
5083
|
}
|
|
@@ -5027,13 +5126,27 @@ var SelectTool = class {
|
|
|
5027
5126
|
return;
|
|
5028
5127
|
}
|
|
5029
5128
|
}
|
|
5129
|
+
this.pendingSingleSelectId = null;
|
|
5130
|
+
this.hasDragged = false;
|
|
5030
5131
|
const hit = this.hitTest(world, ctx);
|
|
5031
5132
|
if (hit) {
|
|
5032
5133
|
const alreadySelected = this._selectedIds.includes(hit.id);
|
|
5033
|
-
if (
|
|
5034
|
-
|
|
5134
|
+
if (state.shiftKey) {
|
|
5135
|
+
if (alreadySelected) {
|
|
5136
|
+
this._selectedIds = this._selectedIds.filter((id) => id !== hit.id);
|
|
5137
|
+
this.mode = { type: "idle" };
|
|
5138
|
+
} else {
|
|
5139
|
+
this._selectedIds = [...this._selectedIds, hit.id];
|
|
5140
|
+
this.mode = hit.locked ? { type: "idle" } : { type: "dragging" };
|
|
5141
|
+
}
|
|
5142
|
+
} else {
|
|
5143
|
+
if (!alreadySelected) {
|
|
5144
|
+
this._selectedIds = [hit.id];
|
|
5145
|
+
} else if (this._selectedIds.length > 1) {
|
|
5146
|
+
this.pendingSingleSelectId = hit.id;
|
|
5147
|
+
}
|
|
5148
|
+
this.mode = hit.locked ? { type: "idle" } : { type: "dragging" };
|
|
5035
5149
|
}
|
|
5036
|
-
this.mode = hit.locked ? { type: "idle" } : { type: "dragging" };
|
|
5037
5150
|
} else {
|
|
5038
5151
|
this._selectedIds = [];
|
|
5039
5152
|
this.mode = { type: "marquee", start: world };
|
|
@@ -5059,6 +5172,7 @@ var SelectTool = class {
|
|
|
5059
5172
|
return;
|
|
5060
5173
|
}
|
|
5061
5174
|
if (this.mode.type === "dragging" && this._selectedIds.length > 0) {
|
|
5175
|
+
this.hasDragged = true;
|
|
5062
5176
|
ctx.setCursor?.("move");
|
|
5063
5177
|
const snapped = this.snap(world, ctx);
|
|
5064
5178
|
const dx = snapped.x - this.lastWorld.x;
|
|
@@ -5127,6 +5241,11 @@ var SelectTool = class {
|
|
|
5127
5241
|
}
|
|
5128
5242
|
ctx.requestRender();
|
|
5129
5243
|
}
|
|
5244
|
+
if (!this.hasDragged && this.pendingSingleSelectId !== null) {
|
|
5245
|
+
this._selectedIds = [this.pendingSingleSelectId];
|
|
5246
|
+
}
|
|
5247
|
+
this.pendingSingleSelectId = null;
|
|
5248
|
+
this.hasDragged = false;
|
|
5130
5249
|
this.mode = { type: "idle" };
|
|
5131
5250
|
ctx.setCursor?.("default");
|
|
5132
5251
|
}
|
|
@@ -6307,7 +6426,7 @@ var UpdateLayerCommand = class {
|
|
|
6307
6426
|
};
|
|
6308
6427
|
|
|
6309
6428
|
// src/index.ts
|
|
6310
|
-
var VERSION = "0.
|
|
6429
|
+
var VERSION = "0.12.0";
|
|
6311
6430
|
export {
|
|
6312
6431
|
AddElementCommand,
|
|
6313
6432
|
ArrowTool,
|