@fieldnotes/core 0.32.0 → 0.33.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 +9 -0
- package/dist/index.cjs +121 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +121 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -579,6 +579,15 @@ viewport.distributeSelection('horizontal'); // equal horizontal spacing
|
|
|
579
579
|
|
|
580
580
|
Grids are ignored by both operations.
|
|
581
581
|
|
|
582
|
+
## Smart Alignment Guides
|
|
583
|
+
|
|
584
|
+
Call `viewport.setSmartGuides(true)` to enable drag-time alignment snapping. While dragging a selection, its edges and centers snap to the edges and centers of nearby visible elements (within 6 screen pixels), and guide lines are drawn at each matched alignment. Smart guides replace grid snapping for the duration of the drag; the result is still committed as a single undo step.
|
|
585
|
+
|
|
586
|
+
```typescript
|
|
587
|
+
viewport.setSmartGuides(true); // enable
|
|
588
|
+
viewport.setSmartGuides(false); // disable (default)
|
|
589
|
+
```
|
|
590
|
+
|
|
582
591
|
## Built-in Interactions
|
|
583
592
|
|
|
584
593
|
| Input | Action |
|
package/dist/index.cjs
CHANGED
|
@@ -5576,7 +5576,9 @@ var Viewport = class {
|
|
|
5576
5576
|
gridSize: this._gridSize,
|
|
5577
5577
|
activeLayerId: this.layerManager.activeLayerId,
|
|
5578
5578
|
isLayerVisible: (id) => this.layerManager.isLayerVisible(id),
|
|
5579
|
-
isLayerLocked: (id) => this.layerManager.isLayerLocked(id)
|
|
5579
|
+
isLayerLocked: (id) => this.layerManager.isLayerLocked(id),
|
|
5580
|
+
smartGuides: false,
|
|
5581
|
+
getVisibleRect: () => this.camera.getVisibleRect(this.canvasEl.clientWidth, this.canvasEl.clientHeight)
|
|
5580
5582
|
};
|
|
5581
5583
|
this.inputHandler = new InputHandler(this.wrapper, this.camera, {
|
|
5582
5584
|
toolManager: this.toolManager,
|
|
@@ -5680,6 +5682,7 @@ var Viewport = class {
|
|
|
5680
5682
|
marginViewport;
|
|
5681
5683
|
resizeObserver = null;
|
|
5682
5684
|
_snapToGrid = false;
|
|
5685
|
+
_smartGuides = false;
|
|
5683
5686
|
_gridSize;
|
|
5684
5687
|
renderLoop;
|
|
5685
5688
|
domNodeManager;
|
|
@@ -5700,6 +5703,13 @@ var Viewport = class {
|
|
|
5700
5703
|
this._snapToGrid = enabled;
|
|
5701
5704
|
this.toolContext.snapToGrid = enabled;
|
|
5702
5705
|
}
|
|
5706
|
+
get smartGuides() {
|
|
5707
|
+
return this._smartGuides;
|
|
5708
|
+
}
|
|
5709
|
+
setSmartGuides(enabled) {
|
|
5710
|
+
this._smartGuides = enabled;
|
|
5711
|
+
this.toolContext.smartGuides = enabled;
|
|
5712
|
+
}
|
|
5703
5713
|
fitToContent(padding = 40) {
|
|
5704
5714
|
if (this.wrapper.clientWidth === 0 || this.wrapper.clientHeight === 0) return;
|
|
5705
5715
|
const visibleElements = this.store.getAll().filter((el) => this.layerManager.isLayerVisible(el.layerId));
|
|
@@ -6737,8 +6747,47 @@ function renderArrowHandles(canvasCtx, arrow, zoom) {
|
|
|
6737
6747
|
}
|
|
6738
6748
|
}
|
|
6739
6749
|
|
|
6750
|
+
// src/elements/snap-guides.ts
|
|
6751
|
+
function xAnchors(b) {
|
|
6752
|
+
return { lo: b.x, mid: b.x + b.w / 2, hi: b.x + b.w };
|
|
6753
|
+
}
|
|
6754
|
+
function yAnchors(b) {
|
|
6755
|
+
return { lo: b.y, mid: b.y + b.h / 2, hi: b.y + b.h };
|
|
6756
|
+
}
|
|
6757
|
+
function bestAxisSnap(moving, targets, anchorsFn, threshold) {
|
|
6758
|
+
let best = null;
|
|
6759
|
+
for (const t of targets) {
|
|
6760
|
+
const ta = anchorsFn(t);
|
|
6761
|
+
const pairs = [
|
|
6762
|
+
// colinear alignment: same-type edges/centers line up
|
|
6763
|
+
[ta.lo - moving.lo, ta.lo],
|
|
6764
|
+
[ta.mid - moving.mid, ta.mid],
|
|
6765
|
+
[ta.hi - moving.hi, ta.hi],
|
|
6766
|
+
// abutment: the moving box sits flush against the target's opposite edge
|
|
6767
|
+
[ta.lo - moving.hi, ta.lo],
|
|
6768
|
+
[ta.hi - moving.lo, ta.hi]
|
|
6769
|
+
];
|
|
6770
|
+
for (const [delta, position] of pairs) {
|
|
6771
|
+
const abs = Math.abs(delta);
|
|
6772
|
+
if (abs <= threshold && (best === null || abs < Math.abs(best.delta))) {
|
|
6773
|
+
best = { delta, position };
|
|
6774
|
+
}
|
|
6775
|
+
}
|
|
6776
|
+
}
|
|
6777
|
+
return best;
|
|
6778
|
+
}
|
|
6779
|
+
function computeSnapGuides(moving, targets, threshold) {
|
|
6780
|
+
const xSnap = bestAxisSnap(xAnchors(moving), targets, xAnchors, threshold);
|
|
6781
|
+
const ySnap = bestAxisSnap(yAnchors(moving), targets, yAnchors, threshold);
|
|
6782
|
+
const guides = [];
|
|
6783
|
+
if (xSnap) guides.push({ axis: "x", position: xSnap.position });
|
|
6784
|
+
if (ySnap) guides.push({ axis: "y", position: ySnap.position });
|
|
6785
|
+
return { dx: xSnap?.delta ?? 0, dy: ySnap?.delta ?? 0, guides };
|
|
6786
|
+
}
|
|
6787
|
+
|
|
6740
6788
|
// src/tools/select-tool.ts
|
|
6741
6789
|
var HANDLE_SIZE = 8;
|
|
6790
|
+
var SNAP_PX = 6;
|
|
6742
6791
|
var HANDLE_HIT_PADDING2 = 4;
|
|
6743
6792
|
var SELECTION_PAD = 4;
|
|
6744
6793
|
var MIN_ELEMENT_SIZE = 20;
|
|
@@ -6758,6 +6807,9 @@ var SelectTool = class {
|
|
|
6758
6807
|
ctx = null;
|
|
6759
6808
|
pendingSingleSelectId = null;
|
|
6760
6809
|
hasDragged = false;
|
|
6810
|
+
activeGuides = [];
|
|
6811
|
+
dragSnapTargets = null;
|
|
6812
|
+
dragVisibleRect = null;
|
|
6761
6813
|
resizeAspectRatio = 0;
|
|
6762
6814
|
hoveredId = null;
|
|
6763
6815
|
get selectedIds() {
|
|
@@ -6789,6 +6841,9 @@ var SelectTool = class {
|
|
|
6789
6841
|
this.setSelectedIds([]);
|
|
6790
6842
|
this.mode = { type: "idle" };
|
|
6791
6843
|
this.hoveredId = null;
|
|
6844
|
+
this.activeGuides = [];
|
|
6845
|
+
this.dragSnapTargets = null;
|
|
6846
|
+
this.dragVisibleRect = null;
|
|
6792
6847
|
ctx.setCursor?.("default");
|
|
6793
6848
|
}
|
|
6794
6849
|
snap(point, ctx) {
|
|
@@ -6797,6 +6852,8 @@ var SelectTool = class {
|
|
|
6797
6852
|
onPointerDown(state, ctx) {
|
|
6798
6853
|
this.ctx = ctx;
|
|
6799
6854
|
this.setHovered(null, ctx);
|
|
6855
|
+
this.dragSnapTargets = null;
|
|
6856
|
+
this.dragVisibleRect = null;
|
|
6800
6857
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
6801
6858
|
this.lastWorld = this.snap(world, ctx);
|
|
6802
6859
|
this.currentWorld = world;
|
|
@@ -6897,6 +6954,31 @@ var SelectTool = class {
|
|
|
6897
6954
|
const dx = snapped.x - this.lastWorld.x;
|
|
6898
6955
|
const dy = snapped.y - this.lastWorld.y;
|
|
6899
6956
|
this.lastWorld = snapped;
|
|
6957
|
+
let adjDx = dx;
|
|
6958
|
+
let adjDy = dy;
|
|
6959
|
+
this.activeGuides = [];
|
|
6960
|
+
if (ctx.smartGuides) {
|
|
6961
|
+
if (this.dragSnapTargets === null) {
|
|
6962
|
+
const selSet = new Set(this._selectedIds);
|
|
6963
|
+
this.dragVisibleRect = ctx.getVisibleRect?.() ?? null;
|
|
6964
|
+
const candidates = (this.dragVisibleRect ? ctx.store.queryRect(this.dragVisibleRect) : ctx.store.getAll()).filter((el) => !selSet.has(el.id) && el.type !== "grid");
|
|
6965
|
+
const targets = [];
|
|
6966
|
+
for (const el of candidates) {
|
|
6967
|
+
const b = getElementBounds(el);
|
|
6968
|
+
if (b) targets.push(b);
|
|
6969
|
+
}
|
|
6970
|
+
this.dragSnapTargets = targets;
|
|
6971
|
+
}
|
|
6972
|
+
const selectedEls = this._selectedIds.map((id) => ctx.store.getById(id)).filter((el) => !!el && !el.locked);
|
|
6973
|
+
const base = getElementsBoundingBox(selectedEls);
|
|
6974
|
+
if (base) {
|
|
6975
|
+
const moving = { x: base.x + dx, y: base.y + dy, w: base.w, h: base.h };
|
|
6976
|
+
const res = computeSnapGuides(moving, this.dragSnapTargets, SNAP_PX / ctx.camera.zoom);
|
|
6977
|
+
adjDx = dx + res.dx;
|
|
6978
|
+
adjDy = dy + res.dy;
|
|
6979
|
+
this.activeGuides = res.guides;
|
|
6980
|
+
}
|
|
6981
|
+
}
|
|
6900
6982
|
for (const id of this._selectedIds) {
|
|
6901
6983
|
const el = ctx.store.getById(id);
|
|
6902
6984
|
if (!el || el.locked) continue;
|
|
@@ -6905,13 +6987,13 @@ var SelectTool = class {
|
|
|
6905
6987
|
continue;
|
|
6906
6988
|
}
|
|
6907
6989
|
ctx.store.update(id, {
|
|
6908
|
-
position: { x: el.position.x +
|
|
6909
|
-
from: { x: el.from.x +
|
|
6910
|
-
to: { x: el.to.x +
|
|
6990
|
+
position: { x: el.position.x + adjDx, y: el.position.y + adjDy },
|
|
6991
|
+
from: { x: el.from.x + adjDx, y: el.from.y + adjDy },
|
|
6992
|
+
to: { x: el.to.x + adjDx, y: el.to.y + adjDy }
|
|
6911
6993
|
});
|
|
6912
|
-
} else if (ctx.gridType && "size" in el) {
|
|
6913
|
-
const centerX = el.position.x + el.size.w / 2 +
|
|
6914
|
-
const centerY = el.position.y + el.size.h / 2 +
|
|
6994
|
+
} else if (!ctx.smartGuides && ctx.gridType && "size" in el) {
|
|
6995
|
+
const centerX = el.position.x + el.size.w / 2 + adjDx;
|
|
6996
|
+
const centerY = el.position.y + el.size.h / 2 + adjDy;
|
|
6915
6997
|
const snappedCenter = this.snap({ x: centerX, y: centerY }, ctx);
|
|
6916
6998
|
ctx.store.update(id, {
|
|
6917
6999
|
position: {
|
|
@@ -6921,7 +7003,7 @@ var SelectTool = class {
|
|
|
6921
7003
|
});
|
|
6922
7004
|
} else {
|
|
6923
7005
|
ctx.store.update(id, {
|
|
6924
|
-
position: { x: el.position.x +
|
|
7006
|
+
position: { x: el.position.x + adjDx, y: el.position.y + adjDy }
|
|
6925
7007
|
});
|
|
6926
7008
|
}
|
|
6927
7009
|
}
|
|
@@ -6951,6 +7033,10 @@ var SelectTool = class {
|
|
|
6951
7033
|
this.hasDragged = false;
|
|
6952
7034
|
const resizedNoteId = this.mode.type === "resizing" ? this.mode.elementId : null;
|
|
6953
7035
|
this.mode = { type: "idle" };
|
|
7036
|
+
this.activeGuides = [];
|
|
7037
|
+
this.dragSnapTargets = null;
|
|
7038
|
+
this.dragVisibleRect = null;
|
|
7039
|
+
ctx.requestRender();
|
|
6954
7040
|
ctx.setCursor?.("default");
|
|
6955
7041
|
if (resizedNoteId !== null) {
|
|
6956
7042
|
const el = ctx.store.getById(resizedNoteId);
|
|
@@ -6998,6 +7084,32 @@ var SelectTool = class {
|
|
|
6998
7084
|
}
|
|
6999
7085
|
}
|
|
7000
7086
|
}
|
|
7087
|
+
this.renderGuideLines(canvasCtx);
|
|
7088
|
+
}
|
|
7089
|
+
renderGuideLines(canvasCtx) {
|
|
7090
|
+
if (this.mode.type !== "dragging" || !this.ctx || this.activeGuides.length === 0) return;
|
|
7091
|
+
const zoom = this.ctx.camera.zoom;
|
|
7092
|
+
const rect = this.dragVisibleRect;
|
|
7093
|
+
canvasCtx.save();
|
|
7094
|
+
canvasCtx.strokeStyle = "#FF4081";
|
|
7095
|
+
canvasCtx.lineWidth = 1 / zoom;
|
|
7096
|
+
canvasCtx.setLineDash([]);
|
|
7097
|
+
for (const g of this.activeGuides) {
|
|
7098
|
+
canvasCtx.beginPath();
|
|
7099
|
+
if (g.axis === "x") {
|
|
7100
|
+
const y0 = rect ? rect.y : this.currentWorld.y - 1e5;
|
|
7101
|
+
const y1 = rect ? rect.y + rect.h : this.currentWorld.y + 1e5;
|
|
7102
|
+
canvasCtx.moveTo(g.position, y0);
|
|
7103
|
+
canvasCtx.lineTo(g.position, y1);
|
|
7104
|
+
} else {
|
|
7105
|
+
const x0 = rect ? rect.x : this.currentWorld.x - 1e5;
|
|
7106
|
+
const x1 = rect ? rect.x + rect.w : this.currentWorld.x + 1e5;
|
|
7107
|
+
canvasCtx.moveTo(x0, g.position);
|
|
7108
|
+
canvasCtx.lineTo(x1, g.position);
|
|
7109
|
+
}
|
|
7110
|
+
canvasCtx.stroke();
|
|
7111
|
+
}
|
|
7112
|
+
canvasCtx.restore();
|
|
7001
7113
|
}
|
|
7002
7114
|
updateArrowsBoundTo(ids, ctx) {
|
|
7003
7115
|
updateArrowsBoundToElements(ids, ctx.store);
|
|
@@ -8200,7 +8312,7 @@ var TemplateTool = class {
|
|
|
8200
8312
|
};
|
|
8201
8313
|
|
|
8202
8314
|
// src/index.ts
|
|
8203
|
-
var VERSION = "0.
|
|
8315
|
+
var VERSION = "0.33.0";
|
|
8204
8316
|
// Annotate the CommonJS export names for ESM import in node:
|
|
8205
8317
|
0 && (module.exports = {
|
|
8206
8318
|
ArrowTool,
|