@fieldnotes/core 0.31.1 → 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 +25 -0
- package/dist/index.cjs +243 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -2
- package/dist/index.d.ts +17 -2
- package/dist/index.js +243 -36
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -200,6 +200,8 @@ interface ToolContext {
|
|
|
200
200
|
activeLayerId?: string;
|
|
201
201
|
isLayerVisible?: (layerId: string) => boolean;
|
|
202
202
|
isLayerLocked?: (layerId: string) => boolean;
|
|
203
|
+
smartGuides?: boolean;
|
|
204
|
+
getVisibleRect?: () => Bounds;
|
|
203
205
|
}
|
|
204
206
|
interface PointerState {
|
|
205
207
|
x: number;
|
|
@@ -410,6 +412,8 @@ interface ElementStyle {
|
|
|
410
412
|
declare function styleToPatch(element: CanvasElement, style: ElementStyle): Partial<CanvasElement>;
|
|
411
413
|
declare function getElementStyle(element: CanvasElement): ElementStyle;
|
|
412
414
|
|
|
415
|
+
type AlignEdge = 'left' | 'center-x' | 'right' | 'top' | 'middle' | 'bottom';
|
|
416
|
+
type DistributeAxis = 'horizontal' | 'vertical';
|
|
413
417
|
interface GridInfo {
|
|
414
418
|
gridType: 'square' | 'hex';
|
|
415
419
|
hexOrientation: 'pointy' | 'flat';
|
|
@@ -458,6 +462,7 @@ declare class Viewport {
|
|
|
458
462
|
private readonly marginViewport;
|
|
459
463
|
private resizeObserver;
|
|
460
464
|
private _snapToGrid;
|
|
465
|
+
private _smartGuides;
|
|
461
466
|
private readonly _gridSize;
|
|
462
467
|
private readonly renderLoop;
|
|
463
468
|
private readonly domNodeManager;
|
|
@@ -472,6 +477,8 @@ declare class Viewport {
|
|
|
472
477
|
get ctx(): CanvasRenderingContext2D | null;
|
|
473
478
|
get snapToGrid(): boolean;
|
|
474
479
|
setSnapToGrid(enabled: boolean): void;
|
|
480
|
+
get smartGuides(): boolean;
|
|
481
|
+
setSmartGuides(enabled: boolean): void;
|
|
475
482
|
fitToContent(padding?: number): void;
|
|
476
483
|
requestRender(): void;
|
|
477
484
|
exportState(): CanvasState;
|
|
@@ -516,6 +523,10 @@ declare class Viewport {
|
|
|
516
523
|
onSelectionChange(listener: () => void): () => void;
|
|
517
524
|
getSelectionStyle(): ElementStyle | null;
|
|
518
525
|
applyStyleToSelection(style: ElementStyle): void;
|
|
526
|
+
alignSelection(edge: AlignEdge): void;
|
|
527
|
+
distributeSelection(axis: DistributeAxis): void;
|
|
528
|
+
private boundedSelection;
|
|
529
|
+
private isMovable;
|
|
519
530
|
getRenderStats(): RenderStatsSnapshot;
|
|
520
531
|
logPerformance(intervalMs?: number): () => void;
|
|
521
532
|
destroy(): void;
|
|
@@ -743,6 +754,9 @@ declare class SelectTool implements Tool {
|
|
|
743
754
|
private ctx;
|
|
744
755
|
private pendingSingleSelectId;
|
|
745
756
|
private hasDragged;
|
|
757
|
+
private activeGuides;
|
|
758
|
+
private dragSnapTargets;
|
|
759
|
+
private dragVisibleRect;
|
|
746
760
|
private resizeAspectRatio;
|
|
747
761
|
private hoveredId;
|
|
748
762
|
get selectedIds(): string[];
|
|
@@ -758,6 +772,7 @@ declare class SelectTool implements Tool {
|
|
|
758
772
|
onPointerUp(_state: PointerState, ctx: ToolContext): void;
|
|
759
773
|
onHover(state: PointerState, ctx: ToolContext): void;
|
|
760
774
|
renderOverlay(canvasCtx: CanvasRenderingContext2D): void;
|
|
775
|
+
private renderGuideLines;
|
|
761
776
|
private updateArrowsBoundTo;
|
|
762
777
|
nudgeSelection(dx: number, dy: number, ctx: ToolContext): boolean;
|
|
763
778
|
private updateHoverCursor;
|
|
@@ -973,6 +988,6 @@ declare class TemplateTool implements Tool {
|
|
|
973
988
|
private notifyOptionsChange;
|
|
974
989
|
}
|
|
975
990
|
|
|
976
|
-
declare const VERSION = "0.
|
|
991
|
+
declare const VERSION = "0.33.0";
|
|
977
992
|
|
|
978
|
-
export { type ActiveFormats, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportImageOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
|
|
993
|
+
export { type ActiveFormats, type AlignEdge, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DistributeAxis, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportImageOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
|
package/dist/index.d.ts
CHANGED
|
@@ -200,6 +200,8 @@ interface ToolContext {
|
|
|
200
200
|
activeLayerId?: string;
|
|
201
201
|
isLayerVisible?: (layerId: string) => boolean;
|
|
202
202
|
isLayerLocked?: (layerId: string) => boolean;
|
|
203
|
+
smartGuides?: boolean;
|
|
204
|
+
getVisibleRect?: () => Bounds;
|
|
203
205
|
}
|
|
204
206
|
interface PointerState {
|
|
205
207
|
x: number;
|
|
@@ -410,6 +412,8 @@ interface ElementStyle {
|
|
|
410
412
|
declare function styleToPatch(element: CanvasElement, style: ElementStyle): Partial<CanvasElement>;
|
|
411
413
|
declare function getElementStyle(element: CanvasElement): ElementStyle;
|
|
412
414
|
|
|
415
|
+
type AlignEdge = 'left' | 'center-x' | 'right' | 'top' | 'middle' | 'bottom';
|
|
416
|
+
type DistributeAxis = 'horizontal' | 'vertical';
|
|
413
417
|
interface GridInfo {
|
|
414
418
|
gridType: 'square' | 'hex';
|
|
415
419
|
hexOrientation: 'pointy' | 'flat';
|
|
@@ -458,6 +462,7 @@ declare class Viewport {
|
|
|
458
462
|
private readonly marginViewport;
|
|
459
463
|
private resizeObserver;
|
|
460
464
|
private _snapToGrid;
|
|
465
|
+
private _smartGuides;
|
|
461
466
|
private readonly _gridSize;
|
|
462
467
|
private readonly renderLoop;
|
|
463
468
|
private readonly domNodeManager;
|
|
@@ -472,6 +477,8 @@ declare class Viewport {
|
|
|
472
477
|
get ctx(): CanvasRenderingContext2D | null;
|
|
473
478
|
get snapToGrid(): boolean;
|
|
474
479
|
setSnapToGrid(enabled: boolean): void;
|
|
480
|
+
get smartGuides(): boolean;
|
|
481
|
+
setSmartGuides(enabled: boolean): void;
|
|
475
482
|
fitToContent(padding?: number): void;
|
|
476
483
|
requestRender(): void;
|
|
477
484
|
exportState(): CanvasState;
|
|
@@ -516,6 +523,10 @@ declare class Viewport {
|
|
|
516
523
|
onSelectionChange(listener: () => void): () => void;
|
|
517
524
|
getSelectionStyle(): ElementStyle | null;
|
|
518
525
|
applyStyleToSelection(style: ElementStyle): void;
|
|
526
|
+
alignSelection(edge: AlignEdge): void;
|
|
527
|
+
distributeSelection(axis: DistributeAxis): void;
|
|
528
|
+
private boundedSelection;
|
|
529
|
+
private isMovable;
|
|
519
530
|
getRenderStats(): RenderStatsSnapshot;
|
|
520
531
|
logPerformance(intervalMs?: number): () => void;
|
|
521
532
|
destroy(): void;
|
|
@@ -743,6 +754,9 @@ declare class SelectTool implements Tool {
|
|
|
743
754
|
private ctx;
|
|
744
755
|
private pendingSingleSelectId;
|
|
745
756
|
private hasDragged;
|
|
757
|
+
private activeGuides;
|
|
758
|
+
private dragSnapTargets;
|
|
759
|
+
private dragVisibleRect;
|
|
746
760
|
private resizeAspectRatio;
|
|
747
761
|
private hoveredId;
|
|
748
762
|
get selectedIds(): string[];
|
|
@@ -758,6 +772,7 @@ declare class SelectTool implements Tool {
|
|
|
758
772
|
onPointerUp(_state: PointerState, ctx: ToolContext): void;
|
|
759
773
|
onHover(state: PointerState, ctx: ToolContext): void;
|
|
760
774
|
renderOverlay(canvasCtx: CanvasRenderingContext2D): void;
|
|
775
|
+
private renderGuideLines;
|
|
761
776
|
private updateArrowsBoundTo;
|
|
762
777
|
nudgeSelection(dx: number, dy: number, ctx: ToolContext): boolean;
|
|
763
778
|
private updateHoverCursor;
|
|
@@ -973,6 +988,6 @@ declare class TemplateTool implements Tool {
|
|
|
973
988
|
private notifyOptionsChange;
|
|
974
989
|
}
|
|
975
990
|
|
|
976
|
-
declare const VERSION = "0.
|
|
991
|
+
declare const VERSION = "0.33.0";
|
|
977
992
|
|
|
978
|
-
export { type ActiveFormats, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportImageOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
|
|
993
|
+
export { type ActiveFormats, type AlignEdge, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DistributeAxis, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportImageOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
|
package/dist/index.js
CHANGED
|
@@ -2270,6 +2270,23 @@ function distToBounds(point, bounds) {
|
|
|
2270
2270
|
function findBoundArrows(elementId, store) {
|
|
2271
2271
|
return store.getElementsByType("arrow").filter((a) => a.fromBinding?.elementId === elementId || a.toBinding?.elementId === elementId);
|
|
2272
2272
|
}
|
|
2273
|
+
function updateArrowsBoundToElements(movedIds, store) {
|
|
2274
|
+
const movedNonArrowIds = /* @__PURE__ */ new Set();
|
|
2275
|
+
for (const id of movedIds) {
|
|
2276
|
+
const el = store.getById(id);
|
|
2277
|
+
if (el && el.type !== "arrow") movedNonArrowIds.add(id);
|
|
2278
|
+
}
|
|
2279
|
+
if (movedNonArrowIds.size === 0) return;
|
|
2280
|
+
const updated = /* @__PURE__ */ new Set();
|
|
2281
|
+
for (const id of movedNonArrowIds) {
|
|
2282
|
+
for (const ba of findBoundArrows(id, store)) {
|
|
2283
|
+
if (updated.has(ba.id)) continue;
|
|
2284
|
+
updated.add(ba.id);
|
|
2285
|
+
const updates = updateBoundArrow(ba, store);
|
|
2286
|
+
if (updates) store.update(ba.id, updates);
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2273
2290
|
function updateBoundArrow(arrow, store) {
|
|
2274
2291
|
if (!arrow.fromBinding && !arrow.toBinding) return null;
|
|
2275
2292
|
const updates = {};
|
|
@@ -3697,6 +3714,19 @@ var NoteEditor = class {
|
|
|
3697
3714
|
}
|
|
3698
3715
|
};
|
|
3699
3716
|
|
|
3717
|
+
// src/elements/translate.ts
|
|
3718
|
+
function translateElementPatch(el, dx, dy) {
|
|
3719
|
+
const position = { x: el.position.x + dx, y: el.position.y + dy };
|
|
3720
|
+
if (el.type === "arrow") {
|
|
3721
|
+
return {
|
|
3722
|
+
position,
|
|
3723
|
+
from: { x: el.from.x + dx, y: el.from.y + dy },
|
|
3724
|
+
to: { x: el.to.x + dx, y: el.to.y + dy }
|
|
3725
|
+
};
|
|
3726
|
+
}
|
|
3727
|
+
return { position };
|
|
3728
|
+
}
|
|
3729
|
+
|
|
3700
3730
|
// src/elements/arrow-label-editor.ts
|
|
3701
3731
|
var ArrowLabelEditor = class {
|
|
3702
3732
|
input = null;
|
|
@@ -5383,6 +5413,16 @@ function getElementStyle(element) {
|
|
|
5383
5413
|
}
|
|
5384
5414
|
|
|
5385
5415
|
// src/canvas/viewport.ts
|
|
5416
|
+
function unionBounds(list) {
|
|
5417
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
5418
|
+
for (const b of list) {
|
|
5419
|
+
minX = Math.min(minX, b.x);
|
|
5420
|
+
minY = Math.min(minY, b.y);
|
|
5421
|
+
maxX = Math.max(maxX, b.x + b.w);
|
|
5422
|
+
maxY = Math.max(maxY, b.y + b.h);
|
|
5423
|
+
}
|
|
5424
|
+
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
5425
|
+
}
|
|
5386
5426
|
var EMPTY_IDS = [];
|
|
5387
5427
|
var ARROW_HIT_THRESHOLD = 10;
|
|
5388
5428
|
function noop() {
|
|
@@ -5455,7 +5495,9 @@ var Viewport = class {
|
|
|
5455
5495
|
gridSize: this._gridSize,
|
|
5456
5496
|
activeLayerId: this.layerManager.activeLayerId,
|
|
5457
5497
|
isLayerVisible: (id) => this.layerManager.isLayerVisible(id),
|
|
5458
|
-
isLayerLocked: (id) => this.layerManager.isLayerLocked(id)
|
|
5498
|
+
isLayerLocked: (id) => this.layerManager.isLayerLocked(id),
|
|
5499
|
+
smartGuides: false,
|
|
5500
|
+
getVisibleRect: () => this.camera.getVisibleRect(this.canvasEl.clientWidth, this.canvasEl.clientHeight)
|
|
5459
5501
|
};
|
|
5460
5502
|
this.inputHandler = new InputHandler(this.wrapper, this.camera, {
|
|
5461
5503
|
toolManager: this.toolManager,
|
|
@@ -5559,6 +5601,7 @@ var Viewport = class {
|
|
|
5559
5601
|
marginViewport;
|
|
5560
5602
|
resizeObserver = null;
|
|
5561
5603
|
_snapToGrid = false;
|
|
5604
|
+
_smartGuides = false;
|
|
5562
5605
|
_gridSize;
|
|
5563
5606
|
renderLoop;
|
|
5564
5607
|
domNodeManager;
|
|
@@ -5579,6 +5622,13 @@ var Viewport = class {
|
|
|
5579
5622
|
this._snapToGrid = enabled;
|
|
5580
5623
|
this.toolContext.snapToGrid = enabled;
|
|
5581
5624
|
}
|
|
5625
|
+
get smartGuides() {
|
|
5626
|
+
return this._smartGuides;
|
|
5627
|
+
}
|
|
5628
|
+
setSmartGuides(enabled) {
|
|
5629
|
+
this._smartGuides = enabled;
|
|
5630
|
+
this.toolContext.smartGuides = enabled;
|
|
5631
|
+
}
|
|
5582
5632
|
fitToContent(padding = 40) {
|
|
5583
5633
|
if (this.wrapper.clientWidth === 0 || this.wrapper.clientHeight === 0) return;
|
|
5584
5634
|
const visibleElements = this.store.getAll().filter((el) => this.layerManager.isLayerVisible(el.layerId));
|
|
@@ -5792,6 +5842,86 @@ var Viewport = class {
|
|
|
5792
5842
|
}
|
|
5793
5843
|
this.historyRecorder.commit();
|
|
5794
5844
|
}
|
|
5845
|
+
alignSelection(edge) {
|
|
5846
|
+
const bounded = this.boundedSelection();
|
|
5847
|
+
if (bounded.length < 2) return;
|
|
5848
|
+
const B = unionBounds(bounded.map((e) => e.bounds));
|
|
5849
|
+
this.historyRecorder.begin();
|
|
5850
|
+
const moved = [];
|
|
5851
|
+
for (const { id, el, bounds: b } of bounded) {
|
|
5852
|
+
if (!this.isMovable(el)) continue;
|
|
5853
|
+
let dx = 0;
|
|
5854
|
+
let dy = 0;
|
|
5855
|
+
switch (edge) {
|
|
5856
|
+
case "left":
|
|
5857
|
+
dx = B.x - b.x;
|
|
5858
|
+
break;
|
|
5859
|
+
case "right":
|
|
5860
|
+
dx = B.x + B.w - (b.x + b.w);
|
|
5861
|
+
break;
|
|
5862
|
+
case "center-x":
|
|
5863
|
+
dx = B.x + B.w / 2 - (b.x + b.w / 2);
|
|
5864
|
+
break;
|
|
5865
|
+
case "top":
|
|
5866
|
+
dy = B.y - b.y;
|
|
5867
|
+
break;
|
|
5868
|
+
case "bottom":
|
|
5869
|
+
dy = B.y + B.h - (b.y + b.h);
|
|
5870
|
+
break;
|
|
5871
|
+
case "middle":
|
|
5872
|
+
dy = B.y + B.h / 2 - (b.y + b.h / 2);
|
|
5873
|
+
break;
|
|
5874
|
+
}
|
|
5875
|
+
if (dx === 0 && dy === 0) continue;
|
|
5876
|
+
this.store.update(id, translateElementPatch(el, dx, dy));
|
|
5877
|
+
moved.push(id);
|
|
5878
|
+
}
|
|
5879
|
+
updateArrowsBoundToElements(moved, this.store);
|
|
5880
|
+
this.historyRecorder.commit();
|
|
5881
|
+
this.requestRender();
|
|
5882
|
+
}
|
|
5883
|
+
distributeSelection(axis) {
|
|
5884
|
+
const bounded = this.boundedSelection();
|
|
5885
|
+
if (bounded.length < 3) return;
|
|
5886
|
+
const center = (b) => axis === "horizontal" ? b.x + b.w / 2 : b.y + b.h / 2;
|
|
5887
|
+
const sorted = [...bounded].sort((p, q) => center(p.bounds) - center(q.bounds));
|
|
5888
|
+
const first = sorted[0];
|
|
5889
|
+
const last = sorted[sorted.length - 1];
|
|
5890
|
+
if (!first || !last) return;
|
|
5891
|
+
const c0 = center(first.bounds);
|
|
5892
|
+
const cN = center(last.bounds);
|
|
5893
|
+
const n = sorted.length;
|
|
5894
|
+
this.historyRecorder.begin();
|
|
5895
|
+
const moved = [];
|
|
5896
|
+
for (let i = 1; i < n - 1; i++) {
|
|
5897
|
+
const item = sorted[i];
|
|
5898
|
+
if (!item || !this.isMovable(item.el)) continue;
|
|
5899
|
+
const target = c0 + i * (cN - c0) / (n - 1);
|
|
5900
|
+
const delta = target - center(item.bounds);
|
|
5901
|
+
if (delta === 0) continue;
|
|
5902
|
+
const [dx, dy] = axis === "horizontal" ? [delta, 0] : [0, delta];
|
|
5903
|
+
this.store.update(item.id, translateElementPatch(item.el, dx, dy));
|
|
5904
|
+
moved.push(item.id);
|
|
5905
|
+
}
|
|
5906
|
+
updateArrowsBoundToElements(moved, this.store);
|
|
5907
|
+
this.historyRecorder.commit();
|
|
5908
|
+
this.requestRender();
|
|
5909
|
+
}
|
|
5910
|
+
boundedSelection() {
|
|
5911
|
+
const out = [];
|
|
5912
|
+
for (const id of this.getSelectedIds()) {
|
|
5913
|
+
const el = this.store.getById(id);
|
|
5914
|
+
if (!el) continue;
|
|
5915
|
+
const bounds = getElementBounds(el);
|
|
5916
|
+
if (bounds) out.push({ id, el, bounds });
|
|
5917
|
+
}
|
|
5918
|
+
return out;
|
|
5919
|
+
}
|
|
5920
|
+
isMovable(el) {
|
|
5921
|
+
if (el.locked) return false;
|
|
5922
|
+
if (el.type === "arrow" && (el.fromBinding ?? el.toBinding)) return false;
|
|
5923
|
+
return true;
|
|
5924
|
+
}
|
|
5795
5925
|
getRenderStats() {
|
|
5796
5926
|
return this.renderLoop.getStats();
|
|
5797
5927
|
}
|
|
@@ -6536,8 +6666,47 @@ function renderArrowHandles(canvasCtx, arrow, zoom) {
|
|
|
6536
6666
|
}
|
|
6537
6667
|
}
|
|
6538
6668
|
|
|
6669
|
+
// src/elements/snap-guides.ts
|
|
6670
|
+
function xAnchors(b) {
|
|
6671
|
+
return { lo: b.x, mid: b.x + b.w / 2, hi: b.x + b.w };
|
|
6672
|
+
}
|
|
6673
|
+
function yAnchors(b) {
|
|
6674
|
+
return { lo: b.y, mid: b.y + b.h / 2, hi: b.y + b.h };
|
|
6675
|
+
}
|
|
6676
|
+
function bestAxisSnap(moving, targets, anchorsFn, threshold) {
|
|
6677
|
+
let best = null;
|
|
6678
|
+
for (const t of targets) {
|
|
6679
|
+
const ta = anchorsFn(t);
|
|
6680
|
+
const pairs = [
|
|
6681
|
+
// colinear alignment: same-type edges/centers line up
|
|
6682
|
+
[ta.lo - moving.lo, ta.lo],
|
|
6683
|
+
[ta.mid - moving.mid, ta.mid],
|
|
6684
|
+
[ta.hi - moving.hi, ta.hi],
|
|
6685
|
+
// abutment: the moving box sits flush against the target's opposite edge
|
|
6686
|
+
[ta.lo - moving.hi, ta.lo],
|
|
6687
|
+
[ta.hi - moving.lo, ta.hi]
|
|
6688
|
+
];
|
|
6689
|
+
for (const [delta, position] of pairs) {
|
|
6690
|
+
const abs = Math.abs(delta);
|
|
6691
|
+
if (abs <= threshold && (best === null || abs < Math.abs(best.delta))) {
|
|
6692
|
+
best = { delta, position };
|
|
6693
|
+
}
|
|
6694
|
+
}
|
|
6695
|
+
}
|
|
6696
|
+
return best;
|
|
6697
|
+
}
|
|
6698
|
+
function computeSnapGuides(moving, targets, threshold) {
|
|
6699
|
+
const xSnap = bestAxisSnap(xAnchors(moving), targets, xAnchors, threshold);
|
|
6700
|
+
const ySnap = bestAxisSnap(yAnchors(moving), targets, yAnchors, threshold);
|
|
6701
|
+
const guides = [];
|
|
6702
|
+
if (xSnap) guides.push({ axis: "x", position: xSnap.position });
|
|
6703
|
+
if (ySnap) guides.push({ axis: "y", position: ySnap.position });
|
|
6704
|
+
return { dx: xSnap?.delta ?? 0, dy: ySnap?.delta ?? 0, guides };
|
|
6705
|
+
}
|
|
6706
|
+
|
|
6539
6707
|
// src/tools/select-tool.ts
|
|
6540
6708
|
var HANDLE_SIZE = 8;
|
|
6709
|
+
var SNAP_PX = 6;
|
|
6541
6710
|
var HANDLE_HIT_PADDING2 = 4;
|
|
6542
6711
|
var SELECTION_PAD = 4;
|
|
6543
6712
|
var MIN_ELEMENT_SIZE = 20;
|
|
@@ -6557,6 +6726,9 @@ var SelectTool = class {
|
|
|
6557
6726
|
ctx = null;
|
|
6558
6727
|
pendingSingleSelectId = null;
|
|
6559
6728
|
hasDragged = false;
|
|
6729
|
+
activeGuides = [];
|
|
6730
|
+
dragSnapTargets = null;
|
|
6731
|
+
dragVisibleRect = null;
|
|
6560
6732
|
resizeAspectRatio = 0;
|
|
6561
6733
|
hoveredId = null;
|
|
6562
6734
|
get selectedIds() {
|
|
@@ -6588,6 +6760,9 @@ var SelectTool = class {
|
|
|
6588
6760
|
this.setSelectedIds([]);
|
|
6589
6761
|
this.mode = { type: "idle" };
|
|
6590
6762
|
this.hoveredId = null;
|
|
6763
|
+
this.activeGuides = [];
|
|
6764
|
+
this.dragSnapTargets = null;
|
|
6765
|
+
this.dragVisibleRect = null;
|
|
6591
6766
|
ctx.setCursor?.("default");
|
|
6592
6767
|
}
|
|
6593
6768
|
snap(point, ctx) {
|
|
@@ -6596,6 +6771,8 @@ var SelectTool = class {
|
|
|
6596
6771
|
onPointerDown(state, ctx) {
|
|
6597
6772
|
this.ctx = ctx;
|
|
6598
6773
|
this.setHovered(null, ctx);
|
|
6774
|
+
this.dragSnapTargets = null;
|
|
6775
|
+
this.dragVisibleRect = null;
|
|
6599
6776
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
6600
6777
|
this.lastWorld = this.snap(world, ctx);
|
|
6601
6778
|
this.currentWorld = world;
|
|
@@ -6696,6 +6873,31 @@ var SelectTool = class {
|
|
|
6696
6873
|
const dx = snapped.x - this.lastWorld.x;
|
|
6697
6874
|
const dy = snapped.y - this.lastWorld.y;
|
|
6698
6875
|
this.lastWorld = snapped;
|
|
6876
|
+
let adjDx = dx;
|
|
6877
|
+
let adjDy = dy;
|
|
6878
|
+
this.activeGuides = [];
|
|
6879
|
+
if (ctx.smartGuides) {
|
|
6880
|
+
if (this.dragSnapTargets === null) {
|
|
6881
|
+
const selSet = new Set(this._selectedIds);
|
|
6882
|
+
this.dragVisibleRect = ctx.getVisibleRect?.() ?? null;
|
|
6883
|
+
const candidates = (this.dragVisibleRect ? ctx.store.queryRect(this.dragVisibleRect) : ctx.store.getAll()).filter((el) => !selSet.has(el.id) && el.type !== "grid");
|
|
6884
|
+
const targets = [];
|
|
6885
|
+
for (const el of candidates) {
|
|
6886
|
+
const b = getElementBounds(el);
|
|
6887
|
+
if (b) targets.push(b);
|
|
6888
|
+
}
|
|
6889
|
+
this.dragSnapTargets = targets;
|
|
6890
|
+
}
|
|
6891
|
+
const selectedEls = this._selectedIds.map((id) => ctx.store.getById(id)).filter((el) => !!el && !el.locked);
|
|
6892
|
+
const base = getElementsBoundingBox(selectedEls);
|
|
6893
|
+
if (base) {
|
|
6894
|
+
const moving = { x: base.x + dx, y: base.y + dy, w: base.w, h: base.h };
|
|
6895
|
+
const res = computeSnapGuides(moving, this.dragSnapTargets, SNAP_PX / ctx.camera.zoom);
|
|
6896
|
+
adjDx = dx + res.dx;
|
|
6897
|
+
adjDy = dy + res.dy;
|
|
6898
|
+
this.activeGuides = res.guides;
|
|
6899
|
+
}
|
|
6900
|
+
}
|
|
6699
6901
|
for (const id of this._selectedIds) {
|
|
6700
6902
|
const el = ctx.store.getById(id);
|
|
6701
6903
|
if (!el || el.locked) continue;
|
|
@@ -6704,13 +6906,13 @@ var SelectTool = class {
|
|
|
6704
6906
|
continue;
|
|
6705
6907
|
}
|
|
6706
6908
|
ctx.store.update(id, {
|
|
6707
|
-
position: { x: el.position.x +
|
|
6708
|
-
from: { x: el.from.x +
|
|
6709
|
-
to: { x: el.to.x +
|
|
6909
|
+
position: { x: el.position.x + adjDx, y: el.position.y + adjDy },
|
|
6910
|
+
from: { x: el.from.x + adjDx, y: el.from.y + adjDy },
|
|
6911
|
+
to: { x: el.to.x + adjDx, y: el.to.y + adjDy }
|
|
6710
6912
|
});
|
|
6711
|
-
} else if (ctx.gridType && "size" in el) {
|
|
6712
|
-
const centerX = el.position.x + el.size.w / 2 +
|
|
6713
|
-
const centerY = el.position.y + el.size.h / 2 +
|
|
6913
|
+
} else if (!ctx.smartGuides && ctx.gridType && "size" in el) {
|
|
6914
|
+
const centerX = el.position.x + el.size.w / 2 + adjDx;
|
|
6915
|
+
const centerY = el.position.y + el.size.h / 2 + adjDy;
|
|
6714
6916
|
const snappedCenter = this.snap({ x: centerX, y: centerY }, ctx);
|
|
6715
6917
|
ctx.store.update(id, {
|
|
6716
6918
|
position: {
|
|
@@ -6720,7 +6922,7 @@ var SelectTool = class {
|
|
|
6720
6922
|
});
|
|
6721
6923
|
} else {
|
|
6722
6924
|
ctx.store.update(id, {
|
|
6723
|
-
position: { x: el.position.x +
|
|
6925
|
+
position: { x: el.position.x + adjDx, y: el.position.y + adjDy }
|
|
6724
6926
|
});
|
|
6725
6927
|
}
|
|
6726
6928
|
}
|
|
@@ -6750,6 +6952,10 @@ var SelectTool = class {
|
|
|
6750
6952
|
this.hasDragged = false;
|
|
6751
6953
|
const resizedNoteId = this.mode.type === "resizing" ? this.mode.elementId : null;
|
|
6752
6954
|
this.mode = { type: "idle" };
|
|
6955
|
+
this.activeGuides = [];
|
|
6956
|
+
this.dragSnapTargets = null;
|
|
6957
|
+
this.dragVisibleRect = null;
|
|
6958
|
+
ctx.requestRender();
|
|
6753
6959
|
ctx.setCursor?.("default");
|
|
6754
6960
|
if (resizedNoteId !== null) {
|
|
6755
6961
|
const el = ctx.store.getById(resizedNoteId);
|
|
@@ -6797,42 +7003,43 @@ var SelectTool = class {
|
|
|
6797
7003
|
}
|
|
6798
7004
|
}
|
|
6799
7005
|
}
|
|
7006
|
+
this.renderGuideLines(canvasCtx);
|
|
6800
7007
|
}
|
|
6801
|
-
|
|
6802
|
-
|
|
6803
|
-
|
|
6804
|
-
|
|
6805
|
-
|
|
6806
|
-
|
|
6807
|
-
|
|
6808
|
-
|
|
6809
|
-
for (const
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
7008
|
+
renderGuideLines(canvasCtx) {
|
|
7009
|
+
if (this.mode.type !== "dragging" || !this.ctx || this.activeGuides.length === 0) return;
|
|
7010
|
+
const zoom = this.ctx.camera.zoom;
|
|
7011
|
+
const rect = this.dragVisibleRect;
|
|
7012
|
+
canvasCtx.save();
|
|
7013
|
+
canvasCtx.strokeStyle = "#FF4081";
|
|
7014
|
+
canvasCtx.lineWidth = 1 / zoom;
|
|
7015
|
+
canvasCtx.setLineDash([]);
|
|
7016
|
+
for (const g of this.activeGuides) {
|
|
7017
|
+
canvasCtx.beginPath();
|
|
7018
|
+
if (g.axis === "x") {
|
|
7019
|
+
const y0 = rect ? rect.y : this.currentWorld.y - 1e5;
|
|
7020
|
+
const y1 = rect ? rect.y + rect.h : this.currentWorld.y + 1e5;
|
|
7021
|
+
canvasCtx.moveTo(g.position, y0);
|
|
7022
|
+
canvasCtx.lineTo(g.position, y1);
|
|
7023
|
+
} else {
|
|
7024
|
+
const x0 = rect ? rect.x : this.currentWorld.x - 1e5;
|
|
7025
|
+
const x1 = rect ? rect.x + rect.w : this.currentWorld.x + 1e5;
|
|
7026
|
+
canvasCtx.moveTo(x0, g.position);
|
|
7027
|
+
canvasCtx.lineTo(x1, g.position);
|
|
6816
7028
|
}
|
|
7029
|
+
canvasCtx.stroke();
|
|
6817
7030
|
}
|
|
7031
|
+
canvasCtx.restore();
|
|
7032
|
+
}
|
|
7033
|
+
updateArrowsBoundTo(ids, ctx) {
|
|
7034
|
+
updateArrowsBoundToElements(ids, ctx.store);
|
|
6818
7035
|
}
|
|
6819
7036
|
nudgeSelection(dx, dy, ctx) {
|
|
6820
7037
|
let moved = false;
|
|
6821
7038
|
for (const id of this._selectedIds) {
|
|
6822
7039
|
const el = ctx.store.getById(id);
|
|
6823
7040
|
if (!el || el.locked) continue;
|
|
6824
|
-
if (el.type === "arrow")
|
|
6825
|
-
|
|
6826
|
-
ctx.store.update(id, {
|
|
6827
|
-
position: { x: el.position.x + dx, y: el.position.y + dy },
|
|
6828
|
-
from: { x: el.from.x + dx, y: el.from.y + dy },
|
|
6829
|
-
to: { x: el.to.x + dx, y: el.to.y + dy }
|
|
6830
|
-
});
|
|
6831
|
-
} else {
|
|
6832
|
-
ctx.store.update(id, {
|
|
6833
|
-
position: { x: el.position.x + dx, y: el.position.y + dy }
|
|
6834
|
-
});
|
|
6835
|
-
}
|
|
7041
|
+
if (el.type === "arrow" && (el.fromBinding || el.toBinding)) continue;
|
|
7042
|
+
ctx.store.update(id, translateElementPatch(el, dx, dy));
|
|
6836
7043
|
moved = true;
|
|
6837
7044
|
}
|
|
6838
7045
|
if (moved) {
|
|
@@ -8024,7 +8231,7 @@ var TemplateTool = class {
|
|
|
8024
8231
|
};
|
|
8025
8232
|
|
|
8026
8233
|
// src/index.ts
|
|
8027
|
-
var VERSION = "0.
|
|
8234
|
+
var VERSION = "0.33.0";
|
|
8028
8235
|
export {
|
|
8029
8236
|
ArrowTool,
|
|
8030
8237
|
AutoSave,
|