@fieldnotes/core 0.22.0 → 0.24.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 +15 -0
- package/dist/index.cjs +212 -131
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -2
- package/dist/index.d.ts +13 -2
- package/dist/index.js +212 -131
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -923,16 +923,17 @@ var KeyboardActions = class {
|
|
|
923
923
|
this.pasteCount = 0;
|
|
924
924
|
}
|
|
925
925
|
paste() {
|
|
926
|
+
if (this.deps.isToolActive()) return;
|
|
926
927
|
this.flushPendingNudge();
|
|
927
|
-
if (this.clipboard.length === 0
|
|
928
|
+
if (this.clipboard.length === 0) return;
|
|
928
929
|
const sel = this.selectTool();
|
|
929
930
|
if (!sel) return;
|
|
930
931
|
this.pasteCount++;
|
|
931
932
|
this.insertClones(this.clipboard, this.pasteCount * 20, sel);
|
|
932
933
|
}
|
|
933
934
|
duplicate() {
|
|
934
|
-
this.flushPendingNudge();
|
|
935
935
|
if (this.deps.isToolActive()) return;
|
|
936
|
+
this.flushPendingNudge();
|
|
936
937
|
const sel = this.selectTool();
|
|
937
938
|
if (!sel) return;
|
|
938
939
|
const source = [];
|
|
@@ -1116,6 +1117,11 @@ function parseBinding(binding) {
|
|
|
1116
1117
|
throw new Error(`Invalid shortcut binding "${binding}": unknown modifier "${part}"`);
|
|
1117
1118
|
}
|
|
1118
1119
|
}
|
|
1120
|
+
if (parsed.mod && (parsed.ctrl || parsed.meta)) {
|
|
1121
|
+
throw new Error(
|
|
1122
|
+
`Invalid shortcut binding "${binding}": "mod" already means Ctrl or Cmd; don't combine it with ctrl/meta`
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1119
1125
|
return parsed;
|
|
1120
1126
|
}
|
|
1121
1127
|
function bindingMatches(p, e, allowShift) {
|
|
@@ -1259,6 +1265,10 @@ var InputHandler = class {
|
|
|
1259
1265
|
this.inputFilter.reset();
|
|
1260
1266
|
this.deferredDown = null;
|
|
1261
1267
|
this.lastPointerEvent = null;
|
|
1268
|
+
if (this.scope === "focus") {
|
|
1269
|
+
this.element.removeAttribute("tabindex");
|
|
1270
|
+
this.element.style.outline = "";
|
|
1271
|
+
}
|
|
1262
1272
|
}
|
|
1263
1273
|
bind() {
|
|
1264
1274
|
const opts = { signal: this.abortController.signal };
|
|
@@ -1589,6 +1599,22 @@ var DoubleTapDetector = class {
|
|
|
1589
1599
|
}
|
|
1590
1600
|
};
|
|
1591
1601
|
|
|
1602
|
+
// src/core/geometry.ts
|
|
1603
|
+
function distSqToSegment(p, a, b) {
|
|
1604
|
+
const abx = b.x - a.x;
|
|
1605
|
+
const aby = b.y - a.y;
|
|
1606
|
+
const apx = p.x - a.x;
|
|
1607
|
+
const apy = p.y - a.y;
|
|
1608
|
+
const lenSq = abx * abx + aby * aby;
|
|
1609
|
+
if (lenSq === 0) {
|
|
1610
|
+
return apx * apx + apy * apy;
|
|
1611
|
+
}
|
|
1612
|
+
const t = Math.max(0, Math.min(1, (apx * abx + apy * aby) / lenSq));
|
|
1613
|
+
const dx = p.x - (a.x + t * abx);
|
|
1614
|
+
const dy = p.y - (a.y + t * aby);
|
|
1615
|
+
return dx * dx + dy * dy;
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1592
1618
|
// src/elements/arrow-geometry.ts
|
|
1593
1619
|
function getArrowControlPoint(from, to, bend) {
|
|
1594
1620
|
const midX = (from.x + to.x) / 2;
|
|
@@ -1677,16 +1703,7 @@ function bezierPoint(from, cp, to, t) {
|
|
|
1677
1703
|
};
|
|
1678
1704
|
}
|
|
1679
1705
|
function isNearLine(point, a, b, threshold) {
|
|
1680
|
-
|
|
1681
|
-
const dy = b.y - a.y;
|
|
1682
|
-
const lenSq = dx * dx + dy * dy;
|
|
1683
|
-
if (lenSq === 0) {
|
|
1684
|
-
return Math.hypot(point.x - a.x, point.y - a.y) <= threshold;
|
|
1685
|
-
}
|
|
1686
|
-
const t = Math.max(0, Math.min(1, ((point.x - a.x) * dx + (point.y - a.y) * dy) / lenSq));
|
|
1687
|
-
const projX = a.x + t * dx;
|
|
1688
|
-
const projY = a.y + t * dy;
|
|
1689
|
-
return Math.hypot(point.x - projX, point.y - projY) <= threshold;
|
|
1706
|
+
return distSqToSegment(point, a, b) <= threshold * threshold;
|
|
1690
1707
|
}
|
|
1691
1708
|
|
|
1692
1709
|
// src/elements/element-bounds.ts
|
|
@@ -2670,6 +2687,7 @@ var ElementRenderer = class {
|
|
|
2670
2687
|
canvasSize = null;
|
|
2671
2688
|
hexTileCache = null;
|
|
2672
2689
|
hexTileCacheKey = "";
|
|
2690
|
+
gridBoundsOverride = null;
|
|
2673
2691
|
setStore(store) {
|
|
2674
2692
|
this.store = store;
|
|
2675
2693
|
}
|
|
@@ -2685,6 +2703,9 @@ var ElementRenderer = class {
|
|
|
2685
2703
|
setCanvasSize(w, h) {
|
|
2686
2704
|
this.canvasSize = { w, h };
|
|
2687
2705
|
}
|
|
2706
|
+
setGridBoundsOverride(bounds) {
|
|
2707
|
+
this.gridBoundsOverride = bounds;
|
|
2708
|
+
}
|
|
2688
2709
|
isDomElement(element) {
|
|
2689
2710
|
return DOM_ELEMENT_TYPES.has(element.type);
|
|
2690
2711
|
}
|
|
@@ -2855,20 +2876,20 @@ var ElementRenderer = class {
|
|
|
2855
2876
|
}
|
|
2856
2877
|
}
|
|
2857
2878
|
renderGrid(ctx, grid) {
|
|
2858
|
-
|
|
2879
|
+
const canvasSize = this.canvasSize;
|
|
2880
|
+
if (!canvasSize) return;
|
|
2859
2881
|
const cam = this.camera;
|
|
2860
2882
|
if (!cam) return;
|
|
2861
|
-
const
|
|
2862
|
-
|
|
2863
|
-
x:
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
};
|
|
2883
|
+
const bounds = this.gridBoundsOverride ?? (() => {
|
|
2884
|
+
const topLeft = cam.screenToWorld({ x: 0, y: 0 });
|
|
2885
|
+
const bottomRight = cam.screenToWorld({ x: canvasSize.w, y: canvasSize.h });
|
|
2886
|
+
return {
|
|
2887
|
+
minX: topLeft.x,
|
|
2888
|
+
minY: topLeft.y,
|
|
2889
|
+
maxX: bottomRight.x,
|
|
2890
|
+
maxY: bottomRight.y
|
|
2891
|
+
};
|
|
2892
|
+
})();
|
|
2872
2893
|
if (grid.gridType === "hex") {
|
|
2873
2894
|
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
2874
2895
|
const scale = cam.zoom * dpr;
|
|
@@ -3113,9 +3134,9 @@ var ElementRenderer = class {
|
|
|
3113
3134
|
});
|
|
3114
3135
|
}
|
|
3115
3136
|
};
|
|
3116
|
-
img.onerror = () => {
|
|
3137
|
+
img.onerror = (event) => {
|
|
3117
3138
|
this.imageCache.set(src, "failed");
|
|
3118
|
-
this.onImageError?.(src);
|
|
3139
|
+
this.onImageError?.(src, event);
|
|
3119
3140
|
this.onImageLoad?.();
|
|
3120
3141
|
};
|
|
3121
3142
|
return null;
|
|
@@ -3540,7 +3561,10 @@ var NoteEditor = class {
|
|
|
3540
3561
|
this.editingNode.removeAttribute("data-fn-placeholder");
|
|
3541
3562
|
this.editingNode.removeAttribute("data-fn-empty");
|
|
3542
3563
|
const text = sanitizeNoteHtml(this.editingNode.innerHTML);
|
|
3543
|
-
store.
|
|
3564
|
+
const current = store.getById(this.editingId);
|
|
3565
|
+
if (current && (current.type === "note" || current.type === "text") && current.text !== text) {
|
|
3566
|
+
store.update(this.editingId, { text });
|
|
3567
|
+
}
|
|
3544
3568
|
this.editingNode.contentEditable = "false";
|
|
3545
3569
|
Object.assign(this.editingNode.style, {
|
|
3546
3570
|
userSelect: "none",
|
|
@@ -4727,19 +4751,14 @@ var RenderLoop = class {
|
|
|
4727
4751
|
layerManager;
|
|
4728
4752
|
domNodeManager;
|
|
4729
4753
|
layerCache;
|
|
4754
|
+
marginViewport;
|
|
4730
4755
|
activeDrawingLayerId = null;
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
lastCamY;
|
|
4756
|
+
gridCacheDirty = true;
|
|
4757
|
+
// set on recenter/viewport-change; consumed by the grid block
|
|
4734
4758
|
stats = new RenderStats();
|
|
4735
4759
|
layerGroups = /* @__PURE__ */ new Map();
|
|
4736
4760
|
gridCacheCanvas = null;
|
|
4737
4761
|
gridCacheCtx = null;
|
|
4738
|
-
gridCacheZoom = -1;
|
|
4739
|
-
gridCacheCamX = -Infinity;
|
|
4740
|
-
gridCacheCamY = -Infinity;
|
|
4741
|
-
gridCacheWidth = 0;
|
|
4742
|
-
gridCacheHeight = 0;
|
|
4743
4762
|
lastGridRef = null;
|
|
4744
4763
|
constructor(deps) {
|
|
4745
4764
|
this.canvasEl = deps.canvasEl;
|
|
@@ -4751,9 +4770,7 @@ var RenderLoop = class {
|
|
|
4751
4770
|
this.layerManager = deps.layerManager;
|
|
4752
4771
|
this.domNodeManager = deps.domNodeManager;
|
|
4753
4772
|
this.layerCache = deps.layerCache;
|
|
4754
|
-
this.
|
|
4755
|
-
this.lastCamX = deps.camera.position.x;
|
|
4756
|
-
this.lastCamY = deps.camera.position.y;
|
|
4773
|
+
this.marginViewport = deps.marginViewport;
|
|
4757
4774
|
}
|
|
4758
4775
|
requestRender() {
|
|
4759
4776
|
this.needsRender = true;
|
|
@@ -4780,7 +4797,9 @@ var RenderLoop = class {
|
|
|
4780
4797
|
setCanvasSize(width, height) {
|
|
4781
4798
|
this.canvasEl.width = width;
|
|
4782
4799
|
this.canvasEl.height = height;
|
|
4783
|
-
|
|
4800
|
+
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
4801
|
+
this.marginViewport.setViewport(width / dpr, height / dpr, dpr);
|
|
4802
|
+
this.layerCache.resize();
|
|
4784
4803
|
}
|
|
4785
4804
|
setActiveDrawingLayer(layerId) {
|
|
4786
4805
|
this.activeDrawingLayerId = layerId;
|
|
@@ -4794,30 +4813,29 @@ var RenderLoop = class {
|
|
|
4794
4813
|
getStats() {
|
|
4795
4814
|
return this.stats.getSnapshot();
|
|
4796
4815
|
}
|
|
4797
|
-
compositeLayerCache(ctx, layerId
|
|
4816
|
+
compositeLayerCache(ctx, layerId) {
|
|
4798
4817
|
const cached = this.layerCache.getCanvas(layerId);
|
|
4818
|
+
const offset = this.marginViewport.compositeOffset(
|
|
4819
|
+
this.camera.position.x,
|
|
4820
|
+
this.camera.position.y
|
|
4821
|
+
);
|
|
4799
4822
|
ctx.save();
|
|
4800
|
-
ctx.
|
|
4801
|
-
ctx.
|
|
4802
|
-
ctx.scale(1 / dpr, 1 / dpr);
|
|
4803
|
-
ctx.drawImage(cached, 0, 0);
|
|
4823
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
4824
|
+
ctx.drawImage(cached, offset.x, offset.y);
|
|
4804
4825
|
ctx.restore();
|
|
4805
4826
|
}
|
|
4806
|
-
ensureGridCache(
|
|
4807
|
-
|
|
4827
|
+
ensureGridCache() {
|
|
4828
|
+
const w = this.marginViewport.physicalWidth();
|
|
4829
|
+
const h = this.marginViewport.physicalHeight();
|
|
4830
|
+
if (this.gridCacheCanvas !== null && this.gridCacheCanvas.width === w && this.gridCacheCanvas.height === h) {
|
|
4808
4831
|
return;
|
|
4809
4832
|
}
|
|
4810
|
-
const physWidth = Math.round(cssWidth * dpr);
|
|
4811
|
-
const physHeight = Math.round(cssHeight * dpr);
|
|
4812
4833
|
if (typeof OffscreenCanvas !== "undefined") {
|
|
4813
|
-
this.gridCacheCanvas = new OffscreenCanvas(
|
|
4814
|
-
physWidth,
|
|
4815
|
-
physHeight
|
|
4816
|
-
);
|
|
4834
|
+
this.gridCacheCanvas = new OffscreenCanvas(w, h);
|
|
4817
4835
|
} else if (typeof document !== "undefined") {
|
|
4818
4836
|
const el = document.createElement("canvas");
|
|
4819
|
-
el.width =
|
|
4820
|
-
el.height =
|
|
4837
|
+
el.width = w;
|
|
4838
|
+
el.height = h;
|
|
4821
4839
|
this.gridCacheCanvas = el;
|
|
4822
4840
|
} else {
|
|
4823
4841
|
this.gridCacheCanvas = null;
|
|
@@ -4836,14 +4854,14 @@ var RenderLoop = class {
|
|
|
4836
4854
|
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
4837
4855
|
const cssWidth = this.canvasEl.clientWidth;
|
|
4838
4856
|
const cssHeight = this.canvasEl.clientHeight;
|
|
4857
|
+
this.marginViewport.setViewport(cssWidth, cssHeight, dpr);
|
|
4839
4858
|
const currentZoom = this.camera.zoom;
|
|
4840
4859
|
const currentCamX = this.camera.position.x;
|
|
4841
4860
|
const currentCamY = this.camera.position.y;
|
|
4842
|
-
if (
|
|
4861
|
+
if (this.marginViewport.needsRecenter(currentCamX, currentCamY, currentZoom)) {
|
|
4862
|
+
this.marginViewport.recenter(currentCamX, currentCamY, currentZoom);
|
|
4843
4863
|
this.layerCache.markAllDirty();
|
|
4844
|
-
this.
|
|
4845
|
-
this.lastCamX = currentCamX;
|
|
4846
|
-
this.lastCamY = currentCamY;
|
|
4864
|
+
this.gridCacheDirty = true;
|
|
4847
4865
|
}
|
|
4848
4866
|
ctx.save();
|
|
4849
4867
|
ctx.scale(dpr, dpr);
|
|
@@ -4862,13 +4880,13 @@ var RenderLoop = class {
|
|
|
4862
4880
|
ctx.save();
|
|
4863
4881
|
ctx.translate(this.camera.position.x, this.camera.position.y);
|
|
4864
4882
|
ctx.scale(this.camera.zoom, this.camera.zoom);
|
|
4865
|
-
const
|
|
4866
|
-
const
|
|
4883
|
+
const cullBounds = this.marginViewport.cachedWorldBounds();
|
|
4884
|
+
const cullPad = Math.max(cullBounds.w, cullBounds.h) * 0.05;
|
|
4867
4885
|
const cullingRect = {
|
|
4868
|
-
x:
|
|
4869
|
-
y:
|
|
4870
|
-
w:
|
|
4871
|
-
h:
|
|
4886
|
+
x: cullBounds.x - cullPad,
|
|
4887
|
+
y: cullBounds.y - cullPad,
|
|
4888
|
+
w: cullBounds.w + cullPad * 2,
|
|
4889
|
+
h: cullBounds.h + cullPad * 2
|
|
4872
4890
|
};
|
|
4873
4891
|
const allElements = this.store.getAll();
|
|
4874
4892
|
this.layerGroups.clear();
|
|
@@ -4905,13 +4923,13 @@ var RenderLoop = class {
|
|
|
4905
4923
|
const isActiveDrawingLayer = layerId === this.activeDrawingLayerId;
|
|
4906
4924
|
if (!this.layerCache.isDirty(layerId)) {
|
|
4907
4925
|
const compT0 = performance.now();
|
|
4908
|
-
this.compositeLayerCache(ctx, layerId
|
|
4926
|
+
this.compositeLayerCache(ctx, layerId);
|
|
4909
4927
|
compositeMs += performance.now() - compT0;
|
|
4910
4928
|
continue;
|
|
4911
4929
|
}
|
|
4912
4930
|
if (isActiveDrawingLayer) {
|
|
4913
4931
|
const compT0 = performance.now();
|
|
4914
|
-
this.compositeLayerCache(ctx, layerId
|
|
4932
|
+
this.compositeLayerCache(ctx, layerId);
|
|
4915
4933
|
compositeMs += performance.now() - compT0;
|
|
4916
4934
|
continue;
|
|
4917
4935
|
}
|
|
@@ -4921,9 +4939,7 @@ var RenderLoop = class {
|
|
|
4921
4939
|
const offCanvas = this.layerCache.getCanvas(layerId);
|
|
4922
4940
|
offCtx.clearRect(0, 0, offCanvas.width, offCanvas.height);
|
|
4923
4941
|
offCtx.save();
|
|
4924
|
-
|
|
4925
|
-
offCtx.translate(this.camera.position.x, this.camera.position.y);
|
|
4926
|
-
offCtx.scale(this.camera.zoom, this.camera.zoom);
|
|
4942
|
+
this.marginViewport.applyRenderTransform(offCtx);
|
|
4927
4943
|
for (const element of elements) {
|
|
4928
4944
|
const elBounds = getElementBounds(element);
|
|
4929
4945
|
if (elBounds && !boundsIntersect(elBounds, cullingRect)) continue;
|
|
@@ -4933,48 +4949,54 @@ var RenderLoop = class {
|
|
|
4933
4949
|
this.layerCache.markClean(layerId);
|
|
4934
4950
|
layersMs += performance.now() - layerT0;
|
|
4935
4951
|
const compT0 = performance.now();
|
|
4936
|
-
this.compositeLayerCache(ctx, layerId
|
|
4952
|
+
this.compositeLayerCache(ctx, layerId);
|
|
4937
4953
|
compositeMs += performance.now() - compT0;
|
|
4938
4954
|
}
|
|
4939
4955
|
}
|
|
4940
4956
|
if (gridElements.length > 0) {
|
|
4941
4957
|
const gridT0 = performance.now();
|
|
4942
4958
|
const gridRef = gridElements[0];
|
|
4943
|
-
const
|
|
4944
|
-
if (
|
|
4945
|
-
|
|
4946
|
-
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
4947
|
-
ctx.drawImage(this.gridCacheCanvas, 0, 0);
|
|
4948
|
-
ctx.restore();
|
|
4949
|
-
} else {
|
|
4950
|
-
this.ensureGridCache(cssWidth, cssHeight, dpr);
|
|
4959
|
+
const gridDirty = this.gridCacheDirty || gridRef !== this.lastGridRef;
|
|
4960
|
+
if (gridDirty) {
|
|
4961
|
+
this.ensureGridCache();
|
|
4951
4962
|
if (this.gridCacheCtx && this.gridCacheCanvas) {
|
|
4963
|
+
const cb = this.marginViewport.cachedWorldBounds();
|
|
4964
|
+
this.renderer.setGridBoundsOverride({
|
|
4965
|
+
minX: cb.x,
|
|
4966
|
+
minY: cb.y,
|
|
4967
|
+
maxX: cb.x + cb.w,
|
|
4968
|
+
maxY: cb.y + cb.h
|
|
4969
|
+
});
|
|
4952
4970
|
const gc = this.gridCacheCtx;
|
|
4953
4971
|
gc.clearRect(0, 0, this.gridCacheCanvas.width, this.gridCacheCanvas.height);
|
|
4954
4972
|
gc.save();
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
}
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
4964
|
-
ctx.drawImage(this.gridCacheCanvas, 0, 0);
|
|
4965
|
-
ctx.restore();
|
|
4966
|
-
} else {
|
|
4967
|
-
for (const grid of gridElements) {
|
|
4968
|
-
this.renderer.renderCanvasElement(ctx, grid);
|
|
4973
|
+
this.marginViewport.applyRenderTransform(gc);
|
|
4974
|
+
try {
|
|
4975
|
+
for (const grid of gridElements) {
|
|
4976
|
+
this.renderer.renderCanvasElement(gc, grid);
|
|
4977
|
+
}
|
|
4978
|
+
} finally {
|
|
4979
|
+
gc.restore();
|
|
4980
|
+
this.renderer.setGridBoundsOverride(null);
|
|
4969
4981
|
}
|
|
4970
4982
|
}
|
|
4971
|
-
this.
|
|
4972
|
-
this.gridCacheCamX = currentCamX;
|
|
4973
|
-
this.gridCacheCamY = currentCamY;
|
|
4974
|
-
this.gridCacheWidth = cssWidth;
|
|
4975
|
-
this.gridCacheHeight = cssHeight;
|
|
4983
|
+
this.gridCacheDirty = false;
|
|
4976
4984
|
this.lastGridRef = gridRef;
|
|
4977
4985
|
}
|
|
4986
|
+
if (this.gridCacheCanvas) {
|
|
4987
|
+
const offset = this.marginViewport.compositeOffset(
|
|
4988
|
+
this.camera.position.x,
|
|
4989
|
+
this.camera.position.y
|
|
4990
|
+
);
|
|
4991
|
+
ctx.save();
|
|
4992
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
4993
|
+
ctx.drawImage(this.gridCacheCanvas, offset.x, offset.y);
|
|
4994
|
+
ctx.restore();
|
|
4995
|
+
} else {
|
|
4996
|
+
for (const grid of gridElements) {
|
|
4997
|
+
this.renderer.renderCanvasElement(ctx, grid);
|
|
4998
|
+
}
|
|
4999
|
+
}
|
|
4978
5000
|
gridMs = performance.now() - gridT0;
|
|
4979
5001
|
}
|
|
4980
5002
|
const overlayT0 = performance.now();
|
|
@@ -5006,15 +5028,11 @@ function createOffscreenCanvas(width, height) {
|
|
|
5006
5028
|
return canvas;
|
|
5007
5029
|
}
|
|
5008
5030
|
var LayerCache = class {
|
|
5031
|
+
constructor(viewport) {
|
|
5032
|
+
this.viewport = viewport;
|
|
5033
|
+
}
|
|
5009
5034
|
canvases = /* @__PURE__ */ new Map();
|
|
5010
5035
|
dirtyFlags = /* @__PURE__ */ new Map();
|
|
5011
|
-
width;
|
|
5012
|
-
height;
|
|
5013
|
-
constructor(width, height) {
|
|
5014
|
-
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
5015
|
-
this.width = Math.round(width * dpr);
|
|
5016
|
-
this.height = Math.round(height * dpr);
|
|
5017
|
-
}
|
|
5018
5036
|
isDirty(layerId) {
|
|
5019
5037
|
return this.dirtyFlags.get(layerId) !== false;
|
|
5020
5038
|
}
|
|
@@ -5032,7 +5050,7 @@ var LayerCache = class {
|
|
|
5032
5050
|
getCanvas(layerId) {
|
|
5033
5051
|
let canvas = this.canvases.get(layerId);
|
|
5034
5052
|
if (!canvas) {
|
|
5035
|
-
canvas = createOffscreenCanvas(this.
|
|
5053
|
+
canvas = createOffscreenCanvas(this.viewport.physicalWidth(), this.viewport.physicalHeight());
|
|
5036
5054
|
this.canvases.set(layerId, canvas);
|
|
5037
5055
|
this.dirtyFlags.set(layerId, true);
|
|
5038
5056
|
}
|
|
@@ -5042,13 +5060,12 @@ var LayerCache = class {
|
|
|
5042
5060
|
const canvas = this.getCanvas(layerId);
|
|
5043
5061
|
return canvas.getContext("2d");
|
|
5044
5062
|
}
|
|
5045
|
-
resize(
|
|
5046
|
-
const
|
|
5047
|
-
|
|
5048
|
-
this.height = Math.round(height * dpr);
|
|
5063
|
+
resize() {
|
|
5064
|
+
const w = this.viewport.physicalWidth();
|
|
5065
|
+
const h = this.viewport.physicalHeight();
|
|
5049
5066
|
for (const [id, canvas] of this.canvases) {
|
|
5050
|
-
canvas.width =
|
|
5051
|
-
canvas.height =
|
|
5067
|
+
canvas.width = w;
|
|
5068
|
+
canvas.height = h;
|
|
5052
5069
|
this.dirtyFlags.set(id, true);
|
|
5053
5070
|
}
|
|
5054
5071
|
}
|
|
@@ -5058,6 +5075,75 @@ var LayerCache = class {
|
|
|
5058
5075
|
}
|
|
5059
5076
|
};
|
|
5060
5077
|
|
|
5078
|
+
// src/canvas/margin-viewport.ts
|
|
5079
|
+
var MarginViewport = class {
|
|
5080
|
+
constructor(marginPx) {
|
|
5081
|
+
this.marginPx = marginPx;
|
|
5082
|
+
}
|
|
5083
|
+
cssW = 0;
|
|
5084
|
+
cssH = 0;
|
|
5085
|
+
dpr = 1;
|
|
5086
|
+
anchorCamX = 0;
|
|
5087
|
+
anchorCamY = 0;
|
|
5088
|
+
anchorZoom = Number.NaN;
|
|
5089
|
+
// sentinel → first needsRecenter is true
|
|
5090
|
+
viewportDirty = true;
|
|
5091
|
+
setMargin(marginPx) {
|
|
5092
|
+
if (marginPx !== this.marginPx) {
|
|
5093
|
+
this.marginPx = marginPx;
|
|
5094
|
+
this.viewportDirty = true;
|
|
5095
|
+
}
|
|
5096
|
+
}
|
|
5097
|
+
setViewport(cssW, cssH, dpr) {
|
|
5098
|
+
if (cssW !== this.cssW || cssH !== this.cssH || dpr !== this.dpr) {
|
|
5099
|
+
this.cssW = cssW;
|
|
5100
|
+
this.cssH = cssH;
|
|
5101
|
+
this.dpr = dpr;
|
|
5102
|
+
this.viewportDirty = true;
|
|
5103
|
+
}
|
|
5104
|
+
}
|
|
5105
|
+
physicalWidth() {
|
|
5106
|
+
return Math.round((this.cssW + 2 * this.marginPx) * this.dpr);
|
|
5107
|
+
}
|
|
5108
|
+
physicalHeight() {
|
|
5109
|
+
return Math.round((this.cssH + 2 * this.marginPx) * this.dpr);
|
|
5110
|
+
}
|
|
5111
|
+
needsRecenter(camX, camY, zoom) {
|
|
5112
|
+
return this.viewportDirty || zoom !== this.anchorZoom || Math.abs(camX - this.anchorCamX) > this.marginPx || Math.abs(camY - this.anchorCamY) > this.marginPx;
|
|
5113
|
+
}
|
|
5114
|
+
recenter(camX, camY, zoom) {
|
|
5115
|
+
this.anchorCamX = camX;
|
|
5116
|
+
this.anchorCamY = camY;
|
|
5117
|
+
this.anchorZoom = zoom;
|
|
5118
|
+
this.viewportDirty = false;
|
|
5119
|
+
}
|
|
5120
|
+
/** Applies dpr scale + anchor-relative world transform. setViewport must have been called first. */
|
|
5121
|
+
applyRenderTransform(ctx) {
|
|
5122
|
+
ctx.scale(this.dpr, this.dpr);
|
|
5123
|
+
ctx.translate(this.marginPx + this.anchorCamX, this.marginPx + this.anchorCamY);
|
|
5124
|
+
ctx.scale(this.anchorZoom, this.anchorZoom);
|
|
5125
|
+
}
|
|
5126
|
+
// Device-px destination for drawImage(cache, x, y).
|
|
5127
|
+
// A world point P sits in the cache at CSS x `margin + anchorCamX + P*zoom`; it must land on
|
|
5128
|
+
// screen at `camX + P*zoom`; so the blit offset is `camX - anchorCamX - margin` (CSS) * dpr.
|
|
5129
|
+
compositeOffset(camX, camY) {
|
|
5130
|
+
return {
|
|
5131
|
+
x: (camX - this.anchorCamX - this.marginPx) * this.dpr,
|
|
5132
|
+
y: (camY - this.anchorCamY - this.marginPx) * this.dpr
|
|
5133
|
+
};
|
|
5134
|
+
}
|
|
5135
|
+
// World-space bounds of the whole cached region at the anchor (cull rect for re-renders).
|
|
5136
|
+
cachedWorldBounds() {
|
|
5137
|
+
const z = this.anchorZoom;
|
|
5138
|
+
return {
|
|
5139
|
+
x: (-this.marginPx - this.anchorCamX) / z,
|
|
5140
|
+
y: (-this.marginPx - this.anchorCamY) / z,
|
|
5141
|
+
w: (this.cssW + 2 * this.marginPx) / z,
|
|
5142
|
+
h: (this.cssH + 2 * this.marginPx) / z
|
|
5143
|
+
};
|
|
5144
|
+
}
|
|
5145
|
+
};
|
|
5146
|
+
|
|
5061
5147
|
// src/canvas/viewport.ts
|
|
5062
5148
|
var Viewport = class {
|
|
5063
5149
|
constructor(container, options = {}) {
|
|
@@ -5075,13 +5161,13 @@ var Viewport = class {
|
|
|
5075
5161
|
this.renderLoop.markAllLayersDirty();
|
|
5076
5162
|
this.requestRender();
|
|
5077
5163
|
});
|
|
5078
|
-
this.renderer.setOnImageError((src) => {
|
|
5164
|
+
this.renderer.setOnImageError((src, cause) => {
|
|
5079
5165
|
const elementIds = [];
|
|
5080
5166
|
for (const el of this.store.getAll()) {
|
|
5081
5167
|
if (el.type === "image" && el.src === src) elementIds.push(el.id);
|
|
5082
5168
|
}
|
|
5083
5169
|
if (options.onImageError) {
|
|
5084
|
-
options.onImageError({ src, elementIds });
|
|
5170
|
+
options.onImageError({ src, elementIds, cause });
|
|
5085
5171
|
} else {
|
|
5086
5172
|
console.warn(`[fieldnotes] image failed to load: ${src}`);
|
|
5087
5173
|
}
|
|
@@ -5134,10 +5220,13 @@ var Viewport = class {
|
|
|
5134
5220
|
this.interactMode = new InteractMode({
|
|
5135
5221
|
getNode: (id) => this.domNodeManager.getNode(id)
|
|
5136
5222
|
});
|
|
5137
|
-
|
|
5223
|
+
this.marginViewport = new MarginViewport(options.panBufferMargin ?? 256);
|
|
5224
|
+
this.marginViewport.setViewport(
|
|
5138
5225
|
this.canvasEl.clientWidth || 800,
|
|
5139
|
-
this.canvasEl.clientHeight || 600
|
|
5226
|
+
this.canvasEl.clientHeight || 600,
|
|
5227
|
+
typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1
|
|
5140
5228
|
);
|
|
5229
|
+
const layerCache = new LayerCache(this.marginViewport);
|
|
5141
5230
|
this.renderLoop = new RenderLoop({
|
|
5142
5231
|
canvasEl: this.canvasEl,
|
|
5143
5232
|
camera: this.camera,
|
|
@@ -5147,7 +5236,8 @@ var Viewport = class {
|
|
|
5147
5236
|
toolManager: this.toolManager,
|
|
5148
5237
|
layerManager: this.layerManager,
|
|
5149
5238
|
domNodeManager: this.domNodeManager,
|
|
5150
|
-
layerCache
|
|
5239
|
+
layerCache,
|
|
5240
|
+
marginViewport: this.marginViewport
|
|
5151
5241
|
});
|
|
5152
5242
|
this.unsubCamera = this.camera.onChange(() => {
|
|
5153
5243
|
this.applyCameraTransform();
|
|
@@ -5211,6 +5301,7 @@ var Viewport = class {
|
|
|
5211
5301
|
noteEditor;
|
|
5212
5302
|
historyRecorder;
|
|
5213
5303
|
toolContext;
|
|
5304
|
+
marginViewport;
|
|
5214
5305
|
resizeObserver = null;
|
|
5215
5306
|
_snapToGrid = false;
|
|
5216
5307
|
_gridSize;
|
|
@@ -5295,6 +5386,10 @@ var Viewport = class {
|
|
|
5295
5386
|
this.loadState(parseState(json));
|
|
5296
5387
|
}
|
|
5297
5388
|
setTool(name) {
|
|
5389
|
+
if (!this.toolManager.getTool(name)) {
|
|
5390
|
+
console.warn(`[fieldnotes] setTool: no tool registered as "${name}"`);
|
|
5391
|
+
return;
|
|
5392
|
+
}
|
|
5298
5393
|
this.toolManager.setTool(name, this.toolContext);
|
|
5299
5394
|
}
|
|
5300
5395
|
get shortcuts() {
|
|
@@ -5791,20 +5886,6 @@ var PencilTool = class {
|
|
|
5791
5886
|
};
|
|
5792
5887
|
|
|
5793
5888
|
// src/elements/stroke-hit.ts
|
|
5794
|
-
function distSqToSegment(p, a, b) {
|
|
5795
|
-
const abx = b.x - a.x;
|
|
5796
|
-
const aby = b.y - a.y;
|
|
5797
|
-
const apx = p.x - a.x;
|
|
5798
|
-
const apy = p.y - a.y;
|
|
5799
|
-
const lenSq = abx * abx + aby * aby;
|
|
5800
|
-
if (lenSq === 0) {
|
|
5801
|
-
return apx * apx + apy * apy;
|
|
5802
|
-
}
|
|
5803
|
-
const t = Math.max(0, Math.min(1, (apx * abx + apy * aby) / lenSq));
|
|
5804
|
-
const dx = p.x - (a.x + t * abx);
|
|
5805
|
-
const dy = p.y - (a.y + t * aby);
|
|
5806
|
-
return dx * dx + dy * dy;
|
|
5807
|
-
}
|
|
5808
5889
|
function hitTestStroke(stroke, point, radius) {
|
|
5809
5890
|
const bounds = getElementBounds(stroke);
|
|
5810
5891
|
if (!bounds) return false;
|
|
@@ -7401,7 +7482,7 @@ var TemplateTool = class {
|
|
|
7401
7482
|
};
|
|
7402
7483
|
|
|
7403
7484
|
// src/index.ts
|
|
7404
|
-
var VERSION = "0.
|
|
7485
|
+
var VERSION = "0.24.0";
|
|
7405
7486
|
export {
|
|
7406
7487
|
AddElementCommand,
|
|
7407
7488
|
ArrowTool,
|