@fieldnotes/core 0.35.0 → 0.37.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 +656 -648
- package/dist/index.cjs +335 -66
- 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 +335 -66
- 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
|
|
@@ -1932,9 +2002,10 @@ var Quadtree = class {
|
|
|
1932
2002
|
};
|
|
1933
2003
|
|
|
1934
2004
|
// src/elements/stroke-smoothing.ts
|
|
1935
|
-
var MIN_PRESSURE_SCALE = 0.
|
|
2005
|
+
var MIN_PRESSURE_SCALE = 0.4;
|
|
2006
|
+
var MAX_PRESSURE_SCALE = 1.8;
|
|
1936
2007
|
function pressureToWidth(pressure, baseWidth) {
|
|
1937
|
-
return baseWidth * (MIN_PRESSURE_SCALE + (
|
|
2008
|
+
return baseWidth * (MIN_PRESSURE_SCALE + (MAX_PRESSURE_SCALE - MIN_PRESSURE_SCALE) * pressure);
|
|
1938
2009
|
}
|
|
1939
2010
|
function simplifyPoints(points, tolerance) {
|
|
1940
2011
|
if (points.length <= 2) return points.slice();
|
|
@@ -3597,12 +3668,33 @@ var NoteToolbar = class {
|
|
|
3597
3668
|
e.stopPropagation();
|
|
3598
3669
|
});
|
|
3599
3670
|
select.addEventListener("change", () => {
|
|
3600
|
-
|
|
3671
|
+
this.applyFontSize(Number(select.value));
|
|
3601
3672
|
this.updateActiveStates();
|
|
3602
3673
|
this.anchor?.focus();
|
|
3603
3674
|
});
|
|
3604
3675
|
return select;
|
|
3605
3676
|
}
|
|
3677
|
+
applyFontSize(size) {
|
|
3678
|
+
const sel = window.getSelection();
|
|
3679
|
+
const collapsed = !sel || sel.rangeCount === 0 || sel.getRangeAt(0).collapsed;
|
|
3680
|
+
if (collapsed && this.anchor) {
|
|
3681
|
+
const range = document.createRange();
|
|
3682
|
+
range.selectNodeContents(this.anchor);
|
|
3683
|
+
sel?.removeAllRanges();
|
|
3684
|
+
sel?.addRange(range);
|
|
3685
|
+
setFontSize(size);
|
|
3686
|
+
const after = window.getSelection();
|
|
3687
|
+
if (after) {
|
|
3688
|
+
const caret = document.createRange();
|
|
3689
|
+
caret.selectNodeContents(this.anchor);
|
|
3690
|
+
caret.collapse(false);
|
|
3691
|
+
after.removeAllRanges();
|
|
3692
|
+
after.addRange(caret);
|
|
3693
|
+
}
|
|
3694
|
+
return;
|
|
3695
|
+
}
|
|
3696
|
+
setFontSize(size);
|
|
3697
|
+
}
|
|
3606
3698
|
positionToolbar(anchor) {
|
|
3607
3699
|
if (!this.el) return;
|
|
3608
3700
|
const rect = anchor.getBoundingClientRect();
|
|
@@ -3801,6 +3893,87 @@ var NoteEditor = class {
|
|
|
3801
3893
|
}
|
|
3802
3894
|
};
|
|
3803
3895
|
|
|
3896
|
+
// src/canvas/context-menu.ts
|
|
3897
|
+
var ContextMenu = class {
|
|
3898
|
+
constructor(options) {
|
|
3899
|
+
this.options = options;
|
|
3900
|
+
}
|
|
3901
|
+
el = null;
|
|
3902
|
+
outsideListener = null;
|
|
3903
|
+
keyListener = null;
|
|
3904
|
+
isOpen() {
|
|
3905
|
+
return this.el !== null;
|
|
3906
|
+
}
|
|
3907
|
+
open(items, screenPos) {
|
|
3908
|
+
this.close();
|
|
3909
|
+
const el = document.createElement("div");
|
|
3910
|
+
el.className = "fieldnotes-context-menu";
|
|
3911
|
+
Object.assign(el.style, {
|
|
3912
|
+
position: "fixed",
|
|
3913
|
+
left: `${screenPos.x}px`,
|
|
3914
|
+
top: `${screenPos.y}px`,
|
|
3915
|
+
zIndex: "10000",
|
|
3916
|
+
display: "flex",
|
|
3917
|
+
flexDirection: "column"
|
|
3918
|
+
});
|
|
3919
|
+
for (const item of items) {
|
|
3920
|
+
const btn = document.createElement("button");
|
|
3921
|
+
btn.type = "button";
|
|
3922
|
+
btn.className = "fieldnotes-context-menu-item" + (item.disabled ? " fieldnotes-context-menu-item--disabled" : "");
|
|
3923
|
+
btn.textContent = item.label;
|
|
3924
|
+
if (item.disabled) {
|
|
3925
|
+
btn.disabled = true;
|
|
3926
|
+
} else {
|
|
3927
|
+
btn.addEventListener("click", () => {
|
|
3928
|
+
this.options.onCommand(item.action);
|
|
3929
|
+
this.close();
|
|
3930
|
+
});
|
|
3931
|
+
}
|
|
3932
|
+
el.appendChild(btn);
|
|
3933
|
+
}
|
|
3934
|
+
document.body.appendChild(el);
|
|
3935
|
+
this.el = el;
|
|
3936
|
+
this.clampToViewport(el, screenPos);
|
|
3937
|
+
this.keyListener = (e) => {
|
|
3938
|
+
if (e.key === "Escape") this.close();
|
|
3939
|
+
};
|
|
3940
|
+
document.addEventListener("keydown", this.keyListener);
|
|
3941
|
+
this.outsideListener = (e) => {
|
|
3942
|
+
if (this.el && !this.el.contains(e.target)) this.close();
|
|
3943
|
+
};
|
|
3944
|
+
setTimeout(() => {
|
|
3945
|
+
if (this.outsideListener) document.addEventListener("pointerdown", this.outsideListener);
|
|
3946
|
+
}, 0);
|
|
3947
|
+
}
|
|
3948
|
+
close() {
|
|
3949
|
+
if (this.keyListener) {
|
|
3950
|
+
document.removeEventListener("keydown", this.keyListener);
|
|
3951
|
+
this.keyListener = null;
|
|
3952
|
+
}
|
|
3953
|
+
if (this.outsideListener) {
|
|
3954
|
+
document.removeEventListener("pointerdown", this.outsideListener);
|
|
3955
|
+
this.outsideListener = null;
|
|
3956
|
+
}
|
|
3957
|
+
if (this.el) {
|
|
3958
|
+
this.el.remove();
|
|
3959
|
+
this.el = null;
|
|
3960
|
+
this.options.onClose();
|
|
3961
|
+
}
|
|
3962
|
+
}
|
|
3963
|
+
dispose() {
|
|
3964
|
+
this.close();
|
|
3965
|
+
}
|
|
3966
|
+
clampToViewport(el, screenPos) {
|
|
3967
|
+
const rect = el.getBoundingClientRect();
|
|
3968
|
+
if (rect.width > 0 && screenPos.x + rect.width > window.innerWidth) {
|
|
3969
|
+
el.style.left = `${Math.max(0, screenPos.x - rect.width)}px`;
|
|
3970
|
+
}
|
|
3971
|
+
if (rect.height > 0 && screenPos.y + rect.height > window.innerHeight) {
|
|
3972
|
+
el.style.top = `${Math.max(0, screenPos.y - rect.height)}px`;
|
|
3973
|
+
}
|
|
3974
|
+
}
|
|
3975
|
+
};
|
|
3976
|
+
|
|
3804
3977
|
// src/elements/translate.ts
|
|
3805
3978
|
function translateElementPatch(el, dx, dy) {
|
|
3806
3979
|
const position = { x: el.position.x + dx, y: el.position.y + dy };
|
|
@@ -5617,8 +5790,20 @@ var Viewport = class {
|
|
|
5617
5790
|
fitToContent: () => this.fitToContent(),
|
|
5618
5791
|
group: () => this.groupSelection(),
|
|
5619
5792
|
ungroup: () => this.ungroupSelection(),
|
|
5793
|
+
toggleLock: () => this.toggleLockSelection(),
|
|
5794
|
+
openContextMenu: (screenPos, world) => {
|
|
5795
|
+
this.getSelectTool()?.selectAtPoint(world, this.toolContext);
|
|
5796
|
+
this.openContextMenu(screenPos);
|
|
5797
|
+
},
|
|
5620
5798
|
shortcuts: options.shortcuts
|
|
5621
5799
|
});
|
|
5800
|
+
if (options.contextMenu !== false) {
|
|
5801
|
+
this.contextMenu = new ContextMenu({
|
|
5802
|
+
onCommand: (action) => this.runAction(action),
|
|
5803
|
+
onClose: noop
|
|
5804
|
+
});
|
|
5805
|
+
}
|
|
5806
|
+
this.unsubToolChange = this.toolManager.onChange(() => this.contextMenu?.close());
|
|
5622
5807
|
this.domNodeManager = new DomNodeManager({
|
|
5623
5808
|
domLayer: this.domLayer,
|
|
5624
5809
|
onEditRequest: (id) => this.startEditingElement(id),
|
|
@@ -5650,6 +5835,7 @@ var Viewport = class {
|
|
|
5650
5835
|
this.unsubCamera = this.camera.onChange(() => {
|
|
5651
5836
|
this.applyCameraTransform();
|
|
5652
5837
|
this.noteEditor.updateToolbarPosition();
|
|
5838
|
+
this.contextMenu?.close();
|
|
5653
5839
|
this.requestRender();
|
|
5654
5840
|
});
|
|
5655
5841
|
this.unsubStore = [
|
|
@@ -5702,6 +5888,7 @@ var Viewport = class {
|
|
|
5702
5888
|
canvasEl;
|
|
5703
5889
|
wrapper;
|
|
5704
5890
|
unsubCamera;
|
|
5891
|
+
unsubToolChange;
|
|
5705
5892
|
unsubStore;
|
|
5706
5893
|
inputHandler;
|
|
5707
5894
|
background;
|
|
@@ -5724,6 +5911,7 @@ var Viewport = class {
|
|
|
5724
5911
|
doubleTapDetector = new DoubleTapDetector();
|
|
5725
5912
|
tapDownX = 0;
|
|
5726
5913
|
tapDownY = 0;
|
|
5914
|
+
contextMenu = null;
|
|
5727
5915
|
get ctx() {
|
|
5728
5916
|
return this.canvasEl.getContext("2d");
|
|
5729
5917
|
}
|
|
@@ -5914,6 +6102,34 @@ var Viewport = class {
|
|
|
5914
6102
|
getSelectedIds() {
|
|
5915
6103
|
return this.getSelectTool()?.selectedIds ?? EMPTY_IDS;
|
|
5916
6104
|
}
|
|
6105
|
+
runAction(action) {
|
|
6106
|
+
this.inputHandler.runAction(action);
|
|
6107
|
+
}
|
|
6108
|
+
canPaste() {
|
|
6109
|
+
return this.inputHandler.hasClipboard();
|
|
6110
|
+
}
|
|
6111
|
+
openContextMenu(screenPos) {
|
|
6112
|
+
if (!this.contextMenu) return;
|
|
6113
|
+
const ids = this.getSelectedIds();
|
|
6114
|
+
const items = [];
|
|
6115
|
+
if (ids.length > 0) {
|
|
6116
|
+
items.push({ label: "Cut", action: "cut" });
|
|
6117
|
+
items.push({ label: "Copy", action: "copy" });
|
|
6118
|
+
if (this.canPaste()) items.push({ label: "Paste", action: "paste" });
|
|
6119
|
+
items.push({ label: "Duplicate", action: "duplicate" });
|
|
6120
|
+
items.push({ label: "Delete", action: "delete" });
|
|
6121
|
+
items.push({ label: "Bring to Front", action: "z-front" });
|
|
6122
|
+
items.push({ label: "Bring Forward", action: "z-forward" });
|
|
6123
|
+
items.push({ label: "Send Backward", action: "z-backward" });
|
|
6124
|
+
items.push({ label: "Send to Back", action: "z-back" });
|
|
6125
|
+
const allLocked = ids.every((id) => this.store.getById(id)?.locked);
|
|
6126
|
+
items.push({ label: allLocked ? "Unlock" : "Lock", action: "toggle-lock" });
|
|
6127
|
+
} else if (this.canPaste()) {
|
|
6128
|
+
items.push({ label: "Paste", action: "paste" });
|
|
6129
|
+
}
|
|
6130
|
+
if (items.length === 0) return;
|
|
6131
|
+
this.contextMenu.open(items, screenPos);
|
|
6132
|
+
}
|
|
5917
6133
|
onSelectionChange(listener) {
|
|
5918
6134
|
const tool = this.getSelectTool();
|
|
5919
6135
|
return tool ? tool.onSelectionChange(listener) : noop;
|
|
@@ -5974,6 +6190,20 @@ var Viewport = class {
|
|
|
5974
6190
|
}
|
|
5975
6191
|
this.historyRecorder.commit();
|
|
5976
6192
|
}
|
|
6193
|
+
toggleLockSelection() {
|
|
6194
|
+
const ids = this.getSelectedIds();
|
|
6195
|
+
if (ids.length === 0) return;
|
|
6196
|
+
const anyUnlocked = ids.some((id) => {
|
|
6197
|
+
const el = this.store.getById(id);
|
|
6198
|
+
return el ? !el.locked : false;
|
|
6199
|
+
});
|
|
6200
|
+
this.historyRecorder.begin();
|
|
6201
|
+
for (const id of ids) {
|
|
6202
|
+
const el = this.store.getById(id);
|
|
6203
|
+
if (el && el.locked !== anyUnlocked) this.store.update(id, { locked: anyUnlocked });
|
|
6204
|
+
}
|
|
6205
|
+
this.historyRecorder.commit();
|
|
6206
|
+
}
|
|
5977
6207
|
alignSelection(edge) {
|
|
5978
6208
|
const bounded = this.boundedSelection();
|
|
5979
6209
|
if (bounded.length < 2) return;
|
|
@@ -6072,12 +6302,14 @@ var Viewport = class {
|
|
|
6072
6302
|
this.noteEditor.destroy(this.store);
|
|
6073
6303
|
this.arrowLabelEditor.cancel();
|
|
6074
6304
|
this.historyRecorder.destroy();
|
|
6305
|
+
this.contextMenu?.dispose();
|
|
6075
6306
|
this.wrapper.removeEventListener("pointerdown", this.onTapDown);
|
|
6076
6307
|
this.wrapper.removeEventListener("pointerup", this.onDoubleTap);
|
|
6077
6308
|
this.wrapper.removeEventListener("dragover", this.onDragOver);
|
|
6078
6309
|
this.wrapper.removeEventListener("drop", this.onDrop);
|
|
6079
6310
|
this.inputHandler.destroy();
|
|
6080
6311
|
this.unsubCamera();
|
|
6312
|
+
this.unsubToolChange();
|
|
6081
6313
|
this.unsubStore.forEach((fn) => fn());
|
|
6082
6314
|
this.resizeObserver?.disconnect();
|
|
6083
6315
|
this.resizeObserver = null;
|
|
@@ -6905,6 +7137,15 @@ var SelectTool = class {
|
|
|
6905
7137
|
this.setSelectedIds(ids);
|
|
6906
7138
|
this.ctx?.requestRender();
|
|
6907
7139
|
}
|
|
7140
|
+
selectAtPoint(world, ctx) {
|
|
7141
|
+
const hit = this.hitTest(world, ctx);
|
|
7142
|
+
if (!hit) {
|
|
7143
|
+
this.setSelectedIds([]);
|
|
7144
|
+
return;
|
|
7145
|
+
}
|
|
7146
|
+
if (this._selectedIds.includes(hit.id)) return;
|
|
7147
|
+
this.setSelectedIds(expandToGroups([hit.id], ctx.store.getAll()));
|
|
7148
|
+
}
|
|
6908
7149
|
get isMarqueeActive() {
|
|
6909
7150
|
return this.mode.type === "marquee";
|
|
6910
7151
|
}
|
|
@@ -7408,6 +7649,7 @@ var SelectTool = class {
|
|
|
7408
7649
|
for (const id of this._selectedIds) {
|
|
7409
7650
|
const el = ctx.store.getById(id);
|
|
7410
7651
|
if (!el || !("size" in el)) continue;
|
|
7652
|
+
if (el.locked) continue;
|
|
7411
7653
|
if (el.type === "shape" && el.shape === "line") continue;
|
|
7412
7654
|
const layout = this.getOverlayLayout(el, zoom);
|
|
7413
7655
|
if (!layout) continue;
|
|
@@ -7543,62 +7785,89 @@ var SelectTool = class {
|
|
|
7543
7785
|
canvasCtx.stroke();
|
|
7544
7786
|
}
|
|
7545
7787
|
}
|
|
7546
|
-
if (
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
|
|
7788
|
+
if (!el.locked) {
|
|
7789
|
+
if ("size" in el) {
|
|
7790
|
+
canvasCtx.setLineDash([]);
|
|
7791
|
+
canvasCtx.fillStyle = "#ffffff";
|
|
7792
|
+
const corners = layout.angle === 0 ? this.getHandlePositions(bounds) : layout.corners;
|
|
7793
|
+
for (const [, pos] of corners) {
|
|
7794
|
+
canvasCtx.fillRect(
|
|
7795
|
+
pos.x - handleWorldSize / 2,
|
|
7796
|
+
pos.y - handleWorldSize / 2,
|
|
7797
|
+
handleWorldSize,
|
|
7798
|
+
handleWorldSize
|
|
7799
|
+
);
|
|
7800
|
+
canvasCtx.strokeRect(
|
|
7801
|
+
pos.x - handleWorldSize / 2,
|
|
7802
|
+
pos.y - handleWorldSize / 2,
|
|
7803
|
+
handleWorldSize,
|
|
7804
|
+
handleWorldSize
|
|
7805
|
+
);
|
|
7806
|
+
}
|
|
7807
|
+
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7808
|
+
} else if (el.type === "template") {
|
|
7809
|
+
canvasCtx.setLineDash([]);
|
|
7810
|
+
canvasCtx.fillStyle = "#ffffff";
|
|
7811
|
+
const hx = bounds.x + bounds.w;
|
|
7812
|
+
const hy = bounds.y + bounds.h;
|
|
7551
7813
|
canvasCtx.fillRect(
|
|
7552
|
-
|
|
7553
|
-
|
|
7814
|
+
hx - handleWorldSize / 2,
|
|
7815
|
+
hy - handleWorldSize / 2,
|
|
7554
7816
|
handleWorldSize,
|
|
7555
7817
|
handleWorldSize
|
|
7556
7818
|
);
|
|
7557
7819
|
canvasCtx.strokeRect(
|
|
7558
|
-
|
|
7559
|
-
|
|
7820
|
+
hx - handleWorldSize / 2,
|
|
7821
|
+
hy - handleWorldSize / 2,
|
|
7560
7822
|
handleWorldSize,
|
|
7561
7823
|
handleWorldSize
|
|
7562
7824
|
);
|
|
7825
|
+
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7826
|
+
}
|
|
7827
|
+
if (this._selectedIds.length === 1 && ROTATABLE_TYPES.has(el.type)) {
|
|
7828
|
+
const stemStart = this.topMidpoint(layout);
|
|
7829
|
+
const stemEnd = layout.rotateHandle;
|
|
7830
|
+
canvasCtx.beginPath();
|
|
7831
|
+
canvasCtx.moveTo(stemStart.x, stemStart.y);
|
|
7832
|
+
canvasCtx.lineTo(stemEnd.x, stemEnd.y);
|
|
7833
|
+
canvasCtx.stroke();
|
|
7834
|
+
canvasCtx.setLineDash([]);
|
|
7835
|
+
canvasCtx.fillStyle = "#ffffff";
|
|
7836
|
+
canvasCtx.beginPath();
|
|
7837
|
+
canvasCtx.arc(stemEnd.x, stemEnd.y, handleWorldSize / 2, 0, Math.PI * 2);
|
|
7838
|
+
canvasCtx.fill();
|
|
7839
|
+
canvasCtx.stroke();
|
|
7840
|
+
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7563
7841
|
}
|
|
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
7842
|
}
|
|
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]);
|
|
7843
|
+
if (el.locked) {
|
|
7844
|
+
const ne = layout.corners.find(([h]) => h === "ne")?.[1];
|
|
7845
|
+
if (ne) this.drawLockBadge(canvasCtx, ne, zoom);
|
|
7598
7846
|
}
|
|
7599
7847
|
}
|
|
7600
7848
|
canvasCtx.restore();
|
|
7601
7849
|
}
|
|
7850
|
+
drawLockBadge(ctx, at, zoom) {
|
|
7851
|
+
const r = 9 / zoom;
|
|
7852
|
+
ctx.save();
|
|
7853
|
+
ctx.setLineDash([]);
|
|
7854
|
+
ctx.beginPath();
|
|
7855
|
+
ctx.arc(at.x, at.y, r, 0, Math.PI * 2);
|
|
7856
|
+
ctx.fillStyle = "#ffffff";
|
|
7857
|
+
ctx.fill();
|
|
7858
|
+
ctx.strokeStyle = "#2196F3";
|
|
7859
|
+
ctx.lineWidth = 1.5 / zoom;
|
|
7860
|
+
ctx.stroke();
|
|
7861
|
+
const bw = 8 / zoom;
|
|
7862
|
+
const bh = 6 / zoom;
|
|
7863
|
+
ctx.fillStyle = "#2196F3";
|
|
7864
|
+
ctx.fillRect(at.x - bw / 2, at.y - bh / 2 + 1 / zoom, bw, bh);
|
|
7865
|
+
ctx.beginPath();
|
|
7866
|
+
ctx.arc(at.x, at.y - bh / 2 + 1 / zoom, 2.5 / zoom, Math.PI, 0);
|
|
7867
|
+
ctx.lineWidth = 1.4 / zoom;
|
|
7868
|
+
ctx.stroke();
|
|
7869
|
+
ctx.restore();
|
|
7870
|
+
}
|
|
7602
7871
|
renderBindingHighlights(canvasCtx, arrow, zoom) {
|
|
7603
7872
|
if (!this.ctx) return;
|
|
7604
7873
|
if (!arrow.fromBinding && !arrow.toBinding) return;
|
|
@@ -8571,7 +8840,7 @@ var TemplateTool = class {
|
|
|
8571
8840
|
};
|
|
8572
8841
|
|
|
8573
8842
|
// src/index.ts
|
|
8574
|
-
var VERSION = "0.
|
|
8843
|
+
var VERSION = "0.37.0";
|
|
8575
8844
|
export {
|
|
8576
8845
|
ArrowTool,
|
|
8577
8846
|
AutoSave,
|