@fieldnotes/core 0.31.1 → 0.32.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 +16 -0
- package/dist/index.cjs +124 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -2
- package/dist/index.d.ts +8 -2
- package/dist/index.js +124 -29
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -563,6 +563,22 @@ const unsub = viewport.onSelectionChange((ids) => {
|
|
|
563
563
|
// call unsub() to unsubscribe
|
|
564
564
|
```
|
|
565
565
|
|
|
566
|
+
## Aligning the Selection
|
|
567
|
+
|
|
568
|
+
Two methods on `Viewport` let you snap or space selected elements in one undo step.
|
|
569
|
+
|
|
570
|
+
- **`viewport.alignSelection(edge)`** — `edge`: `AlignEdge` = `'left' | 'center-x' | 'right' | 'top' | 'middle' | 'bottom'`; aligns every selected element to the corresponding edge or center of the selection's bounding box. Needs 2+ selected elements. Locked elements anchor the bounding box without moving.
|
|
571
|
+
- **`viewport.distributeSelection(axis)`** — `axis`: `DistributeAxis` = `'horizontal' | 'vertical'`; evenly spaces selected elements' centers along the axis. Needs 3+ selected elements. Locked elements anchor the span without moving.
|
|
572
|
+
|
|
573
|
+
```typescript
|
|
574
|
+
viewport.alignSelection('left'); // flush left edges
|
|
575
|
+
viewport.alignSelection('center-x'); // center on vertical axis
|
|
576
|
+
viewport.alignSelection('middle'); // center on horizontal axis
|
|
577
|
+
viewport.distributeSelection('horizontal'); // equal horizontal spacing
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
Grids are ignored by both operations.
|
|
581
|
+
|
|
566
582
|
## Built-in Interactions
|
|
567
583
|
|
|
568
584
|
| Input | Action |
|
package/dist/index.cjs
CHANGED
|
@@ -2351,6 +2351,23 @@ function distToBounds(point, bounds) {
|
|
|
2351
2351
|
function findBoundArrows(elementId, store) {
|
|
2352
2352
|
return store.getElementsByType("arrow").filter((a) => a.fromBinding?.elementId === elementId || a.toBinding?.elementId === elementId);
|
|
2353
2353
|
}
|
|
2354
|
+
function updateArrowsBoundToElements(movedIds, store) {
|
|
2355
|
+
const movedNonArrowIds = /* @__PURE__ */ new Set();
|
|
2356
|
+
for (const id of movedIds) {
|
|
2357
|
+
const el = store.getById(id);
|
|
2358
|
+
if (el && el.type !== "arrow") movedNonArrowIds.add(id);
|
|
2359
|
+
}
|
|
2360
|
+
if (movedNonArrowIds.size === 0) return;
|
|
2361
|
+
const updated = /* @__PURE__ */ new Set();
|
|
2362
|
+
for (const id of movedNonArrowIds) {
|
|
2363
|
+
for (const ba of findBoundArrows(id, store)) {
|
|
2364
|
+
if (updated.has(ba.id)) continue;
|
|
2365
|
+
updated.add(ba.id);
|
|
2366
|
+
const updates = updateBoundArrow(ba, store);
|
|
2367
|
+
if (updates) store.update(ba.id, updates);
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2354
2371
|
function updateBoundArrow(arrow, store) {
|
|
2355
2372
|
if (!arrow.fromBinding && !arrow.toBinding) return null;
|
|
2356
2373
|
const updates = {};
|
|
@@ -3778,6 +3795,19 @@ var NoteEditor = class {
|
|
|
3778
3795
|
}
|
|
3779
3796
|
};
|
|
3780
3797
|
|
|
3798
|
+
// src/elements/translate.ts
|
|
3799
|
+
function translateElementPatch(el, dx, dy) {
|
|
3800
|
+
const position = { x: el.position.x + dx, y: el.position.y + dy };
|
|
3801
|
+
if (el.type === "arrow") {
|
|
3802
|
+
return {
|
|
3803
|
+
position,
|
|
3804
|
+
from: { x: el.from.x + dx, y: el.from.y + dy },
|
|
3805
|
+
to: { x: el.to.x + dx, y: el.to.y + dy }
|
|
3806
|
+
};
|
|
3807
|
+
}
|
|
3808
|
+
return { position };
|
|
3809
|
+
}
|
|
3810
|
+
|
|
3781
3811
|
// src/elements/arrow-label-editor.ts
|
|
3782
3812
|
var ArrowLabelEditor = class {
|
|
3783
3813
|
input = null;
|
|
@@ -5464,6 +5494,16 @@ function getElementStyle(element) {
|
|
|
5464
5494
|
}
|
|
5465
5495
|
|
|
5466
5496
|
// src/canvas/viewport.ts
|
|
5497
|
+
function unionBounds(list) {
|
|
5498
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
5499
|
+
for (const b of list) {
|
|
5500
|
+
minX = Math.min(minX, b.x);
|
|
5501
|
+
minY = Math.min(minY, b.y);
|
|
5502
|
+
maxX = Math.max(maxX, b.x + b.w);
|
|
5503
|
+
maxY = Math.max(maxY, b.y + b.h);
|
|
5504
|
+
}
|
|
5505
|
+
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
5506
|
+
}
|
|
5467
5507
|
var EMPTY_IDS = [];
|
|
5468
5508
|
var ARROW_HIT_THRESHOLD = 10;
|
|
5469
5509
|
function noop() {
|
|
@@ -5873,6 +5913,86 @@ var Viewport = class {
|
|
|
5873
5913
|
}
|
|
5874
5914
|
this.historyRecorder.commit();
|
|
5875
5915
|
}
|
|
5916
|
+
alignSelection(edge) {
|
|
5917
|
+
const bounded = this.boundedSelection();
|
|
5918
|
+
if (bounded.length < 2) return;
|
|
5919
|
+
const B = unionBounds(bounded.map((e) => e.bounds));
|
|
5920
|
+
this.historyRecorder.begin();
|
|
5921
|
+
const moved = [];
|
|
5922
|
+
for (const { id, el, bounds: b } of bounded) {
|
|
5923
|
+
if (!this.isMovable(el)) continue;
|
|
5924
|
+
let dx = 0;
|
|
5925
|
+
let dy = 0;
|
|
5926
|
+
switch (edge) {
|
|
5927
|
+
case "left":
|
|
5928
|
+
dx = B.x - b.x;
|
|
5929
|
+
break;
|
|
5930
|
+
case "right":
|
|
5931
|
+
dx = B.x + B.w - (b.x + b.w);
|
|
5932
|
+
break;
|
|
5933
|
+
case "center-x":
|
|
5934
|
+
dx = B.x + B.w / 2 - (b.x + b.w / 2);
|
|
5935
|
+
break;
|
|
5936
|
+
case "top":
|
|
5937
|
+
dy = B.y - b.y;
|
|
5938
|
+
break;
|
|
5939
|
+
case "bottom":
|
|
5940
|
+
dy = B.y + B.h - (b.y + b.h);
|
|
5941
|
+
break;
|
|
5942
|
+
case "middle":
|
|
5943
|
+
dy = B.y + B.h / 2 - (b.y + b.h / 2);
|
|
5944
|
+
break;
|
|
5945
|
+
}
|
|
5946
|
+
if (dx === 0 && dy === 0) continue;
|
|
5947
|
+
this.store.update(id, translateElementPatch(el, dx, dy));
|
|
5948
|
+
moved.push(id);
|
|
5949
|
+
}
|
|
5950
|
+
updateArrowsBoundToElements(moved, this.store);
|
|
5951
|
+
this.historyRecorder.commit();
|
|
5952
|
+
this.requestRender();
|
|
5953
|
+
}
|
|
5954
|
+
distributeSelection(axis) {
|
|
5955
|
+
const bounded = this.boundedSelection();
|
|
5956
|
+
if (bounded.length < 3) return;
|
|
5957
|
+
const center = (b) => axis === "horizontal" ? b.x + b.w / 2 : b.y + b.h / 2;
|
|
5958
|
+
const sorted = [...bounded].sort((p, q) => center(p.bounds) - center(q.bounds));
|
|
5959
|
+
const first = sorted[0];
|
|
5960
|
+
const last = sorted[sorted.length - 1];
|
|
5961
|
+
if (!first || !last) return;
|
|
5962
|
+
const c0 = center(first.bounds);
|
|
5963
|
+
const cN = center(last.bounds);
|
|
5964
|
+
const n = sorted.length;
|
|
5965
|
+
this.historyRecorder.begin();
|
|
5966
|
+
const moved = [];
|
|
5967
|
+
for (let i = 1; i < n - 1; i++) {
|
|
5968
|
+
const item = sorted[i];
|
|
5969
|
+
if (!item || !this.isMovable(item.el)) continue;
|
|
5970
|
+
const target = c0 + i * (cN - c0) / (n - 1);
|
|
5971
|
+
const delta = target - center(item.bounds);
|
|
5972
|
+
if (delta === 0) continue;
|
|
5973
|
+
const [dx, dy] = axis === "horizontal" ? [delta, 0] : [0, delta];
|
|
5974
|
+
this.store.update(item.id, translateElementPatch(item.el, dx, dy));
|
|
5975
|
+
moved.push(item.id);
|
|
5976
|
+
}
|
|
5977
|
+
updateArrowsBoundToElements(moved, this.store);
|
|
5978
|
+
this.historyRecorder.commit();
|
|
5979
|
+
this.requestRender();
|
|
5980
|
+
}
|
|
5981
|
+
boundedSelection() {
|
|
5982
|
+
const out = [];
|
|
5983
|
+
for (const id of this.getSelectedIds()) {
|
|
5984
|
+
const el = this.store.getById(id);
|
|
5985
|
+
if (!el) continue;
|
|
5986
|
+
const bounds = getElementBounds(el);
|
|
5987
|
+
if (bounds) out.push({ id, el, bounds });
|
|
5988
|
+
}
|
|
5989
|
+
return out;
|
|
5990
|
+
}
|
|
5991
|
+
isMovable(el) {
|
|
5992
|
+
if (el.locked) return false;
|
|
5993
|
+
if (el.type === "arrow" && (el.fromBinding ?? el.toBinding)) return false;
|
|
5994
|
+
return true;
|
|
5995
|
+
}
|
|
5876
5996
|
getRenderStats() {
|
|
5877
5997
|
return this.renderLoop.getStats();
|
|
5878
5998
|
}
|
|
@@ -6880,40 +7000,15 @@ var SelectTool = class {
|
|
|
6880
7000
|
}
|
|
6881
7001
|
}
|
|
6882
7002
|
updateArrowsBoundTo(ids, ctx) {
|
|
6883
|
-
|
|
6884
|
-
for (const id of ids) {
|
|
6885
|
-
const el = ctx.store.getById(id);
|
|
6886
|
-
if (el && el.type !== "arrow") movedNonArrowIds.add(id);
|
|
6887
|
-
}
|
|
6888
|
-
if (movedNonArrowIds.size === 0) return;
|
|
6889
|
-
const updatedArrows = /* @__PURE__ */ new Set();
|
|
6890
|
-
for (const id of movedNonArrowIds) {
|
|
6891
|
-
const boundArrows = findBoundArrows(id, ctx.store);
|
|
6892
|
-
for (const ba of boundArrows) {
|
|
6893
|
-
if (updatedArrows.has(ba.id)) continue;
|
|
6894
|
-
updatedArrows.add(ba.id);
|
|
6895
|
-
const updates = updateBoundArrow(ba, ctx.store);
|
|
6896
|
-
if (updates) ctx.store.update(ba.id, updates);
|
|
6897
|
-
}
|
|
6898
|
-
}
|
|
7003
|
+
updateArrowsBoundToElements(ids, ctx.store);
|
|
6899
7004
|
}
|
|
6900
7005
|
nudgeSelection(dx, dy, ctx) {
|
|
6901
7006
|
let moved = false;
|
|
6902
7007
|
for (const id of this._selectedIds) {
|
|
6903
7008
|
const el = ctx.store.getById(id);
|
|
6904
7009
|
if (!el || el.locked) continue;
|
|
6905
|
-
if (el.type === "arrow")
|
|
6906
|
-
|
|
6907
|
-
ctx.store.update(id, {
|
|
6908
|
-
position: { x: el.position.x + dx, y: el.position.y + dy },
|
|
6909
|
-
from: { x: el.from.x + dx, y: el.from.y + dy },
|
|
6910
|
-
to: { x: el.to.x + dx, y: el.to.y + dy }
|
|
6911
|
-
});
|
|
6912
|
-
} else {
|
|
6913
|
-
ctx.store.update(id, {
|
|
6914
|
-
position: { x: el.position.x + dx, y: el.position.y + dy }
|
|
6915
|
-
});
|
|
6916
|
-
}
|
|
7010
|
+
if (el.type === "arrow" && (el.fromBinding || el.toBinding)) continue;
|
|
7011
|
+
ctx.store.update(id, translateElementPatch(el, dx, dy));
|
|
6917
7012
|
moved = true;
|
|
6918
7013
|
}
|
|
6919
7014
|
if (moved) {
|
|
@@ -8105,7 +8200,7 @@ var TemplateTool = class {
|
|
|
8105
8200
|
};
|
|
8106
8201
|
|
|
8107
8202
|
// src/index.ts
|
|
8108
|
-
var VERSION = "0.
|
|
8203
|
+
var VERSION = "0.32.0";
|
|
8109
8204
|
// Annotate the CommonJS export names for ESM import in node:
|
|
8110
8205
|
0 && (module.exports = {
|
|
8111
8206
|
ArrowTool,
|