@fieldnotes/core 0.22.0 → 0.23.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 +496 -481
- package/dist/index.cjs +171 -100
- 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 +171 -100
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -603,6 +603,8 @@ interface ViewportOptions {
|
|
|
603
603
|
src: string;
|
|
604
604
|
elementIds: string[];
|
|
605
605
|
}) => void;
|
|
606
|
+
/** CSS-pixel margin cached beyond the viewport. Default `256`. Set `0` to disable. */
|
|
607
|
+
panBufferMargin?: number;
|
|
606
608
|
}
|
|
607
609
|
declare class Viewport {
|
|
608
610
|
private readonly container;
|
|
@@ -622,6 +624,7 @@ declare class Viewport {
|
|
|
622
624
|
private readonly noteEditor;
|
|
623
625
|
private readonly historyRecorder;
|
|
624
626
|
readonly toolContext: ToolContext;
|
|
627
|
+
private readonly marginViewport;
|
|
625
628
|
private resizeObserver;
|
|
626
629
|
private _snapToGrid;
|
|
627
630
|
private readonly _gridSize;
|
|
@@ -708,11 +711,18 @@ declare class ElementRenderer {
|
|
|
708
711
|
private canvasSize;
|
|
709
712
|
private hexTileCache;
|
|
710
713
|
private hexTileCacheKey;
|
|
714
|
+
private gridBoundsOverride;
|
|
711
715
|
setStore(store: ElementStore): void;
|
|
712
716
|
setOnImageLoad(callback: () => void): void;
|
|
713
717
|
setOnImageError(callback: (src: string) => void): void;
|
|
714
718
|
setCamera(camera: Camera): void;
|
|
715
719
|
setCanvasSize(w: number, h: number): void;
|
|
720
|
+
setGridBoundsOverride(bounds: {
|
|
721
|
+
minX: number;
|
|
722
|
+
minY: number;
|
|
723
|
+
maxX: number;
|
|
724
|
+
maxY: number;
|
|
725
|
+
} | null): void;
|
|
716
726
|
isDomElement(element: CanvasElement): boolean;
|
|
717
727
|
renderCanvasElement(ctx: CanvasRenderingContext2D, element: CanvasElement): void;
|
|
718
728
|
private renderStroke;
|
|
@@ -1251,6 +1261,6 @@ declare class UpdateLayerCommand implements Command {
|
|
|
1251
1261
|
undo(_store: ElementStore): void;
|
|
1252
1262
|
}
|
|
1253
1263
|
|
|
1254
|
-
declare const VERSION = "0.
|
|
1264
|
+
declare const VERSION = "0.23.0";
|
|
1255
1265
|
|
|
1256
1266
|
export { type ActiveFormats, AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, CreateLayerCommand, DEFAULT_FONT_SIZE_PRESETS, DEFAULT_NOTE_FONT_SIZE, DoubleTapDetector, type DoubleTapDetectorOptions, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, type ExportImageOptions, type FilterAction, type FilteredEvent, type FilteredUpEvent, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputFilter, InputHandler, type InputHandlerOptions, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, NoteEditor, type NoteEditorOptions, type NoteElement, NoteTool, type NoteToolOptions, NoteToolbar, PencilTool, type PencilToolOptions, type Point, type PointerState, Quadtree, RemoveElementCommand, RemoveLayerCommand, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StrokeElement, type StrokePoint, type StyledRun, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, UpdateLayerCommand, VERSION, Viewport, type ViewportOptions, boundsIntersect, clearStaleBindings, createArrow, createGrid, createHtmlElement, createId, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportState, findBindTarget, findBoundArrows, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getEdgeIntersection, getElementBounds, getElementCenter, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isBindable, isNearBezier, isNoteContentEmpty, parseState, sanitizeNoteHtml, setFontSize, smartSnap, snapPoint, snapToHexCenter, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline, unbindArrow, updateBoundArrow };
|
package/dist/index.d.ts
CHANGED
|
@@ -603,6 +603,8 @@ interface ViewportOptions {
|
|
|
603
603
|
src: string;
|
|
604
604
|
elementIds: string[];
|
|
605
605
|
}) => void;
|
|
606
|
+
/** CSS-pixel margin cached beyond the viewport. Default `256`. Set `0` to disable. */
|
|
607
|
+
panBufferMargin?: number;
|
|
606
608
|
}
|
|
607
609
|
declare class Viewport {
|
|
608
610
|
private readonly container;
|
|
@@ -622,6 +624,7 @@ declare class Viewport {
|
|
|
622
624
|
private readonly noteEditor;
|
|
623
625
|
private readonly historyRecorder;
|
|
624
626
|
readonly toolContext: ToolContext;
|
|
627
|
+
private readonly marginViewport;
|
|
625
628
|
private resizeObserver;
|
|
626
629
|
private _snapToGrid;
|
|
627
630
|
private readonly _gridSize;
|
|
@@ -708,11 +711,18 @@ declare class ElementRenderer {
|
|
|
708
711
|
private canvasSize;
|
|
709
712
|
private hexTileCache;
|
|
710
713
|
private hexTileCacheKey;
|
|
714
|
+
private gridBoundsOverride;
|
|
711
715
|
setStore(store: ElementStore): void;
|
|
712
716
|
setOnImageLoad(callback: () => void): void;
|
|
713
717
|
setOnImageError(callback: (src: string) => void): void;
|
|
714
718
|
setCamera(camera: Camera): void;
|
|
715
719
|
setCanvasSize(w: number, h: number): void;
|
|
720
|
+
setGridBoundsOverride(bounds: {
|
|
721
|
+
minX: number;
|
|
722
|
+
minY: number;
|
|
723
|
+
maxX: number;
|
|
724
|
+
maxY: number;
|
|
725
|
+
} | null): void;
|
|
716
726
|
isDomElement(element: CanvasElement): boolean;
|
|
717
727
|
renderCanvasElement(ctx: CanvasRenderingContext2D, element: CanvasElement): void;
|
|
718
728
|
private renderStroke;
|
|
@@ -1251,6 +1261,6 @@ declare class UpdateLayerCommand implements Command {
|
|
|
1251
1261
|
undo(_store: ElementStore): void;
|
|
1252
1262
|
}
|
|
1253
1263
|
|
|
1254
|
-
declare const VERSION = "0.
|
|
1264
|
+
declare const VERSION = "0.23.0";
|
|
1255
1265
|
|
|
1256
1266
|
export { type ActiveFormats, AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, CreateLayerCommand, DEFAULT_FONT_SIZE_PRESETS, DEFAULT_NOTE_FONT_SIZE, DoubleTapDetector, type DoubleTapDetectorOptions, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, type ExportImageOptions, type FilterAction, type FilteredEvent, type FilteredUpEvent, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputFilter, InputHandler, type InputHandlerOptions, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, NoteEditor, type NoteEditorOptions, type NoteElement, NoteTool, type NoteToolOptions, NoteToolbar, PencilTool, type PencilToolOptions, type Point, type PointerState, Quadtree, RemoveElementCommand, RemoveLayerCommand, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StrokeElement, type StrokePoint, type StyledRun, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, UpdateLayerCommand, VERSION, Viewport, type ViewportOptions, boundsIntersect, clearStaleBindings, createArrow, createGrid, createHtmlElement, createId, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportState, findBindTarget, findBoundArrows, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getEdgeIntersection, getElementBounds, getElementCenter, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isBindable, isNearBezier, isNoteContentEmpty, parseState, sanitizeNoteHtml, setFontSize, smartSnap, snapPoint, snapToHexCenter, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline, unbindArrow, updateBoundArrow };
|
package/dist/index.js
CHANGED
|
@@ -2670,6 +2670,7 @@ var ElementRenderer = class {
|
|
|
2670
2670
|
canvasSize = null;
|
|
2671
2671
|
hexTileCache = null;
|
|
2672
2672
|
hexTileCacheKey = "";
|
|
2673
|
+
gridBoundsOverride = null;
|
|
2673
2674
|
setStore(store) {
|
|
2674
2675
|
this.store = store;
|
|
2675
2676
|
}
|
|
@@ -2685,6 +2686,9 @@ var ElementRenderer = class {
|
|
|
2685
2686
|
setCanvasSize(w, h) {
|
|
2686
2687
|
this.canvasSize = { w, h };
|
|
2687
2688
|
}
|
|
2689
|
+
setGridBoundsOverride(bounds) {
|
|
2690
|
+
this.gridBoundsOverride = bounds;
|
|
2691
|
+
}
|
|
2688
2692
|
isDomElement(element) {
|
|
2689
2693
|
return DOM_ELEMENT_TYPES.has(element.type);
|
|
2690
2694
|
}
|
|
@@ -2855,20 +2859,20 @@ var ElementRenderer = class {
|
|
|
2855
2859
|
}
|
|
2856
2860
|
}
|
|
2857
2861
|
renderGrid(ctx, grid) {
|
|
2858
|
-
|
|
2862
|
+
const canvasSize = this.canvasSize;
|
|
2863
|
+
if (!canvasSize) return;
|
|
2859
2864
|
const cam = this.camera;
|
|
2860
2865
|
if (!cam) return;
|
|
2861
|
-
const
|
|
2862
|
-
|
|
2863
|
-
x:
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
};
|
|
2866
|
+
const bounds = this.gridBoundsOverride ?? (() => {
|
|
2867
|
+
const topLeft = cam.screenToWorld({ x: 0, y: 0 });
|
|
2868
|
+
const bottomRight = cam.screenToWorld({ x: canvasSize.w, y: canvasSize.h });
|
|
2869
|
+
return {
|
|
2870
|
+
minX: topLeft.x,
|
|
2871
|
+
minY: topLeft.y,
|
|
2872
|
+
maxX: bottomRight.x,
|
|
2873
|
+
maxY: bottomRight.y
|
|
2874
|
+
};
|
|
2875
|
+
})();
|
|
2872
2876
|
if (grid.gridType === "hex") {
|
|
2873
2877
|
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
2874
2878
|
const scale = cam.zoom * dpr;
|
|
@@ -4727,19 +4731,14 @@ var RenderLoop = class {
|
|
|
4727
4731
|
layerManager;
|
|
4728
4732
|
domNodeManager;
|
|
4729
4733
|
layerCache;
|
|
4734
|
+
marginViewport;
|
|
4730
4735
|
activeDrawingLayerId = null;
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
lastCamY;
|
|
4736
|
+
gridCacheDirty = true;
|
|
4737
|
+
// set on recenter/viewport-change; consumed by the grid block
|
|
4734
4738
|
stats = new RenderStats();
|
|
4735
4739
|
layerGroups = /* @__PURE__ */ new Map();
|
|
4736
4740
|
gridCacheCanvas = null;
|
|
4737
4741
|
gridCacheCtx = null;
|
|
4738
|
-
gridCacheZoom = -1;
|
|
4739
|
-
gridCacheCamX = -Infinity;
|
|
4740
|
-
gridCacheCamY = -Infinity;
|
|
4741
|
-
gridCacheWidth = 0;
|
|
4742
|
-
gridCacheHeight = 0;
|
|
4743
4742
|
lastGridRef = null;
|
|
4744
4743
|
constructor(deps) {
|
|
4745
4744
|
this.canvasEl = deps.canvasEl;
|
|
@@ -4751,9 +4750,7 @@ var RenderLoop = class {
|
|
|
4751
4750
|
this.layerManager = deps.layerManager;
|
|
4752
4751
|
this.domNodeManager = deps.domNodeManager;
|
|
4753
4752
|
this.layerCache = deps.layerCache;
|
|
4754
|
-
this.
|
|
4755
|
-
this.lastCamX = deps.camera.position.x;
|
|
4756
|
-
this.lastCamY = deps.camera.position.y;
|
|
4753
|
+
this.marginViewport = deps.marginViewport;
|
|
4757
4754
|
}
|
|
4758
4755
|
requestRender() {
|
|
4759
4756
|
this.needsRender = true;
|
|
@@ -4780,7 +4777,9 @@ var RenderLoop = class {
|
|
|
4780
4777
|
setCanvasSize(width, height) {
|
|
4781
4778
|
this.canvasEl.width = width;
|
|
4782
4779
|
this.canvasEl.height = height;
|
|
4783
|
-
|
|
4780
|
+
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
4781
|
+
this.marginViewport.setViewport(width / dpr, height / dpr, dpr);
|
|
4782
|
+
this.layerCache.resize();
|
|
4784
4783
|
}
|
|
4785
4784
|
setActiveDrawingLayer(layerId) {
|
|
4786
4785
|
this.activeDrawingLayerId = layerId;
|
|
@@ -4794,30 +4793,29 @@ var RenderLoop = class {
|
|
|
4794
4793
|
getStats() {
|
|
4795
4794
|
return this.stats.getSnapshot();
|
|
4796
4795
|
}
|
|
4797
|
-
compositeLayerCache(ctx, layerId
|
|
4796
|
+
compositeLayerCache(ctx, layerId) {
|
|
4798
4797
|
const cached = this.layerCache.getCanvas(layerId);
|
|
4798
|
+
const offset = this.marginViewport.compositeOffset(
|
|
4799
|
+
this.camera.position.x,
|
|
4800
|
+
this.camera.position.y
|
|
4801
|
+
);
|
|
4799
4802
|
ctx.save();
|
|
4800
|
-
ctx.
|
|
4801
|
-
ctx.
|
|
4802
|
-
ctx.scale(1 / dpr, 1 / dpr);
|
|
4803
|
-
ctx.drawImage(cached, 0, 0);
|
|
4803
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
4804
|
+
ctx.drawImage(cached, offset.x, offset.y);
|
|
4804
4805
|
ctx.restore();
|
|
4805
4806
|
}
|
|
4806
|
-
ensureGridCache(
|
|
4807
|
-
|
|
4807
|
+
ensureGridCache() {
|
|
4808
|
+
const w = this.marginViewport.physicalWidth();
|
|
4809
|
+
const h = this.marginViewport.physicalHeight();
|
|
4810
|
+
if (this.gridCacheCanvas !== null && this.gridCacheCanvas.width === w && this.gridCacheCanvas.height === h) {
|
|
4808
4811
|
return;
|
|
4809
4812
|
}
|
|
4810
|
-
const physWidth = Math.round(cssWidth * dpr);
|
|
4811
|
-
const physHeight = Math.round(cssHeight * dpr);
|
|
4812
4813
|
if (typeof OffscreenCanvas !== "undefined") {
|
|
4813
|
-
this.gridCacheCanvas = new OffscreenCanvas(
|
|
4814
|
-
physWidth,
|
|
4815
|
-
physHeight
|
|
4816
|
-
);
|
|
4814
|
+
this.gridCacheCanvas = new OffscreenCanvas(w, h);
|
|
4817
4815
|
} else if (typeof document !== "undefined") {
|
|
4818
4816
|
const el = document.createElement("canvas");
|
|
4819
|
-
el.width =
|
|
4820
|
-
el.height =
|
|
4817
|
+
el.width = w;
|
|
4818
|
+
el.height = h;
|
|
4821
4819
|
this.gridCacheCanvas = el;
|
|
4822
4820
|
} else {
|
|
4823
4821
|
this.gridCacheCanvas = null;
|
|
@@ -4836,14 +4834,14 @@ var RenderLoop = class {
|
|
|
4836
4834
|
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
4837
4835
|
const cssWidth = this.canvasEl.clientWidth;
|
|
4838
4836
|
const cssHeight = this.canvasEl.clientHeight;
|
|
4837
|
+
this.marginViewport.setViewport(cssWidth, cssHeight, dpr);
|
|
4839
4838
|
const currentZoom = this.camera.zoom;
|
|
4840
4839
|
const currentCamX = this.camera.position.x;
|
|
4841
4840
|
const currentCamY = this.camera.position.y;
|
|
4842
|
-
if (
|
|
4841
|
+
if (this.marginViewport.needsRecenter(currentCamX, currentCamY, currentZoom)) {
|
|
4842
|
+
this.marginViewport.recenter(currentCamX, currentCamY, currentZoom);
|
|
4843
4843
|
this.layerCache.markAllDirty();
|
|
4844
|
-
this.
|
|
4845
|
-
this.lastCamX = currentCamX;
|
|
4846
|
-
this.lastCamY = currentCamY;
|
|
4844
|
+
this.gridCacheDirty = true;
|
|
4847
4845
|
}
|
|
4848
4846
|
ctx.save();
|
|
4849
4847
|
ctx.scale(dpr, dpr);
|
|
@@ -4862,13 +4860,13 @@ var RenderLoop = class {
|
|
|
4862
4860
|
ctx.save();
|
|
4863
4861
|
ctx.translate(this.camera.position.x, this.camera.position.y);
|
|
4864
4862
|
ctx.scale(this.camera.zoom, this.camera.zoom);
|
|
4865
|
-
const
|
|
4866
|
-
const
|
|
4863
|
+
const cullBounds = this.marginViewport.cachedWorldBounds();
|
|
4864
|
+
const cullPad = Math.max(cullBounds.w, cullBounds.h) * 0.05;
|
|
4867
4865
|
const cullingRect = {
|
|
4868
|
-
x:
|
|
4869
|
-
y:
|
|
4870
|
-
w:
|
|
4871
|
-
h:
|
|
4866
|
+
x: cullBounds.x - cullPad,
|
|
4867
|
+
y: cullBounds.y - cullPad,
|
|
4868
|
+
w: cullBounds.w + cullPad * 2,
|
|
4869
|
+
h: cullBounds.h + cullPad * 2
|
|
4872
4870
|
};
|
|
4873
4871
|
const allElements = this.store.getAll();
|
|
4874
4872
|
this.layerGroups.clear();
|
|
@@ -4905,13 +4903,13 @@ var RenderLoop = class {
|
|
|
4905
4903
|
const isActiveDrawingLayer = layerId === this.activeDrawingLayerId;
|
|
4906
4904
|
if (!this.layerCache.isDirty(layerId)) {
|
|
4907
4905
|
const compT0 = performance.now();
|
|
4908
|
-
this.compositeLayerCache(ctx, layerId
|
|
4906
|
+
this.compositeLayerCache(ctx, layerId);
|
|
4909
4907
|
compositeMs += performance.now() - compT0;
|
|
4910
4908
|
continue;
|
|
4911
4909
|
}
|
|
4912
4910
|
if (isActiveDrawingLayer) {
|
|
4913
4911
|
const compT0 = performance.now();
|
|
4914
|
-
this.compositeLayerCache(ctx, layerId
|
|
4912
|
+
this.compositeLayerCache(ctx, layerId);
|
|
4915
4913
|
compositeMs += performance.now() - compT0;
|
|
4916
4914
|
continue;
|
|
4917
4915
|
}
|
|
@@ -4921,9 +4919,7 @@ var RenderLoop = class {
|
|
|
4921
4919
|
const offCanvas = this.layerCache.getCanvas(layerId);
|
|
4922
4920
|
offCtx.clearRect(0, 0, offCanvas.width, offCanvas.height);
|
|
4923
4921
|
offCtx.save();
|
|
4924
|
-
|
|
4925
|
-
offCtx.translate(this.camera.position.x, this.camera.position.y);
|
|
4926
|
-
offCtx.scale(this.camera.zoom, this.camera.zoom);
|
|
4922
|
+
this.marginViewport.applyRenderTransform(offCtx);
|
|
4927
4923
|
for (const element of elements) {
|
|
4928
4924
|
const elBounds = getElementBounds(element);
|
|
4929
4925
|
if (elBounds && !boundsIntersect(elBounds, cullingRect)) continue;
|
|
@@ -4933,48 +4929,54 @@ var RenderLoop = class {
|
|
|
4933
4929
|
this.layerCache.markClean(layerId);
|
|
4934
4930
|
layersMs += performance.now() - layerT0;
|
|
4935
4931
|
const compT0 = performance.now();
|
|
4936
|
-
this.compositeLayerCache(ctx, layerId
|
|
4932
|
+
this.compositeLayerCache(ctx, layerId);
|
|
4937
4933
|
compositeMs += performance.now() - compT0;
|
|
4938
4934
|
}
|
|
4939
4935
|
}
|
|
4940
4936
|
if (gridElements.length > 0) {
|
|
4941
4937
|
const gridT0 = performance.now();
|
|
4942
4938
|
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);
|
|
4939
|
+
const gridDirty = this.gridCacheDirty || gridRef !== this.lastGridRef;
|
|
4940
|
+
if (gridDirty) {
|
|
4941
|
+
this.ensureGridCache();
|
|
4951
4942
|
if (this.gridCacheCtx && this.gridCacheCanvas) {
|
|
4943
|
+
const cb = this.marginViewport.cachedWorldBounds();
|
|
4944
|
+
this.renderer.setGridBoundsOverride({
|
|
4945
|
+
minX: cb.x,
|
|
4946
|
+
minY: cb.y,
|
|
4947
|
+
maxX: cb.x + cb.w,
|
|
4948
|
+
maxY: cb.y + cb.h
|
|
4949
|
+
});
|
|
4952
4950
|
const gc = this.gridCacheCtx;
|
|
4953
4951
|
gc.clearRect(0, 0, this.gridCacheCanvas.width, this.gridCacheCanvas.height);
|
|
4954
4952
|
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);
|
|
4953
|
+
this.marginViewport.applyRenderTransform(gc);
|
|
4954
|
+
try {
|
|
4955
|
+
for (const grid of gridElements) {
|
|
4956
|
+
this.renderer.renderCanvasElement(gc, grid);
|
|
4957
|
+
}
|
|
4958
|
+
} finally {
|
|
4959
|
+
gc.restore();
|
|
4960
|
+
this.renderer.setGridBoundsOverride(null);
|
|
4969
4961
|
}
|
|
4970
4962
|
}
|
|
4971
|
-
this.
|
|
4972
|
-
this.gridCacheCamX = currentCamX;
|
|
4973
|
-
this.gridCacheCamY = currentCamY;
|
|
4974
|
-
this.gridCacheWidth = cssWidth;
|
|
4975
|
-
this.gridCacheHeight = cssHeight;
|
|
4963
|
+
this.gridCacheDirty = false;
|
|
4976
4964
|
this.lastGridRef = gridRef;
|
|
4977
4965
|
}
|
|
4966
|
+
if (this.gridCacheCanvas) {
|
|
4967
|
+
const offset = this.marginViewport.compositeOffset(
|
|
4968
|
+
this.camera.position.x,
|
|
4969
|
+
this.camera.position.y
|
|
4970
|
+
);
|
|
4971
|
+
ctx.save();
|
|
4972
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
4973
|
+
ctx.drawImage(this.gridCacheCanvas, offset.x, offset.y);
|
|
4974
|
+
ctx.restore();
|
|
4975
|
+
} else {
|
|
4976
|
+
for (const grid of gridElements) {
|
|
4977
|
+
this.renderer.renderCanvasElement(ctx, grid);
|
|
4978
|
+
}
|
|
4979
|
+
}
|
|
4978
4980
|
gridMs = performance.now() - gridT0;
|
|
4979
4981
|
}
|
|
4980
4982
|
const overlayT0 = performance.now();
|
|
@@ -5006,15 +5008,11 @@ function createOffscreenCanvas(width, height) {
|
|
|
5006
5008
|
return canvas;
|
|
5007
5009
|
}
|
|
5008
5010
|
var LayerCache = class {
|
|
5011
|
+
constructor(viewport) {
|
|
5012
|
+
this.viewport = viewport;
|
|
5013
|
+
}
|
|
5009
5014
|
canvases = /* @__PURE__ */ new Map();
|
|
5010
5015
|
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
5016
|
isDirty(layerId) {
|
|
5019
5017
|
return this.dirtyFlags.get(layerId) !== false;
|
|
5020
5018
|
}
|
|
@@ -5032,7 +5030,7 @@ var LayerCache = class {
|
|
|
5032
5030
|
getCanvas(layerId) {
|
|
5033
5031
|
let canvas = this.canvases.get(layerId);
|
|
5034
5032
|
if (!canvas) {
|
|
5035
|
-
canvas = createOffscreenCanvas(this.
|
|
5033
|
+
canvas = createOffscreenCanvas(this.viewport.physicalWidth(), this.viewport.physicalHeight());
|
|
5036
5034
|
this.canvases.set(layerId, canvas);
|
|
5037
5035
|
this.dirtyFlags.set(layerId, true);
|
|
5038
5036
|
}
|
|
@@ -5042,13 +5040,12 @@ var LayerCache = class {
|
|
|
5042
5040
|
const canvas = this.getCanvas(layerId);
|
|
5043
5041
|
return canvas.getContext("2d");
|
|
5044
5042
|
}
|
|
5045
|
-
resize(
|
|
5046
|
-
const
|
|
5047
|
-
|
|
5048
|
-
this.height = Math.round(height * dpr);
|
|
5043
|
+
resize() {
|
|
5044
|
+
const w = this.viewport.physicalWidth();
|
|
5045
|
+
const h = this.viewport.physicalHeight();
|
|
5049
5046
|
for (const [id, canvas] of this.canvases) {
|
|
5050
|
-
canvas.width =
|
|
5051
|
-
canvas.height =
|
|
5047
|
+
canvas.width = w;
|
|
5048
|
+
canvas.height = h;
|
|
5052
5049
|
this.dirtyFlags.set(id, true);
|
|
5053
5050
|
}
|
|
5054
5051
|
}
|
|
@@ -5058,6 +5055,75 @@ var LayerCache = class {
|
|
|
5058
5055
|
}
|
|
5059
5056
|
};
|
|
5060
5057
|
|
|
5058
|
+
// src/canvas/margin-viewport.ts
|
|
5059
|
+
var MarginViewport = class {
|
|
5060
|
+
constructor(marginPx) {
|
|
5061
|
+
this.marginPx = marginPx;
|
|
5062
|
+
}
|
|
5063
|
+
cssW = 0;
|
|
5064
|
+
cssH = 0;
|
|
5065
|
+
dpr = 1;
|
|
5066
|
+
anchorCamX = 0;
|
|
5067
|
+
anchorCamY = 0;
|
|
5068
|
+
anchorZoom = Number.NaN;
|
|
5069
|
+
// sentinel → first needsRecenter is true
|
|
5070
|
+
viewportDirty = true;
|
|
5071
|
+
setMargin(marginPx) {
|
|
5072
|
+
if (marginPx !== this.marginPx) {
|
|
5073
|
+
this.marginPx = marginPx;
|
|
5074
|
+
this.viewportDirty = true;
|
|
5075
|
+
}
|
|
5076
|
+
}
|
|
5077
|
+
setViewport(cssW, cssH, dpr) {
|
|
5078
|
+
if (cssW !== this.cssW || cssH !== this.cssH || dpr !== this.dpr) {
|
|
5079
|
+
this.cssW = cssW;
|
|
5080
|
+
this.cssH = cssH;
|
|
5081
|
+
this.dpr = dpr;
|
|
5082
|
+
this.viewportDirty = true;
|
|
5083
|
+
}
|
|
5084
|
+
}
|
|
5085
|
+
physicalWidth() {
|
|
5086
|
+
return Math.round((this.cssW + 2 * this.marginPx) * this.dpr);
|
|
5087
|
+
}
|
|
5088
|
+
physicalHeight() {
|
|
5089
|
+
return Math.round((this.cssH + 2 * this.marginPx) * this.dpr);
|
|
5090
|
+
}
|
|
5091
|
+
needsRecenter(camX, camY, zoom) {
|
|
5092
|
+
return this.viewportDirty || zoom !== this.anchorZoom || Math.abs(camX - this.anchorCamX) > this.marginPx || Math.abs(camY - this.anchorCamY) > this.marginPx;
|
|
5093
|
+
}
|
|
5094
|
+
recenter(camX, camY, zoom) {
|
|
5095
|
+
this.anchorCamX = camX;
|
|
5096
|
+
this.anchorCamY = camY;
|
|
5097
|
+
this.anchorZoom = zoom;
|
|
5098
|
+
this.viewportDirty = false;
|
|
5099
|
+
}
|
|
5100
|
+
/** Applies dpr scale + anchor-relative world transform. setViewport must have been called first. */
|
|
5101
|
+
applyRenderTransform(ctx) {
|
|
5102
|
+
ctx.scale(this.dpr, this.dpr);
|
|
5103
|
+
ctx.translate(this.marginPx + this.anchorCamX, this.marginPx + this.anchorCamY);
|
|
5104
|
+
ctx.scale(this.anchorZoom, this.anchorZoom);
|
|
5105
|
+
}
|
|
5106
|
+
// Device-px destination for drawImage(cache, x, y).
|
|
5107
|
+
// A world point P sits in the cache at CSS x `margin + anchorCamX + P*zoom`; it must land on
|
|
5108
|
+
// screen at `camX + P*zoom`; so the blit offset is `camX - anchorCamX - margin` (CSS) * dpr.
|
|
5109
|
+
compositeOffset(camX, camY) {
|
|
5110
|
+
return {
|
|
5111
|
+
x: (camX - this.anchorCamX - this.marginPx) * this.dpr,
|
|
5112
|
+
y: (camY - this.anchorCamY - this.marginPx) * this.dpr
|
|
5113
|
+
};
|
|
5114
|
+
}
|
|
5115
|
+
// World-space bounds of the whole cached region at the anchor (cull rect for re-renders).
|
|
5116
|
+
cachedWorldBounds() {
|
|
5117
|
+
const z = this.anchorZoom;
|
|
5118
|
+
return {
|
|
5119
|
+
x: (-this.marginPx - this.anchorCamX) / z,
|
|
5120
|
+
y: (-this.marginPx - this.anchorCamY) / z,
|
|
5121
|
+
w: (this.cssW + 2 * this.marginPx) / z,
|
|
5122
|
+
h: (this.cssH + 2 * this.marginPx) / z
|
|
5123
|
+
};
|
|
5124
|
+
}
|
|
5125
|
+
};
|
|
5126
|
+
|
|
5061
5127
|
// src/canvas/viewport.ts
|
|
5062
5128
|
var Viewport = class {
|
|
5063
5129
|
constructor(container, options = {}) {
|
|
@@ -5134,10 +5200,13 @@ var Viewport = class {
|
|
|
5134
5200
|
this.interactMode = new InteractMode({
|
|
5135
5201
|
getNode: (id) => this.domNodeManager.getNode(id)
|
|
5136
5202
|
});
|
|
5137
|
-
|
|
5203
|
+
this.marginViewport = new MarginViewport(options.panBufferMargin ?? 256);
|
|
5204
|
+
this.marginViewport.setViewport(
|
|
5138
5205
|
this.canvasEl.clientWidth || 800,
|
|
5139
|
-
this.canvasEl.clientHeight || 600
|
|
5206
|
+
this.canvasEl.clientHeight || 600,
|
|
5207
|
+
typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1
|
|
5140
5208
|
);
|
|
5209
|
+
const layerCache = new LayerCache(this.marginViewport);
|
|
5141
5210
|
this.renderLoop = new RenderLoop({
|
|
5142
5211
|
canvasEl: this.canvasEl,
|
|
5143
5212
|
camera: this.camera,
|
|
@@ -5147,7 +5216,8 @@ var Viewport = class {
|
|
|
5147
5216
|
toolManager: this.toolManager,
|
|
5148
5217
|
layerManager: this.layerManager,
|
|
5149
5218
|
domNodeManager: this.domNodeManager,
|
|
5150
|
-
layerCache
|
|
5219
|
+
layerCache,
|
|
5220
|
+
marginViewport: this.marginViewport
|
|
5151
5221
|
});
|
|
5152
5222
|
this.unsubCamera = this.camera.onChange(() => {
|
|
5153
5223
|
this.applyCameraTransform();
|
|
@@ -5211,6 +5281,7 @@ var Viewport = class {
|
|
|
5211
5281
|
noteEditor;
|
|
5212
5282
|
historyRecorder;
|
|
5213
5283
|
toolContext;
|
|
5284
|
+
marginViewport;
|
|
5214
5285
|
resizeObserver = null;
|
|
5215
5286
|
_snapToGrid = false;
|
|
5216
5287
|
_gridSize;
|
|
@@ -7401,7 +7472,7 @@ var TemplateTool = class {
|
|
|
7401
7472
|
};
|
|
7402
7473
|
|
|
7403
7474
|
// src/index.ts
|
|
7404
|
-
var VERSION = "0.
|
|
7475
|
+
var VERSION = "0.23.0";
|
|
7405
7476
|
export {
|
|
7406
7477
|
AddElementCommand,
|
|
7407
7478
|
ArrowTool,
|