@fieldnotes/core 0.35.0 → 0.36.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/README.md +8 -0
- package/dist/index.cjs +310 -63
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +310 -63
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -910,6 +910,14 @@ var KeyboardActions = class {
|
|
|
910
910
|
}
|
|
911
911
|
this.pasteCount = 0;
|
|
912
912
|
}
|
|
913
|
+
cut() {
|
|
914
|
+
if (this.deps.isToolActive()) return;
|
|
915
|
+
this.copy();
|
|
916
|
+
this.deleteSelected();
|
|
917
|
+
}
|
|
918
|
+
hasClipboard() {
|
|
919
|
+
return this.clipboard.length > 0;
|
|
920
|
+
}
|
|
913
921
|
paste() {
|
|
914
922
|
if (this.deps.isToolActive()) return;
|
|
915
923
|
this.flushPendingNudge();
|
|
@@ -978,6 +986,10 @@ var KeyboardActions = class {
|
|
|
978
986
|
if (this.deps.isToolActive()) return;
|
|
979
987
|
this.deps.ungroup?.();
|
|
980
988
|
}
|
|
989
|
+
toggleLock() {
|
|
990
|
+
if (this.deps.isToolActive()) return;
|
|
991
|
+
this.deps.toggleLock?.();
|
|
992
|
+
}
|
|
981
993
|
zOrder(operation) {
|
|
982
994
|
if (this.deps.isToolActive()) return;
|
|
983
995
|
this.flushPendingNudge();
|
|
@@ -1079,6 +1091,8 @@ var DEFAULT_BINDINGS = [
|
|
|
1079
1091
|
["zoom-reset", ["mod+0"]],
|
|
1080
1092
|
["group", ["mod+g"]],
|
|
1081
1093
|
["ungroup", ["mod+shift+g"]],
|
|
1094
|
+
["cut", ["mod+x"]],
|
|
1095
|
+
["toggle-lock", ["mod+shift+l"]],
|
|
1082
1096
|
["nudge-left", ["arrowleft"]],
|
|
1083
1097
|
["nudge-right", ["arrowright"]],
|
|
1084
1098
|
["nudge-up", ["arrowup"]],
|
|
@@ -1217,6 +1231,7 @@ var ShortcutMap = class {
|
|
|
1217
1231
|
var ZOOM_SENSITIVITY = 1e-3;
|
|
1218
1232
|
var ZOOM_STEP = 1.2;
|
|
1219
1233
|
var MIDDLE_BUTTON = 1;
|
|
1234
|
+
var LONG_PRESS_MS = 500;
|
|
1220
1235
|
var NUDGE_DELTAS = {
|
|
1221
1236
|
"nudge-left": [-1, 0],
|
|
1222
1237
|
"nudge-right": [1, 0],
|
|
@@ -1240,8 +1255,10 @@ var InputHandler = class {
|
|
|
1240
1255
|
fitToContent: options.fitToContent,
|
|
1241
1256
|
group: options.group,
|
|
1242
1257
|
ungroup: options.ungroup,
|
|
1258
|
+
toggleLock: options.toggleLock,
|
|
1243
1259
|
getLastPointerWorld: () => this.lastPointerWorld()
|
|
1244
1260
|
});
|
|
1261
|
+
this.openContextMenu = options.openContextMenu;
|
|
1245
1262
|
this.shortcutMap = new ShortcutMap(options.shortcuts?.bindings);
|
|
1246
1263
|
this.scope = options.shortcuts?.scope ?? "focus";
|
|
1247
1264
|
this.element.style.touchAction = "none";
|
|
@@ -1265,10 +1282,13 @@ var InputHandler = class {
|
|
|
1265
1282
|
lastPointerEvent = null;
|
|
1266
1283
|
inputFilter = new InputFilter();
|
|
1267
1284
|
deferredDown = null;
|
|
1285
|
+
longPressTimer = null;
|
|
1286
|
+
longPressStart = null;
|
|
1268
1287
|
abortController = new AbortController();
|
|
1269
1288
|
actions;
|
|
1270
1289
|
shortcutMap;
|
|
1271
1290
|
scope;
|
|
1291
|
+
openContextMenu;
|
|
1272
1292
|
setToolManager(toolManager, toolContext) {
|
|
1273
1293
|
this.toolManager = toolManager;
|
|
1274
1294
|
this.toolContext = toolContext;
|
|
@@ -1283,6 +1303,7 @@ var InputHandler = class {
|
|
|
1283
1303
|
this.actions.dispose();
|
|
1284
1304
|
this.abortController.abort();
|
|
1285
1305
|
this.inputFilter.reset();
|
|
1306
|
+
this.cancelLongPress();
|
|
1286
1307
|
this.deferredDown = null;
|
|
1287
1308
|
this.lastPointerEvent = null;
|
|
1288
1309
|
if (this.scope === "focus") {
|
|
@@ -1298,6 +1319,7 @@ var InputHandler = class {
|
|
|
1298
1319
|
this.element.addEventListener("pointerup", this.onPointerUp, opts);
|
|
1299
1320
|
this.element.addEventListener("pointerleave", this.onPointerLeave, opts);
|
|
1300
1321
|
this.element.addEventListener("pointercancel", this.onPointerUp, opts);
|
|
1322
|
+
this.element.addEventListener("contextmenu", this.onContextMenu, opts);
|
|
1301
1323
|
window.addEventListener("keydown", this.onKeyDown, opts);
|
|
1302
1324
|
window.addEventListener("keyup", this.onKeyUp, opts);
|
|
1303
1325
|
}
|
|
@@ -1326,11 +1348,13 @@ var InputHandler = class {
|
|
|
1326
1348
|
this.activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
|
|
1327
1349
|
this.element.setPointerCapture?.(e.pointerId);
|
|
1328
1350
|
if (this.activePointers.size === 2) {
|
|
1351
|
+
this.cancelLongPress();
|
|
1329
1352
|
this.startPinch();
|
|
1330
1353
|
this.cancelToolIfActive(e);
|
|
1331
1354
|
return;
|
|
1332
1355
|
}
|
|
1333
1356
|
if (e.button === MIDDLE_BUTTON || e.button === 0 && this.spaceHeld) {
|
|
1357
|
+
this.cancelLongPress();
|
|
1334
1358
|
this.isPanning = true;
|
|
1335
1359
|
this.lastPointer = { x: e.clientX, y: e.clientY };
|
|
1336
1360
|
return;
|
|
@@ -1340,6 +1364,7 @@ var InputHandler = class {
|
|
|
1340
1364
|
if (result.action === "suppress") return;
|
|
1341
1365
|
if (result.action === "defer") {
|
|
1342
1366
|
this.deferredDown = e;
|
|
1367
|
+
this.startLongPress(e);
|
|
1343
1368
|
return;
|
|
1344
1369
|
}
|
|
1345
1370
|
this.dispatchToolDown(e);
|
|
@@ -1368,6 +1393,7 @@ var InputHandler = class {
|
|
|
1368
1393
|
} else if (this.deferredDown) {
|
|
1369
1394
|
const result = this.inputFilter.filterMove(e);
|
|
1370
1395
|
if (result.action === "dispatch") {
|
|
1396
|
+
this.cancelLongPress();
|
|
1371
1397
|
this.dispatchToolDown(this.deferredDown);
|
|
1372
1398
|
this.deferredDown = null;
|
|
1373
1399
|
this.dispatchToolMove(e);
|
|
@@ -1377,6 +1403,7 @@ var InputHandler = class {
|
|
|
1377
1403
|
}
|
|
1378
1404
|
};
|
|
1379
1405
|
onPointerUp = (e) => {
|
|
1406
|
+
this.cancelLongPress();
|
|
1380
1407
|
try {
|
|
1381
1408
|
this.element.releasePointerCapture(e.pointerId);
|
|
1382
1409
|
} catch {
|
|
@@ -1429,74 +1456,82 @@ var InputHandler = class {
|
|
|
1429
1456
|
runAction(action, e) {
|
|
1430
1457
|
switch (action) {
|
|
1431
1458
|
case "delete":
|
|
1432
|
-
e
|
|
1459
|
+
e?.preventDefault();
|
|
1433
1460
|
this.actions.deleteSelected();
|
|
1434
1461
|
return;
|
|
1435
1462
|
case "deselect":
|
|
1436
1463
|
this.actions.deselect();
|
|
1437
1464
|
return;
|
|
1438
1465
|
case "undo":
|
|
1439
|
-
e
|
|
1466
|
+
e?.preventDefault();
|
|
1440
1467
|
this.actions.undo();
|
|
1441
1468
|
return;
|
|
1442
1469
|
case "redo":
|
|
1443
|
-
e
|
|
1470
|
+
e?.preventDefault();
|
|
1444
1471
|
this.actions.redo();
|
|
1445
1472
|
return;
|
|
1446
1473
|
case "select-all":
|
|
1447
|
-
e
|
|
1474
|
+
e?.preventDefault();
|
|
1448
1475
|
this.actions.selectAll();
|
|
1449
1476
|
return;
|
|
1450
1477
|
case "copy":
|
|
1451
|
-
e
|
|
1478
|
+
e?.preventDefault();
|
|
1452
1479
|
this.actions.copy();
|
|
1453
1480
|
return;
|
|
1454
1481
|
case "paste":
|
|
1455
|
-
e
|
|
1482
|
+
e?.preventDefault();
|
|
1456
1483
|
this.actions.paste();
|
|
1457
1484
|
return;
|
|
1458
1485
|
case "duplicate":
|
|
1459
|
-
e
|
|
1486
|
+
e?.preventDefault();
|
|
1460
1487
|
this.actions.duplicate();
|
|
1461
1488
|
return;
|
|
1462
1489
|
case "z-forward":
|
|
1463
|
-
e
|
|
1490
|
+
e?.preventDefault();
|
|
1464
1491
|
this.actions.zOrder("forward");
|
|
1465
1492
|
return;
|
|
1466
1493
|
case "z-backward":
|
|
1467
|
-
e
|
|
1494
|
+
e?.preventDefault();
|
|
1468
1495
|
this.actions.zOrder("backward");
|
|
1469
1496
|
return;
|
|
1470
1497
|
case "z-front":
|
|
1471
|
-
e
|
|
1498
|
+
e?.preventDefault();
|
|
1472
1499
|
this.actions.zOrder("front");
|
|
1473
1500
|
return;
|
|
1474
1501
|
case "z-back":
|
|
1475
|
-
e
|
|
1502
|
+
e?.preventDefault();
|
|
1476
1503
|
this.actions.zOrder("back");
|
|
1477
1504
|
return;
|
|
1478
1505
|
case "zoom-fit":
|
|
1479
|
-
e
|
|
1506
|
+
e?.preventDefault();
|
|
1480
1507
|
this.actions.zoomToFit();
|
|
1481
1508
|
return;
|
|
1482
1509
|
case "group":
|
|
1483
|
-
e
|
|
1510
|
+
e?.preventDefault();
|
|
1484
1511
|
this.actions.group();
|
|
1485
1512
|
return;
|
|
1486
1513
|
case "ungroup":
|
|
1487
|
-
e
|
|
1514
|
+
e?.preventDefault();
|
|
1488
1515
|
this.actions.ungroup();
|
|
1489
1516
|
return;
|
|
1517
|
+
case "cut":
|
|
1518
|
+
e?.preventDefault();
|
|
1519
|
+
this.actions.cut();
|
|
1520
|
+
return;
|
|
1521
|
+
case "toggle-lock":
|
|
1522
|
+
e?.preventDefault();
|
|
1523
|
+
this.actions.toggleLock();
|
|
1524
|
+
return;
|
|
1490
1525
|
case "zoom-in":
|
|
1491
|
-
e
|
|
1526
|
+
e?.preventDefault();
|
|
1492
1527
|
this.zoomByFactor(ZOOM_STEP);
|
|
1493
1528
|
return;
|
|
1494
1529
|
case "zoom-out":
|
|
1495
|
-
e
|
|
1530
|
+
e?.preventDefault();
|
|
1496
1531
|
this.zoomByFactor(1 / ZOOM_STEP);
|
|
1497
1532
|
return;
|
|
1498
1533
|
case "zoom-reset":
|
|
1499
|
-
e
|
|
1534
|
+
e?.preventDefault();
|
|
1500
1535
|
this.zoomToLevel(1);
|
|
1501
1536
|
return;
|
|
1502
1537
|
case "nudge-left":
|
|
@@ -1504,22 +1539,26 @@ var InputHandler = class {
|
|
|
1504
1539
|
case "nudge-up":
|
|
1505
1540
|
case "nudge-down": {
|
|
1506
1541
|
const delta = NUDGE_DELTAS[action];
|
|
1507
|
-
if (delta && this.actions.nudge(delta[0], delta[1], e
|
|
1508
|
-
e
|
|
1542
|
+
if (delta && this.actions.nudge(delta[0], delta[1], e?.shiftKey ?? false)) {
|
|
1543
|
+
e?.preventDefault();
|
|
1509
1544
|
}
|
|
1510
1545
|
return;
|
|
1511
1546
|
}
|
|
1512
1547
|
default:
|
|
1513
1548
|
if (action.startsWith("tool:")) {
|
|
1514
1549
|
if (this.isToolActive) return;
|
|
1515
|
-
e
|
|
1550
|
+
e?.preventDefault();
|
|
1516
1551
|
this.toolContext?.switchTool?.(action.slice("tool:".length));
|
|
1517
1552
|
return;
|
|
1518
1553
|
}
|
|
1519
1554
|
console.warn(`[fieldnotes] unknown shortcut action "${action}"`);
|
|
1520
1555
|
}
|
|
1521
1556
|
}
|
|
1557
|
+
hasClipboard() {
|
|
1558
|
+
return this.actions.hasClipboard();
|
|
1559
|
+
}
|
|
1522
1560
|
startPinch() {
|
|
1561
|
+
this.cancelLongPress();
|
|
1523
1562
|
this.inputFilter.reset();
|
|
1524
1563
|
this.deferredDown = null;
|
|
1525
1564
|
this.isPanning = true;
|
|
@@ -1562,6 +1601,13 @@ var InputHandler = class {
|
|
|
1562
1601
|
const rect = this.element.getBoundingClientRect();
|
|
1563
1602
|
return this.camera.screenToWorld({ x: e.clientX - rect.left, y: e.clientY - rect.top });
|
|
1564
1603
|
}
|
|
1604
|
+
onContextMenu = (e) => {
|
|
1605
|
+
e.preventDefault();
|
|
1606
|
+
if (this.toolManager?.activeTool?.name !== "select") return;
|
|
1607
|
+
const rect = this.element.getBoundingClientRect();
|
|
1608
|
+
const world = this.camera.screenToWorld({ x: e.clientX - rect.left, y: e.clientY - rect.top });
|
|
1609
|
+
this.openContextMenu?.({ x: e.clientX, y: e.clientY }, world);
|
|
1610
|
+
};
|
|
1565
1611
|
onPointerLeave = (e) => {
|
|
1566
1612
|
this.lastPointerEvent = null;
|
|
1567
1613
|
this.onPointerUp(e);
|
|
@@ -1609,12 +1655,36 @@ var InputHandler = class {
|
|
|
1609
1655
|
this.element.focus({ preventScroll: true });
|
|
1610
1656
|
}
|
|
1611
1657
|
cancelToolIfActive(e) {
|
|
1658
|
+
this.cancelLongPress();
|
|
1612
1659
|
if (this.isToolActive) {
|
|
1613
1660
|
this.dispatchToolUp(e);
|
|
1614
1661
|
this.isToolActive = false;
|
|
1615
1662
|
}
|
|
1616
1663
|
this.deferredDown = null;
|
|
1617
1664
|
}
|
|
1665
|
+
startLongPress(e) {
|
|
1666
|
+
if (e.pointerType !== "touch") return;
|
|
1667
|
+
if (this.toolManager?.activeTool?.name !== "select") return;
|
|
1668
|
+
this.longPressStart = { x: e.clientX, y: e.clientY };
|
|
1669
|
+
this.longPressTimer = setTimeout(() => this.fireLongPress(), LONG_PRESS_MS);
|
|
1670
|
+
}
|
|
1671
|
+
cancelLongPress() {
|
|
1672
|
+
if (this.longPressTimer !== null) {
|
|
1673
|
+
clearTimeout(this.longPressTimer);
|
|
1674
|
+
this.longPressTimer = null;
|
|
1675
|
+
}
|
|
1676
|
+
this.longPressStart = null;
|
|
1677
|
+
}
|
|
1678
|
+
fireLongPress() {
|
|
1679
|
+
this.longPressTimer = null;
|
|
1680
|
+
if (!this.deferredDown || this.activePointers.size !== 1 || this.isPanning) return;
|
|
1681
|
+
const start = this.longPressStart;
|
|
1682
|
+
if (!start) return;
|
|
1683
|
+
const rect = this.element.getBoundingClientRect();
|
|
1684
|
+
const world = this.camera.screenToWorld({ x: start.x - rect.left, y: start.y - rect.top });
|
|
1685
|
+
this.deferredDown = null;
|
|
1686
|
+
this.openContextMenu?.({ x: start.x, y: start.y }, world);
|
|
1687
|
+
}
|
|
1618
1688
|
};
|
|
1619
1689
|
|
|
1620
1690
|
// src/canvas/background.ts
|
|
@@ -3801,6 +3871,87 @@ var NoteEditor = class {
|
|
|
3801
3871
|
}
|
|
3802
3872
|
};
|
|
3803
3873
|
|
|
3874
|
+
// src/canvas/context-menu.ts
|
|
3875
|
+
var ContextMenu = class {
|
|
3876
|
+
constructor(options) {
|
|
3877
|
+
this.options = options;
|
|
3878
|
+
}
|
|
3879
|
+
el = null;
|
|
3880
|
+
outsideListener = null;
|
|
3881
|
+
keyListener = null;
|
|
3882
|
+
isOpen() {
|
|
3883
|
+
return this.el !== null;
|
|
3884
|
+
}
|
|
3885
|
+
open(items, screenPos) {
|
|
3886
|
+
this.close();
|
|
3887
|
+
const el = document.createElement("div");
|
|
3888
|
+
el.className = "fieldnotes-context-menu";
|
|
3889
|
+
Object.assign(el.style, {
|
|
3890
|
+
position: "fixed",
|
|
3891
|
+
left: `${screenPos.x}px`,
|
|
3892
|
+
top: `${screenPos.y}px`,
|
|
3893
|
+
zIndex: "10000",
|
|
3894
|
+
display: "flex",
|
|
3895
|
+
flexDirection: "column"
|
|
3896
|
+
});
|
|
3897
|
+
for (const item of items) {
|
|
3898
|
+
const btn = document.createElement("button");
|
|
3899
|
+
btn.type = "button";
|
|
3900
|
+
btn.className = "fieldnotes-context-menu-item" + (item.disabled ? " fieldnotes-context-menu-item--disabled" : "");
|
|
3901
|
+
btn.textContent = item.label;
|
|
3902
|
+
if (item.disabled) {
|
|
3903
|
+
btn.disabled = true;
|
|
3904
|
+
} else {
|
|
3905
|
+
btn.addEventListener("click", () => {
|
|
3906
|
+
this.options.onCommand(item.action);
|
|
3907
|
+
this.close();
|
|
3908
|
+
});
|
|
3909
|
+
}
|
|
3910
|
+
el.appendChild(btn);
|
|
3911
|
+
}
|
|
3912
|
+
document.body.appendChild(el);
|
|
3913
|
+
this.el = el;
|
|
3914
|
+
this.clampToViewport(el, screenPos);
|
|
3915
|
+
this.keyListener = (e) => {
|
|
3916
|
+
if (e.key === "Escape") this.close();
|
|
3917
|
+
};
|
|
3918
|
+
document.addEventListener("keydown", this.keyListener);
|
|
3919
|
+
this.outsideListener = (e) => {
|
|
3920
|
+
if (this.el && !this.el.contains(e.target)) this.close();
|
|
3921
|
+
};
|
|
3922
|
+
setTimeout(() => {
|
|
3923
|
+
if (this.outsideListener) document.addEventListener("pointerdown", this.outsideListener);
|
|
3924
|
+
}, 0);
|
|
3925
|
+
}
|
|
3926
|
+
close() {
|
|
3927
|
+
if (this.keyListener) {
|
|
3928
|
+
document.removeEventListener("keydown", this.keyListener);
|
|
3929
|
+
this.keyListener = null;
|
|
3930
|
+
}
|
|
3931
|
+
if (this.outsideListener) {
|
|
3932
|
+
document.removeEventListener("pointerdown", this.outsideListener);
|
|
3933
|
+
this.outsideListener = null;
|
|
3934
|
+
}
|
|
3935
|
+
if (this.el) {
|
|
3936
|
+
this.el.remove();
|
|
3937
|
+
this.el = null;
|
|
3938
|
+
this.options.onClose();
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3941
|
+
dispose() {
|
|
3942
|
+
this.close();
|
|
3943
|
+
}
|
|
3944
|
+
clampToViewport(el, screenPos) {
|
|
3945
|
+
const rect = el.getBoundingClientRect();
|
|
3946
|
+
if (rect.width > 0 && screenPos.x + rect.width > window.innerWidth) {
|
|
3947
|
+
el.style.left = `${Math.max(0, screenPos.x - rect.width)}px`;
|
|
3948
|
+
}
|
|
3949
|
+
if (rect.height > 0 && screenPos.y + rect.height > window.innerHeight) {
|
|
3950
|
+
el.style.top = `${Math.max(0, screenPos.y - rect.height)}px`;
|
|
3951
|
+
}
|
|
3952
|
+
}
|
|
3953
|
+
};
|
|
3954
|
+
|
|
3804
3955
|
// src/elements/translate.ts
|
|
3805
3956
|
function translateElementPatch(el, dx, dy) {
|
|
3806
3957
|
const position = { x: el.position.x + dx, y: el.position.y + dy };
|
|
@@ -5617,8 +5768,20 @@ var Viewport = class {
|
|
|
5617
5768
|
fitToContent: () => this.fitToContent(),
|
|
5618
5769
|
group: () => this.groupSelection(),
|
|
5619
5770
|
ungroup: () => this.ungroupSelection(),
|
|
5771
|
+
toggleLock: () => this.toggleLockSelection(),
|
|
5772
|
+
openContextMenu: (screenPos, world) => {
|
|
5773
|
+
this.getSelectTool()?.selectAtPoint(world, this.toolContext);
|
|
5774
|
+
this.openContextMenu(screenPos);
|
|
5775
|
+
},
|
|
5620
5776
|
shortcuts: options.shortcuts
|
|
5621
5777
|
});
|
|
5778
|
+
if (options.contextMenu !== false) {
|
|
5779
|
+
this.contextMenu = new ContextMenu({
|
|
5780
|
+
onCommand: (action) => this.runAction(action),
|
|
5781
|
+
onClose: noop
|
|
5782
|
+
});
|
|
5783
|
+
}
|
|
5784
|
+
this.unsubToolChange = this.toolManager.onChange(() => this.contextMenu?.close());
|
|
5622
5785
|
this.domNodeManager = new DomNodeManager({
|
|
5623
5786
|
domLayer: this.domLayer,
|
|
5624
5787
|
onEditRequest: (id) => this.startEditingElement(id),
|
|
@@ -5650,6 +5813,7 @@ var Viewport = class {
|
|
|
5650
5813
|
this.unsubCamera = this.camera.onChange(() => {
|
|
5651
5814
|
this.applyCameraTransform();
|
|
5652
5815
|
this.noteEditor.updateToolbarPosition();
|
|
5816
|
+
this.contextMenu?.close();
|
|
5653
5817
|
this.requestRender();
|
|
5654
5818
|
});
|
|
5655
5819
|
this.unsubStore = [
|
|
@@ -5702,6 +5866,7 @@ var Viewport = class {
|
|
|
5702
5866
|
canvasEl;
|
|
5703
5867
|
wrapper;
|
|
5704
5868
|
unsubCamera;
|
|
5869
|
+
unsubToolChange;
|
|
5705
5870
|
unsubStore;
|
|
5706
5871
|
inputHandler;
|
|
5707
5872
|
background;
|
|
@@ -5724,6 +5889,7 @@ var Viewport = class {
|
|
|
5724
5889
|
doubleTapDetector = new DoubleTapDetector();
|
|
5725
5890
|
tapDownX = 0;
|
|
5726
5891
|
tapDownY = 0;
|
|
5892
|
+
contextMenu = null;
|
|
5727
5893
|
get ctx() {
|
|
5728
5894
|
return this.canvasEl.getContext("2d");
|
|
5729
5895
|
}
|
|
@@ -5914,6 +6080,34 @@ var Viewport = class {
|
|
|
5914
6080
|
getSelectedIds() {
|
|
5915
6081
|
return this.getSelectTool()?.selectedIds ?? EMPTY_IDS;
|
|
5916
6082
|
}
|
|
6083
|
+
runAction(action) {
|
|
6084
|
+
this.inputHandler.runAction(action);
|
|
6085
|
+
}
|
|
6086
|
+
canPaste() {
|
|
6087
|
+
return this.inputHandler.hasClipboard();
|
|
6088
|
+
}
|
|
6089
|
+
openContextMenu(screenPos) {
|
|
6090
|
+
if (!this.contextMenu) return;
|
|
6091
|
+
const ids = this.getSelectedIds();
|
|
6092
|
+
const items = [];
|
|
6093
|
+
if (ids.length > 0) {
|
|
6094
|
+
items.push({ label: "Cut", action: "cut" });
|
|
6095
|
+
items.push({ label: "Copy", action: "copy" });
|
|
6096
|
+
if (this.canPaste()) items.push({ label: "Paste", action: "paste" });
|
|
6097
|
+
items.push({ label: "Duplicate", action: "duplicate" });
|
|
6098
|
+
items.push({ label: "Delete", action: "delete" });
|
|
6099
|
+
items.push({ label: "Bring to Front", action: "z-front" });
|
|
6100
|
+
items.push({ label: "Bring Forward", action: "z-forward" });
|
|
6101
|
+
items.push({ label: "Send Backward", action: "z-backward" });
|
|
6102
|
+
items.push({ label: "Send to Back", action: "z-back" });
|
|
6103
|
+
const allLocked = ids.every((id) => this.store.getById(id)?.locked);
|
|
6104
|
+
items.push({ label: allLocked ? "Unlock" : "Lock", action: "toggle-lock" });
|
|
6105
|
+
} else if (this.canPaste()) {
|
|
6106
|
+
items.push({ label: "Paste", action: "paste" });
|
|
6107
|
+
}
|
|
6108
|
+
if (items.length === 0) return;
|
|
6109
|
+
this.contextMenu.open(items, screenPos);
|
|
6110
|
+
}
|
|
5917
6111
|
onSelectionChange(listener) {
|
|
5918
6112
|
const tool = this.getSelectTool();
|
|
5919
6113
|
return tool ? tool.onSelectionChange(listener) : noop;
|
|
@@ -5974,6 +6168,20 @@ var Viewport = class {
|
|
|
5974
6168
|
}
|
|
5975
6169
|
this.historyRecorder.commit();
|
|
5976
6170
|
}
|
|
6171
|
+
toggleLockSelection() {
|
|
6172
|
+
const ids = this.getSelectedIds();
|
|
6173
|
+
if (ids.length === 0) return;
|
|
6174
|
+
const anyUnlocked = ids.some((id) => {
|
|
6175
|
+
const el = this.store.getById(id);
|
|
6176
|
+
return el ? !el.locked : false;
|
|
6177
|
+
});
|
|
6178
|
+
this.historyRecorder.begin();
|
|
6179
|
+
for (const id of ids) {
|
|
6180
|
+
const el = this.store.getById(id);
|
|
6181
|
+
if (el && el.locked !== anyUnlocked) this.store.update(id, { locked: anyUnlocked });
|
|
6182
|
+
}
|
|
6183
|
+
this.historyRecorder.commit();
|
|
6184
|
+
}
|
|
5977
6185
|
alignSelection(edge) {
|
|
5978
6186
|
const bounded = this.boundedSelection();
|
|
5979
6187
|
if (bounded.length < 2) return;
|
|
@@ -6072,12 +6280,14 @@ var Viewport = class {
|
|
|
6072
6280
|
this.noteEditor.destroy(this.store);
|
|
6073
6281
|
this.arrowLabelEditor.cancel();
|
|
6074
6282
|
this.historyRecorder.destroy();
|
|
6283
|
+
this.contextMenu?.dispose();
|
|
6075
6284
|
this.wrapper.removeEventListener("pointerdown", this.onTapDown);
|
|
6076
6285
|
this.wrapper.removeEventListener("pointerup", this.onDoubleTap);
|
|
6077
6286
|
this.wrapper.removeEventListener("dragover", this.onDragOver);
|
|
6078
6287
|
this.wrapper.removeEventListener("drop", this.onDrop);
|
|
6079
6288
|
this.inputHandler.destroy();
|
|
6080
6289
|
this.unsubCamera();
|
|
6290
|
+
this.unsubToolChange();
|
|
6081
6291
|
this.unsubStore.forEach((fn) => fn());
|
|
6082
6292
|
this.resizeObserver?.disconnect();
|
|
6083
6293
|
this.resizeObserver = null;
|
|
@@ -6905,6 +7115,15 @@ var SelectTool = class {
|
|
|
6905
7115
|
this.setSelectedIds(ids);
|
|
6906
7116
|
this.ctx?.requestRender();
|
|
6907
7117
|
}
|
|
7118
|
+
selectAtPoint(world, ctx) {
|
|
7119
|
+
const hit = this.hitTest(world, ctx);
|
|
7120
|
+
if (!hit) {
|
|
7121
|
+
this.setSelectedIds([]);
|
|
7122
|
+
return;
|
|
7123
|
+
}
|
|
7124
|
+
if (this._selectedIds.includes(hit.id)) return;
|
|
7125
|
+
this.setSelectedIds(expandToGroups([hit.id], ctx.store.getAll()));
|
|
7126
|
+
}
|
|
6908
7127
|
get isMarqueeActive() {
|
|
6909
7128
|
return this.mode.type === "marquee";
|
|
6910
7129
|
}
|
|
@@ -7408,6 +7627,7 @@ var SelectTool = class {
|
|
|
7408
7627
|
for (const id of this._selectedIds) {
|
|
7409
7628
|
const el = ctx.store.getById(id);
|
|
7410
7629
|
if (!el || !("size" in el)) continue;
|
|
7630
|
+
if (el.locked) continue;
|
|
7411
7631
|
if (el.type === "shape" && el.shape === "line") continue;
|
|
7412
7632
|
const layout = this.getOverlayLayout(el, zoom);
|
|
7413
7633
|
if (!layout) continue;
|
|
@@ -7543,62 +7763,89 @@ var SelectTool = class {
|
|
|
7543
7763
|
canvasCtx.stroke();
|
|
7544
7764
|
}
|
|
7545
7765
|
}
|
|
7546
|
-
if (
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
|
|
7766
|
+
if (!el.locked) {
|
|
7767
|
+
if ("size" in el) {
|
|
7768
|
+
canvasCtx.setLineDash([]);
|
|
7769
|
+
canvasCtx.fillStyle = "#ffffff";
|
|
7770
|
+
const corners = layout.angle === 0 ? this.getHandlePositions(bounds) : layout.corners;
|
|
7771
|
+
for (const [, pos] of corners) {
|
|
7772
|
+
canvasCtx.fillRect(
|
|
7773
|
+
pos.x - handleWorldSize / 2,
|
|
7774
|
+
pos.y - handleWorldSize / 2,
|
|
7775
|
+
handleWorldSize,
|
|
7776
|
+
handleWorldSize
|
|
7777
|
+
);
|
|
7778
|
+
canvasCtx.strokeRect(
|
|
7779
|
+
pos.x - handleWorldSize / 2,
|
|
7780
|
+
pos.y - handleWorldSize / 2,
|
|
7781
|
+
handleWorldSize,
|
|
7782
|
+
handleWorldSize
|
|
7783
|
+
);
|
|
7784
|
+
}
|
|
7785
|
+
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7786
|
+
} else if (el.type === "template") {
|
|
7787
|
+
canvasCtx.setLineDash([]);
|
|
7788
|
+
canvasCtx.fillStyle = "#ffffff";
|
|
7789
|
+
const hx = bounds.x + bounds.w;
|
|
7790
|
+
const hy = bounds.y + bounds.h;
|
|
7551
7791
|
canvasCtx.fillRect(
|
|
7552
|
-
|
|
7553
|
-
|
|
7792
|
+
hx - handleWorldSize / 2,
|
|
7793
|
+
hy - handleWorldSize / 2,
|
|
7554
7794
|
handleWorldSize,
|
|
7555
7795
|
handleWorldSize
|
|
7556
7796
|
);
|
|
7557
7797
|
canvasCtx.strokeRect(
|
|
7558
|
-
|
|
7559
|
-
|
|
7798
|
+
hx - handleWorldSize / 2,
|
|
7799
|
+
hy - handleWorldSize / 2,
|
|
7560
7800
|
handleWorldSize,
|
|
7561
7801
|
handleWorldSize
|
|
7562
7802
|
);
|
|
7803
|
+
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7804
|
+
}
|
|
7805
|
+
if (this._selectedIds.length === 1 && ROTATABLE_TYPES.has(el.type)) {
|
|
7806
|
+
const stemStart = this.topMidpoint(layout);
|
|
7807
|
+
const stemEnd = layout.rotateHandle;
|
|
7808
|
+
canvasCtx.beginPath();
|
|
7809
|
+
canvasCtx.moveTo(stemStart.x, stemStart.y);
|
|
7810
|
+
canvasCtx.lineTo(stemEnd.x, stemEnd.y);
|
|
7811
|
+
canvasCtx.stroke();
|
|
7812
|
+
canvasCtx.setLineDash([]);
|
|
7813
|
+
canvasCtx.fillStyle = "#ffffff";
|
|
7814
|
+
canvasCtx.beginPath();
|
|
7815
|
+
canvasCtx.arc(stemEnd.x, stemEnd.y, handleWorldSize / 2, 0, Math.PI * 2);
|
|
7816
|
+
canvasCtx.fill();
|
|
7817
|
+
canvasCtx.stroke();
|
|
7818
|
+
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7563
7819
|
}
|
|
7564
|
-
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7565
|
-
} else if (el.type === "template") {
|
|
7566
|
-
canvasCtx.setLineDash([]);
|
|
7567
|
-
canvasCtx.fillStyle = "#ffffff";
|
|
7568
|
-
const hx = bounds.x + bounds.w;
|
|
7569
|
-
const hy = bounds.y + bounds.h;
|
|
7570
|
-
canvasCtx.fillRect(
|
|
7571
|
-
hx - handleWorldSize / 2,
|
|
7572
|
-
hy - handleWorldSize / 2,
|
|
7573
|
-
handleWorldSize,
|
|
7574
|
-
handleWorldSize
|
|
7575
|
-
);
|
|
7576
|
-
canvasCtx.strokeRect(
|
|
7577
|
-
hx - handleWorldSize / 2,
|
|
7578
|
-
hy - handleWorldSize / 2,
|
|
7579
|
-
handleWorldSize,
|
|
7580
|
-
handleWorldSize
|
|
7581
|
-
);
|
|
7582
|
-
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7583
7820
|
}
|
|
7584
|
-
if (
|
|
7585
|
-
const
|
|
7586
|
-
|
|
7587
|
-
canvasCtx.beginPath();
|
|
7588
|
-
canvasCtx.moveTo(stemStart.x, stemStart.y);
|
|
7589
|
-
canvasCtx.lineTo(stemEnd.x, stemEnd.y);
|
|
7590
|
-
canvasCtx.stroke();
|
|
7591
|
-
canvasCtx.setLineDash([]);
|
|
7592
|
-
canvasCtx.fillStyle = "#ffffff";
|
|
7593
|
-
canvasCtx.beginPath();
|
|
7594
|
-
canvasCtx.arc(stemEnd.x, stemEnd.y, handleWorldSize / 2, 0, Math.PI * 2);
|
|
7595
|
-
canvasCtx.fill();
|
|
7596
|
-
canvasCtx.stroke();
|
|
7597
|
-
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7821
|
+
if (el.locked) {
|
|
7822
|
+
const ne = layout.corners.find(([h]) => h === "ne")?.[1];
|
|
7823
|
+
if (ne) this.drawLockBadge(canvasCtx, ne, zoom);
|
|
7598
7824
|
}
|
|
7599
7825
|
}
|
|
7600
7826
|
canvasCtx.restore();
|
|
7601
7827
|
}
|
|
7828
|
+
drawLockBadge(ctx, at, zoom) {
|
|
7829
|
+
const r = 9 / zoom;
|
|
7830
|
+
ctx.save();
|
|
7831
|
+
ctx.setLineDash([]);
|
|
7832
|
+
ctx.beginPath();
|
|
7833
|
+
ctx.arc(at.x, at.y, r, 0, Math.PI * 2);
|
|
7834
|
+
ctx.fillStyle = "#ffffff";
|
|
7835
|
+
ctx.fill();
|
|
7836
|
+
ctx.strokeStyle = "#2196F3";
|
|
7837
|
+
ctx.lineWidth = 1.5 / zoom;
|
|
7838
|
+
ctx.stroke();
|
|
7839
|
+
const bw = 8 / zoom;
|
|
7840
|
+
const bh = 6 / zoom;
|
|
7841
|
+
ctx.fillStyle = "#2196F3";
|
|
7842
|
+
ctx.fillRect(at.x - bw / 2, at.y - bh / 2 + 1 / zoom, bw, bh);
|
|
7843
|
+
ctx.beginPath();
|
|
7844
|
+
ctx.arc(at.x, at.y - bh / 2 + 1 / zoom, 2.5 / zoom, Math.PI, 0);
|
|
7845
|
+
ctx.lineWidth = 1.4 / zoom;
|
|
7846
|
+
ctx.stroke();
|
|
7847
|
+
ctx.restore();
|
|
7848
|
+
}
|
|
7602
7849
|
renderBindingHighlights(canvasCtx, arrow, zoom) {
|
|
7603
7850
|
if (!this.ctx) return;
|
|
7604
7851
|
if (!arrow.fromBinding && !arrow.toBinding) return;
|
|
@@ -8571,7 +8818,7 @@ var TemplateTool = class {
|
|
|
8571
8818
|
};
|
|
8572
8819
|
|
|
8573
8820
|
// src/index.ts
|
|
8574
|
-
var VERSION = "0.
|
|
8821
|
+
var VERSION = "0.36.0";
|
|
8575
8822
|
export {
|
|
8576
8823
|
ArrowTool,
|
|
8577
8824
|
AutoSave,
|