@fieldnotes/core 0.32.0 → 0.34.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 +204 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +204 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -20,6 +20,8 @@ interface BaseElement {
|
|
|
20
20
|
zIndex: number;
|
|
21
21
|
locked: boolean;
|
|
22
22
|
layerId: string;
|
|
23
|
+
/** Optional flat group membership. Elements sharing a groupId select/move/delete as a unit. */
|
|
24
|
+
groupId?: string;
|
|
23
25
|
}
|
|
24
26
|
interface StrokeElement extends BaseElement {
|
|
25
27
|
type: 'stroke';
|
|
@@ -200,6 +202,8 @@ interface ToolContext {
|
|
|
200
202
|
activeLayerId?: string;
|
|
201
203
|
isLayerVisible?: (layerId: string) => boolean;
|
|
202
204
|
isLayerLocked?: (layerId: string) => boolean;
|
|
205
|
+
smartGuides?: boolean;
|
|
206
|
+
getVisibleRect?: () => Bounds;
|
|
203
207
|
}
|
|
204
208
|
interface PointerState {
|
|
205
209
|
x: number;
|
|
@@ -460,6 +464,7 @@ declare class Viewport {
|
|
|
460
464
|
private readonly marginViewport;
|
|
461
465
|
private resizeObserver;
|
|
462
466
|
private _snapToGrid;
|
|
467
|
+
private _smartGuides;
|
|
463
468
|
private readonly _gridSize;
|
|
464
469
|
private readonly renderLoop;
|
|
465
470
|
private readonly domNodeManager;
|
|
@@ -474,6 +479,8 @@ declare class Viewport {
|
|
|
474
479
|
get ctx(): CanvasRenderingContext2D | null;
|
|
475
480
|
get snapToGrid(): boolean;
|
|
476
481
|
setSnapToGrid(enabled: boolean): void;
|
|
482
|
+
get smartGuides(): boolean;
|
|
483
|
+
setSmartGuides(enabled: boolean): void;
|
|
477
484
|
fitToContent(padding?: number): void;
|
|
478
485
|
requestRender(): void;
|
|
479
486
|
exportState(): CanvasState;
|
|
@@ -518,6 +525,8 @@ declare class Viewport {
|
|
|
518
525
|
onSelectionChange(listener: () => void): () => void;
|
|
519
526
|
getSelectionStyle(): ElementStyle | null;
|
|
520
527
|
applyStyleToSelection(style: ElementStyle): void;
|
|
528
|
+
groupSelection(): void;
|
|
529
|
+
ungroupSelection(): void;
|
|
521
530
|
alignSelection(edge: AlignEdge): void;
|
|
522
531
|
distributeSelection(axis: DistributeAxis): void;
|
|
523
532
|
private boundedSelection;
|
|
@@ -749,6 +758,9 @@ declare class SelectTool implements Tool {
|
|
|
749
758
|
private ctx;
|
|
750
759
|
private pendingSingleSelectId;
|
|
751
760
|
private hasDragged;
|
|
761
|
+
private activeGuides;
|
|
762
|
+
private dragSnapTargets;
|
|
763
|
+
private dragVisibleRect;
|
|
752
764
|
private resizeAspectRatio;
|
|
753
765
|
private hoveredId;
|
|
754
766
|
get selectedIds(): string[];
|
|
@@ -764,6 +776,7 @@ declare class SelectTool implements Tool {
|
|
|
764
776
|
onPointerUp(_state: PointerState, ctx: ToolContext): void;
|
|
765
777
|
onHover(state: PointerState, ctx: ToolContext): void;
|
|
766
778
|
renderOverlay(canvasCtx: CanvasRenderingContext2D): void;
|
|
779
|
+
private renderGuideLines;
|
|
767
780
|
private updateArrowsBoundTo;
|
|
768
781
|
nudgeSelection(dx: number, dy: number, ctx: ToolContext): boolean;
|
|
769
782
|
private updateHoverCursor;
|
|
@@ -979,6 +992,6 @@ declare class TemplateTool implements Tool {
|
|
|
979
992
|
private notifyOptionsChange;
|
|
980
993
|
}
|
|
981
994
|
|
|
982
|
-
declare const VERSION = "0.
|
|
995
|
+
declare const VERSION = "0.34.0";
|
|
983
996
|
|
|
984
997
|
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
|
@@ -20,6 +20,8 @@ interface BaseElement {
|
|
|
20
20
|
zIndex: number;
|
|
21
21
|
locked: boolean;
|
|
22
22
|
layerId: string;
|
|
23
|
+
/** Optional flat group membership. Elements sharing a groupId select/move/delete as a unit. */
|
|
24
|
+
groupId?: string;
|
|
23
25
|
}
|
|
24
26
|
interface StrokeElement extends BaseElement {
|
|
25
27
|
type: 'stroke';
|
|
@@ -200,6 +202,8 @@ interface ToolContext {
|
|
|
200
202
|
activeLayerId?: string;
|
|
201
203
|
isLayerVisible?: (layerId: string) => boolean;
|
|
202
204
|
isLayerLocked?: (layerId: string) => boolean;
|
|
205
|
+
smartGuides?: boolean;
|
|
206
|
+
getVisibleRect?: () => Bounds;
|
|
203
207
|
}
|
|
204
208
|
interface PointerState {
|
|
205
209
|
x: number;
|
|
@@ -460,6 +464,7 @@ declare class Viewport {
|
|
|
460
464
|
private readonly marginViewport;
|
|
461
465
|
private resizeObserver;
|
|
462
466
|
private _snapToGrid;
|
|
467
|
+
private _smartGuides;
|
|
463
468
|
private readonly _gridSize;
|
|
464
469
|
private readonly renderLoop;
|
|
465
470
|
private readonly domNodeManager;
|
|
@@ -474,6 +479,8 @@ declare class Viewport {
|
|
|
474
479
|
get ctx(): CanvasRenderingContext2D | null;
|
|
475
480
|
get snapToGrid(): boolean;
|
|
476
481
|
setSnapToGrid(enabled: boolean): void;
|
|
482
|
+
get smartGuides(): boolean;
|
|
483
|
+
setSmartGuides(enabled: boolean): void;
|
|
477
484
|
fitToContent(padding?: number): void;
|
|
478
485
|
requestRender(): void;
|
|
479
486
|
exportState(): CanvasState;
|
|
@@ -518,6 +525,8 @@ declare class Viewport {
|
|
|
518
525
|
onSelectionChange(listener: () => void): () => void;
|
|
519
526
|
getSelectionStyle(): ElementStyle | null;
|
|
520
527
|
applyStyleToSelection(style: ElementStyle): void;
|
|
528
|
+
groupSelection(): void;
|
|
529
|
+
ungroupSelection(): void;
|
|
521
530
|
alignSelection(edge: AlignEdge): void;
|
|
522
531
|
distributeSelection(axis: DistributeAxis): void;
|
|
523
532
|
private boundedSelection;
|
|
@@ -749,6 +758,9 @@ declare class SelectTool implements Tool {
|
|
|
749
758
|
private ctx;
|
|
750
759
|
private pendingSingleSelectId;
|
|
751
760
|
private hasDragged;
|
|
761
|
+
private activeGuides;
|
|
762
|
+
private dragSnapTargets;
|
|
763
|
+
private dragVisibleRect;
|
|
752
764
|
private resizeAspectRatio;
|
|
753
765
|
private hoveredId;
|
|
754
766
|
get selectedIds(): string[];
|
|
@@ -764,6 +776,7 @@ declare class SelectTool implements Tool {
|
|
|
764
776
|
onPointerUp(_state: PointerState, ctx: ToolContext): void;
|
|
765
777
|
onHover(state: PointerState, ctx: ToolContext): void;
|
|
766
778
|
renderOverlay(canvasCtx: CanvasRenderingContext2D): void;
|
|
779
|
+
private renderGuideLines;
|
|
767
780
|
private updateArrowsBoundTo;
|
|
768
781
|
nudgeSelection(dx: number, dy: number, ctx: ToolContext): boolean;
|
|
769
782
|
private updateHoverCursor;
|
|
@@ -979,6 +992,6 @@ declare class TemplateTool implements Tool {
|
|
|
979
992
|
private notifyOptionsChange;
|
|
980
993
|
}
|
|
981
994
|
|
|
982
|
-
declare const VERSION = "0.
|
|
995
|
+
declare const VERSION = "0.34.0";
|
|
983
996
|
|
|
984
997
|
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
|
@@ -940,6 +940,14 @@ var KeyboardActions = class {
|
|
|
940
940
|
if (this.deps.isToolActive()) return;
|
|
941
941
|
this.deps.fitToContent?.();
|
|
942
942
|
}
|
|
943
|
+
group() {
|
|
944
|
+
if (this.deps.isToolActive()) return;
|
|
945
|
+
this.deps.group?.();
|
|
946
|
+
}
|
|
947
|
+
ungroup() {
|
|
948
|
+
if (this.deps.isToolActive()) return;
|
|
949
|
+
this.deps.ungroup?.();
|
|
950
|
+
}
|
|
943
951
|
zOrder(operation) {
|
|
944
952
|
if (this.deps.isToolActive()) return;
|
|
945
953
|
this.flushPendingNudge();
|
|
@@ -973,6 +981,10 @@ var KeyboardActions = class {
|
|
|
973
981
|
for (const el of source) {
|
|
974
982
|
idMap.set(el.id, createId(el.type));
|
|
975
983
|
}
|
|
984
|
+
const groupIdMap = /* @__PURE__ */ new Map();
|
|
985
|
+
for (const el of source) {
|
|
986
|
+
if (el.groupId && !groupIdMap.has(el.groupId)) groupIdMap.set(el.groupId, createId("group"));
|
|
987
|
+
}
|
|
976
988
|
const newIds = [];
|
|
977
989
|
const recorder = this.deps.getHistoryRecorder();
|
|
978
990
|
recorder?.begin();
|
|
@@ -981,6 +993,7 @@ var KeyboardActions = class {
|
|
|
981
993
|
const newId = idMap.get(el.id);
|
|
982
994
|
if (!newId) continue;
|
|
983
995
|
clone.id = newId;
|
|
996
|
+
if (clone.groupId) clone.groupId = groupIdMap.get(clone.groupId) ?? clone.groupId;
|
|
984
997
|
clone.position = { x: clone.position.x + offset.x, y: clone.position.y + offset.y };
|
|
985
998
|
if (clone.type === "arrow") {
|
|
986
999
|
const arrow = clone;
|
|
@@ -1034,6 +1047,8 @@ var DEFAULT_BINDINGS = [
|
|
|
1034
1047
|
["zoom-in", ["mod+="]],
|
|
1035
1048
|
["zoom-out", ["mod+-"]],
|
|
1036
1049
|
["zoom-reset", ["mod+0"]],
|
|
1050
|
+
["group", ["mod+g"]],
|
|
1051
|
+
["ungroup", ["mod+shift+g"]],
|
|
1037
1052
|
["nudge-left", ["arrowleft"]],
|
|
1038
1053
|
["nudge-right", ["arrowright"]],
|
|
1039
1054
|
["nudge-up", ["arrowup"]],
|
|
@@ -1193,6 +1208,8 @@ var InputHandler = class {
|
|
|
1193
1208
|
getHistoryStack: () => this.historyStack,
|
|
1194
1209
|
isToolActive: () => this.isToolActive,
|
|
1195
1210
|
fitToContent: options.fitToContent,
|
|
1211
|
+
group: options.group,
|
|
1212
|
+
ungroup: options.ungroup,
|
|
1196
1213
|
getLastPointerWorld: () => this.lastPointerWorld()
|
|
1197
1214
|
});
|
|
1198
1215
|
this.shortcutMap = new ShortcutMap(options.shortcuts?.bindings);
|
|
@@ -1432,6 +1449,14 @@ var InputHandler = class {
|
|
|
1432
1449
|
e.preventDefault();
|
|
1433
1450
|
this.actions.zoomToFit();
|
|
1434
1451
|
return;
|
|
1452
|
+
case "group":
|
|
1453
|
+
e.preventDefault();
|
|
1454
|
+
this.actions.group();
|
|
1455
|
+
return;
|
|
1456
|
+
case "ungroup":
|
|
1457
|
+
e.preventDefault();
|
|
1458
|
+
this.actions.ungroup();
|
|
1459
|
+
return;
|
|
1435
1460
|
case "zoom-in":
|
|
1436
1461
|
e.preventDefault();
|
|
1437
1462
|
this.zoomByFactor(ZOOM_STEP);
|
|
@@ -3937,12 +3962,19 @@ var UpdateElementCommand = class {
|
|
|
3937
3962
|
this.current = current;
|
|
3938
3963
|
}
|
|
3939
3964
|
execute(store) {
|
|
3940
|
-
store.update(this.id,
|
|
3965
|
+
store.update(this.id, diffPatch(this.previous, this.current));
|
|
3941
3966
|
}
|
|
3942
3967
|
undo(store) {
|
|
3943
|
-
store.update(this.id,
|
|
3968
|
+
store.update(this.id, diffPatch(this.current, this.previous));
|
|
3944
3969
|
}
|
|
3945
3970
|
};
|
|
3971
|
+
function diffPatch(from, to) {
|
|
3972
|
+
const patch = { ...to };
|
|
3973
|
+
for (const key of Object.keys(from)) {
|
|
3974
|
+
if (!(key in to)) patch[key] = void 0;
|
|
3975
|
+
}
|
|
3976
|
+
return patch;
|
|
3977
|
+
}
|
|
3946
3978
|
var BatchCommand = class {
|
|
3947
3979
|
commands;
|
|
3948
3980
|
constructor(commands) {
|
|
@@ -5495,7 +5527,9 @@ var Viewport = class {
|
|
|
5495
5527
|
gridSize: this._gridSize,
|
|
5496
5528
|
activeLayerId: this.layerManager.activeLayerId,
|
|
5497
5529
|
isLayerVisible: (id) => this.layerManager.isLayerVisible(id),
|
|
5498
|
-
isLayerLocked: (id) => this.layerManager.isLayerLocked(id)
|
|
5530
|
+
isLayerLocked: (id) => this.layerManager.isLayerLocked(id),
|
|
5531
|
+
smartGuides: false,
|
|
5532
|
+
getVisibleRect: () => this.camera.getVisibleRect(this.canvasEl.clientWidth, this.canvasEl.clientHeight)
|
|
5499
5533
|
};
|
|
5500
5534
|
this.inputHandler = new InputHandler(this.wrapper, this.camera, {
|
|
5501
5535
|
toolManager: this.toolManager,
|
|
@@ -5503,6 +5537,8 @@ var Viewport = class {
|
|
|
5503
5537
|
historyRecorder: this.historyRecorder,
|
|
5504
5538
|
historyStack: this.history,
|
|
5505
5539
|
fitToContent: () => this.fitToContent(),
|
|
5540
|
+
group: () => this.groupSelection(),
|
|
5541
|
+
ungroup: () => this.ungroupSelection(),
|
|
5506
5542
|
shortcuts: options.shortcuts
|
|
5507
5543
|
});
|
|
5508
5544
|
this.domNodeManager = new DomNodeManager({
|
|
@@ -5599,6 +5635,7 @@ var Viewport = class {
|
|
|
5599
5635
|
marginViewport;
|
|
5600
5636
|
resizeObserver = null;
|
|
5601
5637
|
_snapToGrid = false;
|
|
5638
|
+
_smartGuides = false;
|
|
5602
5639
|
_gridSize;
|
|
5603
5640
|
renderLoop;
|
|
5604
5641
|
domNodeManager;
|
|
@@ -5619,6 +5656,13 @@ var Viewport = class {
|
|
|
5619
5656
|
this._snapToGrid = enabled;
|
|
5620
5657
|
this.toolContext.snapToGrid = enabled;
|
|
5621
5658
|
}
|
|
5659
|
+
get smartGuides() {
|
|
5660
|
+
return this._smartGuides;
|
|
5661
|
+
}
|
|
5662
|
+
setSmartGuides(enabled) {
|
|
5663
|
+
this._smartGuides = enabled;
|
|
5664
|
+
this.toolContext.smartGuides = enabled;
|
|
5665
|
+
}
|
|
5622
5666
|
fitToContent(padding = 40) {
|
|
5623
5667
|
if (this.wrapper.clientWidth === 0 || this.wrapper.clientHeight === 0) return;
|
|
5624
5668
|
const visibleElements = this.store.getAll().filter((el) => this.layerManager.isLayerVisible(el.layerId));
|
|
@@ -5832,6 +5876,26 @@ var Viewport = class {
|
|
|
5832
5876
|
}
|
|
5833
5877
|
this.historyRecorder.commit();
|
|
5834
5878
|
}
|
|
5879
|
+
groupSelection() {
|
|
5880
|
+
const ids = this.getSelectedIds();
|
|
5881
|
+
if (ids.length < 2) return;
|
|
5882
|
+
const groupId = createId("group");
|
|
5883
|
+
this.historyRecorder.begin();
|
|
5884
|
+
for (const id of ids) {
|
|
5885
|
+
if (this.store.getById(id)) this.store.update(id, { groupId });
|
|
5886
|
+
}
|
|
5887
|
+
this.historyRecorder.commit();
|
|
5888
|
+
}
|
|
5889
|
+
ungroupSelection() {
|
|
5890
|
+
const ids = this.getSelectedIds();
|
|
5891
|
+
if (ids.length === 0) return;
|
|
5892
|
+
this.historyRecorder.begin();
|
|
5893
|
+
for (const id of ids) {
|
|
5894
|
+
const el = this.store.getById(id);
|
|
5895
|
+
if (el && el.groupId !== void 0) this.store.update(id, { groupId: void 0 });
|
|
5896
|
+
}
|
|
5897
|
+
this.historyRecorder.commit();
|
|
5898
|
+
}
|
|
5835
5899
|
alignSelection(edge) {
|
|
5836
5900
|
const bounded = this.boundedSelection();
|
|
5837
5901
|
if (bounded.length < 2) return;
|
|
@@ -6541,6 +6605,26 @@ var EraserTool = class {
|
|
|
6541
6605
|
}
|
|
6542
6606
|
};
|
|
6543
6607
|
|
|
6608
|
+
// src/elements/group.ts
|
|
6609
|
+
function expandToGroups(ids, elements) {
|
|
6610
|
+
const byId = new Map(elements.map((e) => [e.id, e]));
|
|
6611
|
+
const groupIds = /* @__PURE__ */ new Set();
|
|
6612
|
+
for (const id of ids) {
|
|
6613
|
+
const g = byId.get(id)?.groupId;
|
|
6614
|
+
if (g) groupIds.add(g);
|
|
6615
|
+
}
|
|
6616
|
+
if (groupIds.size === 0) return ids;
|
|
6617
|
+
const idSet = new Set(ids);
|
|
6618
|
+
const result = [...ids];
|
|
6619
|
+
for (const el of elements) {
|
|
6620
|
+
if (el.groupId && groupIds.has(el.groupId) && !idSet.has(el.id)) {
|
|
6621
|
+
result.push(el.id);
|
|
6622
|
+
idSet.add(el.id);
|
|
6623
|
+
}
|
|
6624
|
+
}
|
|
6625
|
+
return result;
|
|
6626
|
+
}
|
|
6627
|
+
|
|
6544
6628
|
// src/tools/arrow-handles.ts
|
|
6545
6629
|
var BIND_THRESHOLD = 20;
|
|
6546
6630
|
var HANDLE_RADIUS = 5;
|
|
@@ -6656,8 +6740,47 @@ function renderArrowHandles(canvasCtx, arrow, zoom) {
|
|
|
6656
6740
|
}
|
|
6657
6741
|
}
|
|
6658
6742
|
|
|
6743
|
+
// src/elements/snap-guides.ts
|
|
6744
|
+
function xAnchors(b) {
|
|
6745
|
+
return { lo: b.x, mid: b.x + b.w / 2, hi: b.x + b.w };
|
|
6746
|
+
}
|
|
6747
|
+
function yAnchors(b) {
|
|
6748
|
+
return { lo: b.y, mid: b.y + b.h / 2, hi: b.y + b.h };
|
|
6749
|
+
}
|
|
6750
|
+
function bestAxisSnap(moving, targets, anchorsFn, threshold) {
|
|
6751
|
+
let best = null;
|
|
6752
|
+
for (const t of targets) {
|
|
6753
|
+
const ta = anchorsFn(t);
|
|
6754
|
+
const pairs = [
|
|
6755
|
+
// colinear alignment: same-type edges/centers line up
|
|
6756
|
+
[ta.lo - moving.lo, ta.lo],
|
|
6757
|
+
[ta.mid - moving.mid, ta.mid],
|
|
6758
|
+
[ta.hi - moving.hi, ta.hi],
|
|
6759
|
+
// abutment: the moving box sits flush against the target's opposite edge
|
|
6760
|
+
[ta.lo - moving.hi, ta.lo],
|
|
6761
|
+
[ta.hi - moving.lo, ta.hi]
|
|
6762
|
+
];
|
|
6763
|
+
for (const [delta, position] of pairs) {
|
|
6764
|
+
const abs = Math.abs(delta);
|
|
6765
|
+
if (abs <= threshold && (best === null || abs < Math.abs(best.delta))) {
|
|
6766
|
+
best = { delta, position };
|
|
6767
|
+
}
|
|
6768
|
+
}
|
|
6769
|
+
}
|
|
6770
|
+
return best;
|
|
6771
|
+
}
|
|
6772
|
+
function computeSnapGuides(moving, targets, threshold) {
|
|
6773
|
+
const xSnap = bestAxisSnap(xAnchors(moving), targets, xAnchors, threshold);
|
|
6774
|
+
const ySnap = bestAxisSnap(yAnchors(moving), targets, yAnchors, threshold);
|
|
6775
|
+
const guides = [];
|
|
6776
|
+
if (xSnap) guides.push({ axis: "x", position: xSnap.position });
|
|
6777
|
+
if (ySnap) guides.push({ axis: "y", position: ySnap.position });
|
|
6778
|
+
return { dx: xSnap?.delta ?? 0, dy: ySnap?.delta ?? 0, guides };
|
|
6779
|
+
}
|
|
6780
|
+
|
|
6659
6781
|
// src/tools/select-tool.ts
|
|
6660
6782
|
var HANDLE_SIZE = 8;
|
|
6783
|
+
var SNAP_PX = 6;
|
|
6661
6784
|
var HANDLE_HIT_PADDING2 = 4;
|
|
6662
6785
|
var SELECTION_PAD = 4;
|
|
6663
6786
|
var MIN_ELEMENT_SIZE = 20;
|
|
@@ -6677,6 +6800,9 @@ var SelectTool = class {
|
|
|
6677
6800
|
ctx = null;
|
|
6678
6801
|
pendingSingleSelectId = null;
|
|
6679
6802
|
hasDragged = false;
|
|
6803
|
+
activeGuides = [];
|
|
6804
|
+
dragSnapTargets = null;
|
|
6805
|
+
dragVisibleRect = null;
|
|
6680
6806
|
resizeAspectRatio = 0;
|
|
6681
6807
|
hoveredId = null;
|
|
6682
6808
|
get selectedIds() {
|
|
@@ -6708,6 +6834,9 @@ var SelectTool = class {
|
|
|
6708
6834
|
this.setSelectedIds([]);
|
|
6709
6835
|
this.mode = { type: "idle" };
|
|
6710
6836
|
this.hoveredId = null;
|
|
6837
|
+
this.activeGuides = [];
|
|
6838
|
+
this.dragSnapTargets = null;
|
|
6839
|
+
this.dragVisibleRect = null;
|
|
6711
6840
|
ctx.setCursor?.("default");
|
|
6712
6841
|
}
|
|
6713
6842
|
snap(point, ctx) {
|
|
@@ -6716,6 +6845,8 @@ var SelectTool = class {
|
|
|
6716
6845
|
onPointerDown(state, ctx) {
|
|
6717
6846
|
this.ctx = ctx;
|
|
6718
6847
|
this.setHovered(null, ctx);
|
|
6848
|
+
this.dragSnapTargets = null;
|
|
6849
|
+
this.dragVisibleRect = null;
|
|
6719
6850
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
6720
6851
|
this.lastWorld = this.snap(world, ctx);
|
|
6721
6852
|
this.currentWorld = world;
|
|
@@ -6759,18 +6890,20 @@ var SelectTool = class {
|
|
|
6759
6890
|
this.hasDragged = false;
|
|
6760
6891
|
const hit = this.hitTest(world, ctx);
|
|
6761
6892
|
if (hit) {
|
|
6893
|
+
const all = ctx.store.getAll();
|
|
6762
6894
|
const alreadySelected = this._selectedIds.includes(hit.id);
|
|
6763
6895
|
if (state.shiftKey) {
|
|
6764
6896
|
if (alreadySelected) {
|
|
6765
|
-
|
|
6897
|
+
const grp = new Set(expandToGroups([hit.id], all));
|
|
6898
|
+
this.setSelectedIds(this._selectedIds.filter((id) => !grp.has(id)));
|
|
6766
6899
|
this.mode = { type: "idle" };
|
|
6767
6900
|
} else {
|
|
6768
|
-
this.setSelectedIds([...this._selectedIds, hit.id]);
|
|
6901
|
+
this.setSelectedIds(expandToGroups([...this._selectedIds, hit.id], all));
|
|
6769
6902
|
this.mode = hit.locked ? { type: "idle" } : { type: "dragging" };
|
|
6770
6903
|
}
|
|
6771
6904
|
} else {
|
|
6772
6905
|
if (!alreadySelected) {
|
|
6773
|
-
this.setSelectedIds([hit.id]);
|
|
6906
|
+
this.setSelectedIds(expandToGroups([hit.id], all));
|
|
6774
6907
|
} else if (this._selectedIds.length > 1) {
|
|
6775
6908
|
this.pendingSingleSelectId = hit.id;
|
|
6776
6909
|
}
|
|
@@ -6816,6 +6949,31 @@ var SelectTool = class {
|
|
|
6816
6949
|
const dx = snapped.x - this.lastWorld.x;
|
|
6817
6950
|
const dy = snapped.y - this.lastWorld.y;
|
|
6818
6951
|
this.lastWorld = snapped;
|
|
6952
|
+
let adjDx = dx;
|
|
6953
|
+
let adjDy = dy;
|
|
6954
|
+
this.activeGuides = [];
|
|
6955
|
+
if (ctx.smartGuides) {
|
|
6956
|
+
if (this.dragSnapTargets === null) {
|
|
6957
|
+
const selSet = new Set(this._selectedIds);
|
|
6958
|
+
this.dragVisibleRect = ctx.getVisibleRect?.() ?? null;
|
|
6959
|
+
const candidates = (this.dragVisibleRect ? ctx.store.queryRect(this.dragVisibleRect) : ctx.store.getAll()).filter((el) => !selSet.has(el.id) && el.type !== "grid");
|
|
6960
|
+
const targets = [];
|
|
6961
|
+
for (const el of candidates) {
|
|
6962
|
+
const b = getElementBounds(el);
|
|
6963
|
+
if (b) targets.push(b);
|
|
6964
|
+
}
|
|
6965
|
+
this.dragSnapTargets = targets;
|
|
6966
|
+
}
|
|
6967
|
+
const selectedEls = this._selectedIds.map((id) => ctx.store.getById(id)).filter((el) => !!el && !el.locked);
|
|
6968
|
+
const base = getElementsBoundingBox(selectedEls);
|
|
6969
|
+
if (base) {
|
|
6970
|
+
const moving = { x: base.x + dx, y: base.y + dy, w: base.w, h: base.h };
|
|
6971
|
+
const res = computeSnapGuides(moving, this.dragSnapTargets, SNAP_PX / ctx.camera.zoom);
|
|
6972
|
+
adjDx = dx + res.dx;
|
|
6973
|
+
adjDy = dy + res.dy;
|
|
6974
|
+
this.activeGuides = res.guides;
|
|
6975
|
+
}
|
|
6976
|
+
}
|
|
6819
6977
|
for (const id of this._selectedIds) {
|
|
6820
6978
|
const el = ctx.store.getById(id);
|
|
6821
6979
|
if (!el || el.locked) continue;
|
|
@@ -6824,13 +6982,13 @@ var SelectTool = class {
|
|
|
6824
6982
|
continue;
|
|
6825
6983
|
}
|
|
6826
6984
|
ctx.store.update(id, {
|
|
6827
|
-
position: { x: el.position.x +
|
|
6828
|
-
from: { x: el.from.x +
|
|
6829
|
-
to: { x: el.to.x +
|
|
6985
|
+
position: { x: el.position.x + adjDx, y: el.position.y + adjDy },
|
|
6986
|
+
from: { x: el.from.x + adjDx, y: el.from.y + adjDy },
|
|
6987
|
+
to: { x: el.to.x + adjDx, y: el.to.y + adjDy }
|
|
6830
6988
|
});
|
|
6831
|
-
} else if (ctx.gridType && "size" in el) {
|
|
6832
|
-
const centerX = el.position.x + el.size.w / 2 +
|
|
6833
|
-
const centerY = el.position.y + el.size.h / 2 +
|
|
6989
|
+
} else if (!ctx.smartGuides && ctx.gridType && "size" in el) {
|
|
6990
|
+
const centerX = el.position.x + el.size.w / 2 + adjDx;
|
|
6991
|
+
const centerY = el.position.y + el.size.h / 2 + adjDy;
|
|
6834
6992
|
const snappedCenter = this.snap({ x: centerX, y: centerY }, ctx);
|
|
6835
6993
|
ctx.store.update(id, {
|
|
6836
6994
|
position: {
|
|
@@ -6840,7 +6998,7 @@ var SelectTool = class {
|
|
|
6840
6998
|
});
|
|
6841
6999
|
} else {
|
|
6842
7000
|
ctx.store.update(id, {
|
|
6843
|
-
position: { x: el.position.x +
|
|
7001
|
+
position: { x: el.position.x + adjDx, y: el.position.y + adjDy }
|
|
6844
7002
|
});
|
|
6845
7003
|
}
|
|
6846
7004
|
}
|
|
@@ -6859,17 +7017,21 @@ var SelectTool = class {
|
|
|
6859
7017
|
if (this.mode.type === "marquee") {
|
|
6860
7018
|
const rect = this.getMarqueeRect();
|
|
6861
7019
|
if (rect) {
|
|
6862
|
-
this.setSelectedIds(this.findElementsInRect(rect, ctx));
|
|
7020
|
+
this.setSelectedIds(expandToGroups(this.findElementsInRect(rect, ctx), ctx.store.getAll()));
|
|
6863
7021
|
}
|
|
6864
7022
|
ctx.requestRender();
|
|
6865
7023
|
}
|
|
6866
7024
|
if (!this.hasDragged && this.pendingSingleSelectId !== null) {
|
|
6867
|
-
this.setSelectedIds([this.pendingSingleSelectId]);
|
|
7025
|
+
this.setSelectedIds(expandToGroups([this.pendingSingleSelectId], ctx.store.getAll()));
|
|
6868
7026
|
}
|
|
6869
7027
|
this.pendingSingleSelectId = null;
|
|
6870
7028
|
this.hasDragged = false;
|
|
6871
7029
|
const resizedNoteId = this.mode.type === "resizing" ? this.mode.elementId : null;
|
|
6872
7030
|
this.mode = { type: "idle" };
|
|
7031
|
+
this.activeGuides = [];
|
|
7032
|
+
this.dragSnapTargets = null;
|
|
7033
|
+
this.dragVisibleRect = null;
|
|
7034
|
+
ctx.requestRender();
|
|
6873
7035
|
ctx.setCursor?.("default");
|
|
6874
7036
|
if (resizedNoteId !== null) {
|
|
6875
7037
|
const el = ctx.store.getById(resizedNoteId);
|
|
@@ -6917,6 +7079,32 @@ var SelectTool = class {
|
|
|
6917
7079
|
}
|
|
6918
7080
|
}
|
|
6919
7081
|
}
|
|
7082
|
+
this.renderGuideLines(canvasCtx);
|
|
7083
|
+
}
|
|
7084
|
+
renderGuideLines(canvasCtx) {
|
|
7085
|
+
if (this.mode.type !== "dragging" || !this.ctx || this.activeGuides.length === 0) return;
|
|
7086
|
+
const zoom = this.ctx.camera.zoom;
|
|
7087
|
+
const rect = this.dragVisibleRect;
|
|
7088
|
+
canvasCtx.save();
|
|
7089
|
+
canvasCtx.strokeStyle = "#FF4081";
|
|
7090
|
+
canvasCtx.lineWidth = 1 / zoom;
|
|
7091
|
+
canvasCtx.setLineDash([]);
|
|
7092
|
+
for (const g of this.activeGuides) {
|
|
7093
|
+
canvasCtx.beginPath();
|
|
7094
|
+
if (g.axis === "x") {
|
|
7095
|
+
const y0 = rect ? rect.y : this.currentWorld.y - 1e5;
|
|
7096
|
+
const y1 = rect ? rect.y + rect.h : this.currentWorld.y + 1e5;
|
|
7097
|
+
canvasCtx.moveTo(g.position, y0);
|
|
7098
|
+
canvasCtx.lineTo(g.position, y1);
|
|
7099
|
+
} else {
|
|
7100
|
+
const x0 = rect ? rect.x : this.currentWorld.x - 1e5;
|
|
7101
|
+
const x1 = rect ? rect.x + rect.w : this.currentWorld.x + 1e5;
|
|
7102
|
+
canvasCtx.moveTo(x0, g.position);
|
|
7103
|
+
canvasCtx.lineTo(x1, g.position);
|
|
7104
|
+
}
|
|
7105
|
+
canvasCtx.stroke();
|
|
7106
|
+
}
|
|
7107
|
+
canvasCtx.restore();
|
|
6920
7108
|
}
|
|
6921
7109
|
updateArrowsBoundTo(ids, ctx) {
|
|
6922
7110
|
updateArrowsBoundToElements(ids, ctx.store);
|
|
@@ -8119,7 +8307,7 @@ var TemplateTool = class {
|
|
|
8119
8307
|
};
|
|
8120
8308
|
|
|
8121
8309
|
// src/index.ts
|
|
8122
|
-
var VERSION = "0.
|
|
8310
|
+
var VERSION = "0.34.0";
|
|
8123
8311
|
export {
|
|
8124
8312
|
ArrowTool,
|
|
8125
8313
|
AutoSave,
|