@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.cjs
CHANGED
|
@@ -991,6 +991,14 @@ var KeyboardActions = class {
|
|
|
991
991
|
}
|
|
992
992
|
this.pasteCount = 0;
|
|
993
993
|
}
|
|
994
|
+
cut() {
|
|
995
|
+
if (this.deps.isToolActive()) return;
|
|
996
|
+
this.copy();
|
|
997
|
+
this.deleteSelected();
|
|
998
|
+
}
|
|
999
|
+
hasClipboard() {
|
|
1000
|
+
return this.clipboard.length > 0;
|
|
1001
|
+
}
|
|
994
1002
|
paste() {
|
|
995
1003
|
if (this.deps.isToolActive()) return;
|
|
996
1004
|
this.flushPendingNudge();
|
|
@@ -1059,6 +1067,10 @@ var KeyboardActions = class {
|
|
|
1059
1067
|
if (this.deps.isToolActive()) return;
|
|
1060
1068
|
this.deps.ungroup?.();
|
|
1061
1069
|
}
|
|
1070
|
+
toggleLock() {
|
|
1071
|
+
if (this.deps.isToolActive()) return;
|
|
1072
|
+
this.deps.toggleLock?.();
|
|
1073
|
+
}
|
|
1062
1074
|
zOrder(operation) {
|
|
1063
1075
|
if (this.deps.isToolActive()) return;
|
|
1064
1076
|
this.flushPendingNudge();
|
|
@@ -1160,6 +1172,8 @@ var DEFAULT_BINDINGS = [
|
|
|
1160
1172
|
["zoom-reset", ["mod+0"]],
|
|
1161
1173
|
["group", ["mod+g"]],
|
|
1162
1174
|
["ungroup", ["mod+shift+g"]],
|
|
1175
|
+
["cut", ["mod+x"]],
|
|
1176
|
+
["toggle-lock", ["mod+shift+l"]],
|
|
1163
1177
|
["nudge-left", ["arrowleft"]],
|
|
1164
1178
|
["nudge-right", ["arrowright"]],
|
|
1165
1179
|
["nudge-up", ["arrowup"]],
|
|
@@ -1298,6 +1312,7 @@ var ShortcutMap = class {
|
|
|
1298
1312
|
var ZOOM_SENSITIVITY = 1e-3;
|
|
1299
1313
|
var ZOOM_STEP = 1.2;
|
|
1300
1314
|
var MIDDLE_BUTTON = 1;
|
|
1315
|
+
var LONG_PRESS_MS = 500;
|
|
1301
1316
|
var NUDGE_DELTAS = {
|
|
1302
1317
|
"nudge-left": [-1, 0],
|
|
1303
1318
|
"nudge-right": [1, 0],
|
|
@@ -1321,8 +1336,10 @@ var InputHandler = class {
|
|
|
1321
1336
|
fitToContent: options.fitToContent,
|
|
1322
1337
|
group: options.group,
|
|
1323
1338
|
ungroup: options.ungroup,
|
|
1339
|
+
toggleLock: options.toggleLock,
|
|
1324
1340
|
getLastPointerWorld: () => this.lastPointerWorld()
|
|
1325
1341
|
});
|
|
1342
|
+
this.openContextMenu = options.openContextMenu;
|
|
1326
1343
|
this.shortcutMap = new ShortcutMap(options.shortcuts?.bindings);
|
|
1327
1344
|
this.scope = options.shortcuts?.scope ?? "focus";
|
|
1328
1345
|
this.element.style.touchAction = "none";
|
|
@@ -1346,10 +1363,13 @@ var InputHandler = class {
|
|
|
1346
1363
|
lastPointerEvent = null;
|
|
1347
1364
|
inputFilter = new InputFilter();
|
|
1348
1365
|
deferredDown = null;
|
|
1366
|
+
longPressTimer = null;
|
|
1367
|
+
longPressStart = null;
|
|
1349
1368
|
abortController = new AbortController();
|
|
1350
1369
|
actions;
|
|
1351
1370
|
shortcutMap;
|
|
1352
1371
|
scope;
|
|
1372
|
+
openContextMenu;
|
|
1353
1373
|
setToolManager(toolManager, toolContext) {
|
|
1354
1374
|
this.toolManager = toolManager;
|
|
1355
1375
|
this.toolContext = toolContext;
|
|
@@ -1364,6 +1384,7 @@ var InputHandler = class {
|
|
|
1364
1384
|
this.actions.dispose();
|
|
1365
1385
|
this.abortController.abort();
|
|
1366
1386
|
this.inputFilter.reset();
|
|
1387
|
+
this.cancelLongPress();
|
|
1367
1388
|
this.deferredDown = null;
|
|
1368
1389
|
this.lastPointerEvent = null;
|
|
1369
1390
|
if (this.scope === "focus") {
|
|
@@ -1379,6 +1400,7 @@ var InputHandler = class {
|
|
|
1379
1400
|
this.element.addEventListener("pointerup", this.onPointerUp, opts);
|
|
1380
1401
|
this.element.addEventListener("pointerleave", this.onPointerLeave, opts);
|
|
1381
1402
|
this.element.addEventListener("pointercancel", this.onPointerUp, opts);
|
|
1403
|
+
this.element.addEventListener("contextmenu", this.onContextMenu, opts);
|
|
1382
1404
|
window.addEventListener("keydown", this.onKeyDown, opts);
|
|
1383
1405
|
window.addEventListener("keyup", this.onKeyUp, opts);
|
|
1384
1406
|
}
|
|
@@ -1407,11 +1429,13 @@ var InputHandler = class {
|
|
|
1407
1429
|
this.activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
|
|
1408
1430
|
this.element.setPointerCapture?.(e.pointerId);
|
|
1409
1431
|
if (this.activePointers.size === 2) {
|
|
1432
|
+
this.cancelLongPress();
|
|
1410
1433
|
this.startPinch();
|
|
1411
1434
|
this.cancelToolIfActive(e);
|
|
1412
1435
|
return;
|
|
1413
1436
|
}
|
|
1414
1437
|
if (e.button === MIDDLE_BUTTON || e.button === 0 && this.spaceHeld) {
|
|
1438
|
+
this.cancelLongPress();
|
|
1415
1439
|
this.isPanning = true;
|
|
1416
1440
|
this.lastPointer = { x: e.clientX, y: e.clientY };
|
|
1417
1441
|
return;
|
|
@@ -1421,6 +1445,7 @@ var InputHandler = class {
|
|
|
1421
1445
|
if (result.action === "suppress") return;
|
|
1422
1446
|
if (result.action === "defer") {
|
|
1423
1447
|
this.deferredDown = e;
|
|
1448
|
+
this.startLongPress(e);
|
|
1424
1449
|
return;
|
|
1425
1450
|
}
|
|
1426
1451
|
this.dispatchToolDown(e);
|
|
@@ -1449,6 +1474,7 @@ var InputHandler = class {
|
|
|
1449
1474
|
} else if (this.deferredDown) {
|
|
1450
1475
|
const result = this.inputFilter.filterMove(e);
|
|
1451
1476
|
if (result.action === "dispatch") {
|
|
1477
|
+
this.cancelLongPress();
|
|
1452
1478
|
this.dispatchToolDown(this.deferredDown);
|
|
1453
1479
|
this.deferredDown = null;
|
|
1454
1480
|
this.dispatchToolMove(e);
|
|
@@ -1458,6 +1484,7 @@ var InputHandler = class {
|
|
|
1458
1484
|
}
|
|
1459
1485
|
};
|
|
1460
1486
|
onPointerUp = (e) => {
|
|
1487
|
+
this.cancelLongPress();
|
|
1461
1488
|
try {
|
|
1462
1489
|
this.element.releasePointerCapture(e.pointerId);
|
|
1463
1490
|
} catch {
|
|
@@ -1510,74 +1537,82 @@ var InputHandler = class {
|
|
|
1510
1537
|
runAction(action, e) {
|
|
1511
1538
|
switch (action) {
|
|
1512
1539
|
case "delete":
|
|
1513
|
-
e
|
|
1540
|
+
e?.preventDefault();
|
|
1514
1541
|
this.actions.deleteSelected();
|
|
1515
1542
|
return;
|
|
1516
1543
|
case "deselect":
|
|
1517
1544
|
this.actions.deselect();
|
|
1518
1545
|
return;
|
|
1519
1546
|
case "undo":
|
|
1520
|
-
e
|
|
1547
|
+
e?.preventDefault();
|
|
1521
1548
|
this.actions.undo();
|
|
1522
1549
|
return;
|
|
1523
1550
|
case "redo":
|
|
1524
|
-
e
|
|
1551
|
+
e?.preventDefault();
|
|
1525
1552
|
this.actions.redo();
|
|
1526
1553
|
return;
|
|
1527
1554
|
case "select-all":
|
|
1528
|
-
e
|
|
1555
|
+
e?.preventDefault();
|
|
1529
1556
|
this.actions.selectAll();
|
|
1530
1557
|
return;
|
|
1531
1558
|
case "copy":
|
|
1532
|
-
e
|
|
1559
|
+
e?.preventDefault();
|
|
1533
1560
|
this.actions.copy();
|
|
1534
1561
|
return;
|
|
1535
1562
|
case "paste":
|
|
1536
|
-
e
|
|
1563
|
+
e?.preventDefault();
|
|
1537
1564
|
this.actions.paste();
|
|
1538
1565
|
return;
|
|
1539
1566
|
case "duplicate":
|
|
1540
|
-
e
|
|
1567
|
+
e?.preventDefault();
|
|
1541
1568
|
this.actions.duplicate();
|
|
1542
1569
|
return;
|
|
1543
1570
|
case "z-forward":
|
|
1544
|
-
e
|
|
1571
|
+
e?.preventDefault();
|
|
1545
1572
|
this.actions.zOrder("forward");
|
|
1546
1573
|
return;
|
|
1547
1574
|
case "z-backward":
|
|
1548
|
-
e
|
|
1575
|
+
e?.preventDefault();
|
|
1549
1576
|
this.actions.zOrder("backward");
|
|
1550
1577
|
return;
|
|
1551
1578
|
case "z-front":
|
|
1552
|
-
e
|
|
1579
|
+
e?.preventDefault();
|
|
1553
1580
|
this.actions.zOrder("front");
|
|
1554
1581
|
return;
|
|
1555
1582
|
case "z-back":
|
|
1556
|
-
e
|
|
1583
|
+
e?.preventDefault();
|
|
1557
1584
|
this.actions.zOrder("back");
|
|
1558
1585
|
return;
|
|
1559
1586
|
case "zoom-fit":
|
|
1560
|
-
e
|
|
1587
|
+
e?.preventDefault();
|
|
1561
1588
|
this.actions.zoomToFit();
|
|
1562
1589
|
return;
|
|
1563
1590
|
case "group":
|
|
1564
|
-
e
|
|
1591
|
+
e?.preventDefault();
|
|
1565
1592
|
this.actions.group();
|
|
1566
1593
|
return;
|
|
1567
1594
|
case "ungroup":
|
|
1568
|
-
e
|
|
1595
|
+
e?.preventDefault();
|
|
1569
1596
|
this.actions.ungroup();
|
|
1570
1597
|
return;
|
|
1598
|
+
case "cut":
|
|
1599
|
+
e?.preventDefault();
|
|
1600
|
+
this.actions.cut();
|
|
1601
|
+
return;
|
|
1602
|
+
case "toggle-lock":
|
|
1603
|
+
e?.preventDefault();
|
|
1604
|
+
this.actions.toggleLock();
|
|
1605
|
+
return;
|
|
1571
1606
|
case "zoom-in":
|
|
1572
|
-
e
|
|
1607
|
+
e?.preventDefault();
|
|
1573
1608
|
this.zoomByFactor(ZOOM_STEP);
|
|
1574
1609
|
return;
|
|
1575
1610
|
case "zoom-out":
|
|
1576
|
-
e
|
|
1611
|
+
e?.preventDefault();
|
|
1577
1612
|
this.zoomByFactor(1 / ZOOM_STEP);
|
|
1578
1613
|
return;
|
|
1579
1614
|
case "zoom-reset":
|
|
1580
|
-
e
|
|
1615
|
+
e?.preventDefault();
|
|
1581
1616
|
this.zoomToLevel(1);
|
|
1582
1617
|
return;
|
|
1583
1618
|
case "nudge-left":
|
|
@@ -1585,22 +1620,26 @@ var InputHandler = class {
|
|
|
1585
1620
|
case "nudge-up":
|
|
1586
1621
|
case "nudge-down": {
|
|
1587
1622
|
const delta = NUDGE_DELTAS[action];
|
|
1588
|
-
if (delta && this.actions.nudge(delta[0], delta[1], e
|
|
1589
|
-
e
|
|
1623
|
+
if (delta && this.actions.nudge(delta[0], delta[1], e?.shiftKey ?? false)) {
|
|
1624
|
+
e?.preventDefault();
|
|
1590
1625
|
}
|
|
1591
1626
|
return;
|
|
1592
1627
|
}
|
|
1593
1628
|
default:
|
|
1594
1629
|
if (action.startsWith("tool:")) {
|
|
1595
1630
|
if (this.isToolActive) return;
|
|
1596
|
-
e
|
|
1631
|
+
e?.preventDefault();
|
|
1597
1632
|
this.toolContext?.switchTool?.(action.slice("tool:".length));
|
|
1598
1633
|
return;
|
|
1599
1634
|
}
|
|
1600
1635
|
console.warn(`[fieldnotes] unknown shortcut action "${action}"`);
|
|
1601
1636
|
}
|
|
1602
1637
|
}
|
|
1638
|
+
hasClipboard() {
|
|
1639
|
+
return this.actions.hasClipboard();
|
|
1640
|
+
}
|
|
1603
1641
|
startPinch() {
|
|
1642
|
+
this.cancelLongPress();
|
|
1604
1643
|
this.inputFilter.reset();
|
|
1605
1644
|
this.deferredDown = null;
|
|
1606
1645
|
this.isPanning = true;
|
|
@@ -1643,6 +1682,13 @@ var InputHandler = class {
|
|
|
1643
1682
|
const rect = this.element.getBoundingClientRect();
|
|
1644
1683
|
return this.camera.screenToWorld({ x: e.clientX - rect.left, y: e.clientY - rect.top });
|
|
1645
1684
|
}
|
|
1685
|
+
onContextMenu = (e) => {
|
|
1686
|
+
e.preventDefault();
|
|
1687
|
+
if (this.toolManager?.activeTool?.name !== "select") return;
|
|
1688
|
+
const rect = this.element.getBoundingClientRect();
|
|
1689
|
+
const world = this.camera.screenToWorld({ x: e.clientX - rect.left, y: e.clientY - rect.top });
|
|
1690
|
+
this.openContextMenu?.({ x: e.clientX, y: e.clientY }, world);
|
|
1691
|
+
};
|
|
1646
1692
|
onPointerLeave = (e) => {
|
|
1647
1693
|
this.lastPointerEvent = null;
|
|
1648
1694
|
this.onPointerUp(e);
|
|
@@ -1690,12 +1736,36 @@ var InputHandler = class {
|
|
|
1690
1736
|
this.element.focus({ preventScroll: true });
|
|
1691
1737
|
}
|
|
1692
1738
|
cancelToolIfActive(e) {
|
|
1739
|
+
this.cancelLongPress();
|
|
1693
1740
|
if (this.isToolActive) {
|
|
1694
1741
|
this.dispatchToolUp(e);
|
|
1695
1742
|
this.isToolActive = false;
|
|
1696
1743
|
}
|
|
1697
1744
|
this.deferredDown = null;
|
|
1698
1745
|
}
|
|
1746
|
+
startLongPress(e) {
|
|
1747
|
+
if (e.pointerType !== "touch") return;
|
|
1748
|
+
if (this.toolManager?.activeTool?.name !== "select") return;
|
|
1749
|
+
this.longPressStart = { x: e.clientX, y: e.clientY };
|
|
1750
|
+
this.longPressTimer = setTimeout(() => this.fireLongPress(), LONG_PRESS_MS);
|
|
1751
|
+
}
|
|
1752
|
+
cancelLongPress() {
|
|
1753
|
+
if (this.longPressTimer !== null) {
|
|
1754
|
+
clearTimeout(this.longPressTimer);
|
|
1755
|
+
this.longPressTimer = null;
|
|
1756
|
+
}
|
|
1757
|
+
this.longPressStart = null;
|
|
1758
|
+
}
|
|
1759
|
+
fireLongPress() {
|
|
1760
|
+
this.longPressTimer = null;
|
|
1761
|
+
if (!this.deferredDown || this.activePointers.size !== 1 || this.isPanning) return;
|
|
1762
|
+
const start = this.longPressStart;
|
|
1763
|
+
if (!start) return;
|
|
1764
|
+
const rect = this.element.getBoundingClientRect();
|
|
1765
|
+
const world = this.camera.screenToWorld({ x: start.x - rect.left, y: start.y - rect.top });
|
|
1766
|
+
this.deferredDown = null;
|
|
1767
|
+
this.openContextMenu?.({ x: start.x, y: start.y }, world);
|
|
1768
|
+
}
|
|
1699
1769
|
};
|
|
1700
1770
|
|
|
1701
1771
|
// src/canvas/background.ts
|
|
@@ -2013,9 +2083,10 @@ var Quadtree = class {
|
|
|
2013
2083
|
};
|
|
2014
2084
|
|
|
2015
2085
|
// src/elements/stroke-smoothing.ts
|
|
2016
|
-
var MIN_PRESSURE_SCALE = 0.
|
|
2086
|
+
var MIN_PRESSURE_SCALE = 0.4;
|
|
2087
|
+
var MAX_PRESSURE_SCALE = 1.8;
|
|
2017
2088
|
function pressureToWidth(pressure, baseWidth) {
|
|
2018
|
-
return baseWidth * (MIN_PRESSURE_SCALE + (
|
|
2089
|
+
return baseWidth * (MIN_PRESSURE_SCALE + (MAX_PRESSURE_SCALE - MIN_PRESSURE_SCALE) * pressure);
|
|
2019
2090
|
}
|
|
2020
2091
|
function simplifyPoints(points, tolerance) {
|
|
2021
2092
|
if (points.length <= 2) return points.slice();
|
|
@@ -3678,12 +3749,33 @@ var NoteToolbar = class {
|
|
|
3678
3749
|
e.stopPropagation();
|
|
3679
3750
|
});
|
|
3680
3751
|
select.addEventListener("change", () => {
|
|
3681
|
-
|
|
3752
|
+
this.applyFontSize(Number(select.value));
|
|
3682
3753
|
this.updateActiveStates();
|
|
3683
3754
|
this.anchor?.focus();
|
|
3684
3755
|
});
|
|
3685
3756
|
return select;
|
|
3686
3757
|
}
|
|
3758
|
+
applyFontSize(size) {
|
|
3759
|
+
const sel = window.getSelection();
|
|
3760
|
+
const collapsed = !sel || sel.rangeCount === 0 || sel.getRangeAt(0).collapsed;
|
|
3761
|
+
if (collapsed && this.anchor) {
|
|
3762
|
+
const range = document.createRange();
|
|
3763
|
+
range.selectNodeContents(this.anchor);
|
|
3764
|
+
sel?.removeAllRanges();
|
|
3765
|
+
sel?.addRange(range);
|
|
3766
|
+
setFontSize(size);
|
|
3767
|
+
const after = window.getSelection();
|
|
3768
|
+
if (after) {
|
|
3769
|
+
const caret = document.createRange();
|
|
3770
|
+
caret.selectNodeContents(this.anchor);
|
|
3771
|
+
caret.collapse(false);
|
|
3772
|
+
after.removeAllRanges();
|
|
3773
|
+
after.addRange(caret);
|
|
3774
|
+
}
|
|
3775
|
+
return;
|
|
3776
|
+
}
|
|
3777
|
+
setFontSize(size);
|
|
3778
|
+
}
|
|
3687
3779
|
positionToolbar(anchor) {
|
|
3688
3780
|
if (!this.el) return;
|
|
3689
3781
|
const rect = anchor.getBoundingClientRect();
|
|
@@ -3882,6 +3974,87 @@ var NoteEditor = class {
|
|
|
3882
3974
|
}
|
|
3883
3975
|
};
|
|
3884
3976
|
|
|
3977
|
+
// src/canvas/context-menu.ts
|
|
3978
|
+
var ContextMenu = class {
|
|
3979
|
+
constructor(options) {
|
|
3980
|
+
this.options = options;
|
|
3981
|
+
}
|
|
3982
|
+
el = null;
|
|
3983
|
+
outsideListener = null;
|
|
3984
|
+
keyListener = null;
|
|
3985
|
+
isOpen() {
|
|
3986
|
+
return this.el !== null;
|
|
3987
|
+
}
|
|
3988
|
+
open(items, screenPos) {
|
|
3989
|
+
this.close();
|
|
3990
|
+
const el = document.createElement("div");
|
|
3991
|
+
el.className = "fieldnotes-context-menu";
|
|
3992
|
+
Object.assign(el.style, {
|
|
3993
|
+
position: "fixed",
|
|
3994
|
+
left: `${screenPos.x}px`,
|
|
3995
|
+
top: `${screenPos.y}px`,
|
|
3996
|
+
zIndex: "10000",
|
|
3997
|
+
display: "flex",
|
|
3998
|
+
flexDirection: "column"
|
|
3999
|
+
});
|
|
4000
|
+
for (const item of items) {
|
|
4001
|
+
const btn = document.createElement("button");
|
|
4002
|
+
btn.type = "button";
|
|
4003
|
+
btn.className = "fieldnotes-context-menu-item" + (item.disabled ? " fieldnotes-context-menu-item--disabled" : "");
|
|
4004
|
+
btn.textContent = item.label;
|
|
4005
|
+
if (item.disabled) {
|
|
4006
|
+
btn.disabled = true;
|
|
4007
|
+
} else {
|
|
4008
|
+
btn.addEventListener("click", () => {
|
|
4009
|
+
this.options.onCommand(item.action);
|
|
4010
|
+
this.close();
|
|
4011
|
+
});
|
|
4012
|
+
}
|
|
4013
|
+
el.appendChild(btn);
|
|
4014
|
+
}
|
|
4015
|
+
document.body.appendChild(el);
|
|
4016
|
+
this.el = el;
|
|
4017
|
+
this.clampToViewport(el, screenPos);
|
|
4018
|
+
this.keyListener = (e) => {
|
|
4019
|
+
if (e.key === "Escape") this.close();
|
|
4020
|
+
};
|
|
4021
|
+
document.addEventListener("keydown", this.keyListener);
|
|
4022
|
+
this.outsideListener = (e) => {
|
|
4023
|
+
if (this.el && !this.el.contains(e.target)) this.close();
|
|
4024
|
+
};
|
|
4025
|
+
setTimeout(() => {
|
|
4026
|
+
if (this.outsideListener) document.addEventListener("pointerdown", this.outsideListener);
|
|
4027
|
+
}, 0);
|
|
4028
|
+
}
|
|
4029
|
+
close() {
|
|
4030
|
+
if (this.keyListener) {
|
|
4031
|
+
document.removeEventListener("keydown", this.keyListener);
|
|
4032
|
+
this.keyListener = null;
|
|
4033
|
+
}
|
|
4034
|
+
if (this.outsideListener) {
|
|
4035
|
+
document.removeEventListener("pointerdown", this.outsideListener);
|
|
4036
|
+
this.outsideListener = null;
|
|
4037
|
+
}
|
|
4038
|
+
if (this.el) {
|
|
4039
|
+
this.el.remove();
|
|
4040
|
+
this.el = null;
|
|
4041
|
+
this.options.onClose();
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
dispose() {
|
|
4045
|
+
this.close();
|
|
4046
|
+
}
|
|
4047
|
+
clampToViewport(el, screenPos) {
|
|
4048
|
+
const rect = el.getBoundingClientRect();
|
|
4049
|
+
if (rect.width > 0 && screenPos.x + rect.width > window.innerWidth) {
|
|
4050
|
+
el.style.left = `${Math.max(0, screenPos.x - rect.width)}px`;
|
|
4051
|
+
}
|
|
4052
|
+
if (rect.height > 0 && screenPos.y + rect.height > window.innerHeight) {
|
|
4053
|
+
el.style.top = `${Math.max(0, screenPos.y - rect.height)}px`;
|
|
4054
|
+
}
|
|
4055
|
+
}
|
|
4056
|
+
};
|
|
4057
|
+
|
|
3885
4058
|
// src/elements/translate.ts
|
|
3886
4059
|
function translateElementPatch(el, dx, dy) {
|
|
3887
4060
|
const position = { x: el.position.x + dx, y: el.position.y + dy };
|
|
@@ -5698,8 +5871,20 @@ var Viewport = class {
|
|
|
5698
5871
|
fitToContent: () => this.fitToContent(),
|
|
5699
5872
|
group: () => this.groupSelection(),
|
|
5700
5873
|
ungroup: () => this.ungroupSelection(),
|
|
5874
|
+
toggleLock: () => this.toggleLockSelection(),
|
|
5875
|
+
openContextMenu: (screenPos, world) => {
|
|
5876
|
+
this.getSelectTool()?.selectAtPoint(world, this.toolContext);
|
|
5877
|
+
this.openContextMenu(screenPos);
|
|
5878
|
+
},
|
|
5701
5879
|
shortcuts: options.shortcuts
|
|
5702
5880
|
});
|
|
5881
|
+
if (options.contextMenu !== false) {
|
|
5882
|
+
this.contextMenu = new ContextMenu({
|
|
5883
|
+
onCommand: (action) => this.runAction(action),
|
|
5884
|
+
onClose: noop
|
|
5885
|
+
});
|
|
5886
|
+
}
|
|
5887
|
+
this.unsubToolChange = this.toolManager.onChange(() => this.contextMenu?.close());
|
|
5703
5888
|
this.domNodeManager = new DomNodeManager({
|
|
5704
5889
|
domLayer: this.domLayer,
|
|
5705
5890
|
onEditRequest: (id) => this.startEditingElement(id),
|
|
@@ -5731,6 +5916,7 @@ var Viewport = class {
|
|
|
5731
5916
|
this.unsubCamera = this.camera.onChange(() => {
|
|
5732
5917
|
this.applyCameraTransform();
|
|
5733
5918
|
this.noteEditor.updateToolbarPosition();
|
|
5919
|
+
this.contextMenu?.close();
|
|
5734
5920
|
this.requestRender();
|
|
5735
5921
|
});
|
|
5736
5922
|
this.unsubStore = [
|
|
@@ -5783,6 +5969,7 @@ var Viewport = class {
|
|
|
5783
5969
|
canvasEl;
|
|
5784
5970
|
wrapper;
|
|
5785
5971
|
unsubCamera;
|
|
5972
|
+
unsubToolChange;
|
|
5786
5973
|
unsubStore;
|
|
5787
5974
|
inputHandler;
|
|
5788
5975
|
background;
|
|
@@ -5805,6 +5992,7 @@ var Viewport = class {
|
|
|
5805
5992
|
doubleTapDetector = new DoubleTapDetector();
|
|
5806
5993
|
tapDownX = 0;
|
|
5807
5994
|
tapDownY = 0;
|
|
5995
|
+
contextMenu = null;
|
|
5808
5996
|
get ctx() {
|
|
5809
5997
|
return this.canvasEl.getContext("2d");
|
|
5810
5998
|
}
|
|
@@ -5995,6 +6183,34 @@ var Viewport = class {
|
|
|
5995
6183
|
getSelectedIds() {
|
|
5996
6184
|
return this.getSelectTool()?.selectedIds ?? EMPTY_IDS;
|
|
5997
6185
|
}
|
|
6186
|
+
runAction(action) {
|
|
6187
|
+
this.inputHandler.runAction(action);
|
|
6188
|
+
}
|
|
6189
|
+
canPaste() {
|
|
6190
|
+
return this.inputHandler.hasClipboard();
|
|
6191
|
+
}
|
|
6192
|
+
openContextMenu(screenPos) {
|
|
6193
|
+
if (!this.contextMenu) return;
|
|
6194
|
+
const ids = this.getSelectedIds();
|
|
6195
|
+
const items = [];
|
|
6196
|
+
if (ids.length > 0) {
|
|
6197
|
+
items.push({ label: "Cut", action: "cut" });
|
|
6198
|
+
items.push({ label: "Copy", action: "copy" });
|
|
6199
|
+
if (this.canPaste()) items.push({ label: "Paste", action: "paste" });
|
|
6200
|
+
items.push({ label: "Duplicate", action: "duplicate" });
|
|
6201
|
+
items.push({ label: "Delete", action: "delete" });
|
|
6202
|
+
items.push({ label: "Bring to Front", action: "z-front" });
|
|
6203
|
+
items.push({ label: "Bring Forward", action: "z-forward" });
|
|
6204
|
+
items.push({ label: "Send Backward", action: "z-backward" });
|
|
6205
|
+
items.push({ label: "Send to Back", action: "z-back" });
|
|
6206
|
+
const allLocked = ids.every((id) => this.store.getById(id)?.locked);
|
|
6207
|
+
items.push({ label: allLocked ? "Unlock" : "Lock", action: "toggle-lock" });
|
|
6208
|
+
} else if (this.canPaste()) {
|
|
6209
|
+
items.push({ label: "Paste", action: "paste" });
|
|
6210
|
+
}
|
|
6211
|
+
if (items.length === 0) return;
|
|
6212
|
+
this.contextMenu.open(items, screenPos);
|
|
6213
|
+
}
|
|
5998
6214
|
onSelectionChange(listener) {
|
|
5999
6215
|
const tool = this.getSelectTool();
|
|
6000
6216
|
return tool ? tool.onSelectionChange(listener) : noop;
|
|
@@ -6055,6 +6271,20 @@ var Viewport = class {
|
|
|
6055
6271
|
}
|
|
6056
6272
|
this.historyRecorder.commit();
|
|
6057
6273
|
}
|
|
6274
|
+
toggleLockSelection() {
|
|
6275
|
+
const ids = this.getSelectedIds();
|
|
6276
|
+
if (ids.length === 0) return;
|
|
6277
|
+
const anyUnlocked = ids.some((id) => {
|
|
6278
|
+
const el = this.store.getById(id);
|
|
6279
|
+
return el ? !el.locked : false;
|
|
6280
|
+
});
|
|
6281
|
+
this.historyRecorder.begin();
|
|
6282
|
+
for (const id of ids) {
|
|
6283
|
+
const el = this.store.getById(id);
|
|
6284
|
+
if (el && el.locked !== anyUnlocked) this.store.update(id, { locked: anyUnlocked });
|
|
6285
|
+
}
|
|
6286
|
+
this.historyRecorder.commit();
|
|
6287
|
+
}
|
|
6058
6288
|
alignSelection(edge) {
|
|
6059
6289
|
const bounded = this.boundedSelection();
|
|
6060
6290
|
if (bounded.length < 2) return;
|
|
@@ -6153,12 +6383,14 @@ var Viewport = class {
|
|
|
6153
6383
|
this.noteEditor.destroy(this.store);
|
|
6154
6384
|
this.arrowLabelEditor.cancel();
|
|
6155
6385
|
this.historyRecorder.destroy();
|
|
6386
|
+
this.contextMenu?.dispose();
|
|
6156
6387
|
this.wrapper.removeEventListener("pointerdown", this.onTapDown);
|
|
6157
6388
|
this.wrapper.removeEventListener("pointerup", this.onDoubleTap);
|
|
6158
6389
|
this.wrapper.removeEventListener("dragover", this.onDragOver);
|
|
6159
6390
|
this.wrapper.removeEventListener("drop", this.onDrop);
|
|
6160
6391
|
this.inputHandler.destroy();
|
|
6161
6392
|
this.unsubCamera();
|
|
6393
|
+
this.unsubToolChange();
|
|
6162
6394
|
this.unsubStore.forEach((fn) => fn());
|
|
6163
6395
|
this.resizeObserver?.disconnect();
|
|
6164
6396
|
this.resizeObserver = null;
|
|
@@ -6986,6 +7218,15 @@ var SelectTool = class {
|
|
|
6986
7218
|
this.setSelectedIds(ids);
|
|
6987
7219
|
this.ctx?.requestRender();
|
|
6988
7220
|
}
|
|
7221
|
+
selectAtPoint(world, ctx) {
|
|
7222
|
+
const hit = this.hitTest(world, ctx);
|
|
7223
|
+
if (!hit) {
|
|
7224
|
+
this.setSelectedIds([]);
|
|
7225
|
+
return;
|
|
7226
|
+
}
|
|
7227
|
+
if (this._selectedIds.includes(hit.id)) return;
|
|
7228
|
+
this.setSelectedIds(expandToGroups([hit.id], ctx.store.getAll()));
|
|
7229
|
+
}
|
|
6989
7230
|
get isMarqueeActive() {
|
|
6990
7231
|
return this.mode.type === "marquee";
|
|
6991
7232
|
}
|
|
@@ -7489,6 +7730,7 @@ var SelectTool = class {
|
|
|
7489
7730
|
for (const id of this._selectedIds) {
|
|
7490
7731
|
const el = ctx.store.getById(id);
|
|
7491
7732
|
if (!el || !("size" in el)) continue;
|
|
7733
|
+
if (el.locked) continue;
|
|
7492
7734
|
if (el.type === "shape" && el.shape === "line") continue;
|
|
7493
7735
|
const layout = this.getOverlayLayout(el, zoom);
|
|
7494
7736
|
if (!layout) continue;
|
|
@@ -7624,62 +7866,89 @@ var SelectTool = class {
|
|
|
7624
7866
|
canvasCtx.stroke();
|
|
7625
7867
|
}
|
|
7626
7868
|
}
|
|
7627
|
-
if (
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
|
|
7869
|
+
if (!el.locked) {
|
|
7870
|
+
if ("size" in el) {
|
|
7871
|
+
canvasCtx.setLineDash([]);
|
|
7872
|
+
canvasCtx.fillStyle = "#ffffff";
|
|
7873
|
+
const corners = layout.angle === 0 ? this.getHandlePositions(bounds) : layout.corners;
|
|
7874
|
+
for (const [, pos] of corners) {
|
|
7875
|
+
canvasCtx.fillRect(
|
|
7876
|
+
pos.x - handleWorldSize / 2,
|
|
7877
|
+
pos.y - handleWorldSize / 2,
|
|
7878
|
+
handleWorldSize,
|
|
7879
|
+
handleWorldSize
|
|
7880
|
+
);
|
|
7881
|
+
canvasCtx.strokeRect(
|
|
7882
|
+
pos.x - handleWorldSize / 2,
|
|
7883
|
+
pos.y - handleWorldSize / 2,
|
|
7884
|
+
handleWorldSize,
|
|
7885
|
+
handleWorldSize
|
|
7886
|
+
);
|
|
7887
|
+
}
|
|
7888
|
+
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7889
|
+
} else if (el.type === "template") {
|
|
7890
|
+
canvasCtx.setLineDash([]);
|
|
7891
|
+
canvasCtx.fillStyle = "#ffffff";
|
|
7892
|
+
const hx = bounds.x + bounds.w;
|
|
7893
|
+
const hy = bounds.y + bounds.h;
|
|
7632
7894
|
canvasCtx.fillRect(
|
|
7633
|
-
|
|
7634
|
-
|
|
7895
|
+
hx - handleWorldSize / 2,
|
|
7896
|
+
hy - handleWorldSize / 2,
|
|
7635
7897
|
handleWorldSize,
|
|
7636
7898
|
handleWorldSize
|
|
7637
7899
|
);
|
|
7638
7900
|
canvasCtx.strokeRect(
|
|
7639
|
-
|
|
7640
|
-
|
|
7901
|
+
hx - handleWorldSize / 2,
|
|
7902
|
+
hy - handleWorldSize / 2,
|
|
7641
7903
|
handleWorldSize,
|
|
7642
7904
|
handleWorldSize
|
|
7643
7905
|
);
|
|
7906
|
+
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7907
|
+
}
|
|
7908
|
+
if (this._selectedIds.length === 1 && ROTATABLE_TYPES.has(el.type)) {
|
|
7909
|
+
const stemStart = this.topMidpoint(layout);
|
|
7910
|
+
const stemEnd = layout.rotateHandle;
|
|
7911
|
+
canvasCtx.beginPath();
|
|
7912
|
+
canvasCtx.moveTo(stemStart.x, stemStart.y);
|
|
7913
|
+
canvasCtx.lineTo(stemEnd.x, stemEnd.y);
|
|
7914
|
+
canvasCtx.stroke();
|
|
7915
|
+
canvasCtx.setLineDash([]);
|
|
7916
|
+
canvasCtx.fillStyle = "#ffffff";
|
|
7917
|
+
canvasCtx.beginPath();
|
|
7918
|
+
canvasCtx.arc(stemEnd.x, stemEnd.y, handleWorldSize / 2, 0, Math.PI * 2);
|
|
7919
|
+
canvasCtx.fill();
|
|
7920
|
+
canvasCtx.stroke();
|
|
7921
|
+
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7644
7922
|
}
|
|
7645
|
-
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7646
|
-
} else if (el.type === "template") {
|
|
7647
|
-
canvasCtx.setLineDash([]);
|
|
7648
|
-
canvasCtx.fillStyle = "#ffffff";
|
|
7649
|
-
const hx = bounds.x + bounds.w;
|
|
7650
|
-
const hy = bounds.y + bounds.h;
|
|
7651
|
-
canvasCtx.fillRect(
|
|
7652
|
-
hx - handleWorldSize / 2,
|
|
7653
|
-
hy - handleWorldSize / 2,
|
|
7654
|
-
handleWorldSize,
|
|
7655
|
-
handleWorldSize
|
|
7656
|
-
);
|
|
7657
|
-
canvasCtx.strokeRect(
|
|
7658
|
-
hx - handleWorldSize / 2,
|
|
7659
|
-
hy - handleWorldSize / 2,
|
|
7660
|
-
handleWorldSize,
|
|
7661
|
-
handleWorldSize
|
|
7662
|
-
);
|
|
7663
|
-
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7664
7923
|
}
|
|
7665
|
-
if (
|
|
7666
|
-
const
|
|
7667
|
-
|
|
7668
|
-
canvasCtx.beginPath();
|
|
7669
|
-
canvasCtx.moveTo(stemStart.x, stemStart.y);
|
|
7670
|
-
canvasCtx.lineTo(stemEnd.x, stemEnd.y);
|
|
7671
|
-
canvasCtx.stroke();
|
|
7672
|
-
canvasCtx.setLineDash([]);
|
|
7673
|
-
canvasCtx.fillStyle = "#ffffff";
|
|
7674
|
-
canvasCtx.beginPath();
|
|
7675
|
-
canvasCtx.arc(stemEnd.x, stemEnd.y, handleWorldSize / 2, 0, Math.PI * 2);
|
|
7676
|
-
canvasCtx.fill();
|
|
7677
|
-
canvasCtx.stroke();
|
|
7678
|
-
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7924
|
+
if (el.locked) {
|
|
7925
|
+
const ne = layout.corners.find(([h]) => h === "ne")?.[1];
|
|
7926
|
+
if (ne) this.drawLockBadge(canvasCtx, ne, zoom);
|
|
7679
7927
|
}
|
|
7680
7928
|
}
|
|
7681
7929
|
canvasCtx.restore();
|
|
7682
7930
|
}
|
|
7931
|
+
drawLockBadge(ctx, at, zoom) {
|
|
7932
|
+
const r = 9 / zoom;
|
|
7933
|
+
ctx.save();
|
|
7934
|
+
ctx.setLineDash([]);
|
|
7935
|
+
ctx.beginPath();
|
|
7936
|
+
ctx.arc(at.x, at.y, r, 0, Math.PI * 2);
|
|
7937
|
+
ctx.fillStyle = "#ffffff";
|
|
7938
|
+
ctx.fill();
|
|
7939
|
+
ctx.strokeStyle = "#2196F3";
|
|
7940
|
+
ctx.lineWidth = 1.5 / zoom;
|
|
7941
|
+
ctx.stroke();
|
|
7942
|
+
const bw = 8 / zoom;
|
|
7943
|
+
const bh = 6 / zoom;
|
|
7944
|
+
ctx.fillStyle = "#2196F3";
|
|
7945
|
+
ctx.fillRect(at.x - bw / 2, at.y - bh / 2 + 1 / zoom, bw, bh);
|
|
7946
|
+
ctx.beginPath();
|
|
7947
|
+
ctx.arc(at.x, at.y - bh / 2 + 1 / zoom, 2.5 / zoom, Math.PI, 0);
|
|
7948
|
+
ctx.lineWidth = 1.4 / zoom;
|
|
7949
|
+
ctx.stroke();
|
|
7950
|
+
ctx.restore();
|
|
7951
|
+
}
|
|
7683
7952
|
renderBindingHighlights(canvasCtx, arrow, zoom) {
|
|
7684
7953
|
if (!this.ctx) return;
|
|
7685
7954
|
if (!arrow.fromBinding && !arrow.toBinding) return;
|
|
@@ -8652,7 +8921,7 @@ var TemplateTool = class {
|
|
|
8652
8921
|
};
|
|
8653
8922
|
|
|
8654
8923
|
// src/index.ts
|
|
8655
|
-
var VERSION = "0.
|
|
8924
|
+
var VERSION = "0.37.0";
|
|
8656
8925
|
// Annotate the CommonJS export names for ESM import in node:
|
|
8657
8926
|
0 && (module.exports = {
|
|
8658
8927
|
ArrowTool,
|