@fieldnotes/core 0.24.0 → 0.26.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 +8 -1
- package/dist/index.cjs +779 -813
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -369
- package/dist/index.d.ts +40 -369
- package/dist/index.js +778 -781
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -20,50 +20,30 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
-
AddElementCommand: () => AddElementCommand,
|
|
24
23
|
ArrowTool: () => ArrowTool,
|
|
25
24
|
AutoSave: () => AutoSave,
|
|
26
|
-
Background: () => Background,
|
|
27
|
-
BatchCommand: () => BatchCommand,
|
|
28
25
|
Camera: () => Camera,
|
|
29
|
-
CreateLayerCommand: () => CreateLayerCommand,
|
|
30
|
-
DEFAULT_FONT_SIZE_PRESETS: () => DEFAULT_FONT_SIZE_PRESETS,
|
|
31
26
|
DEFAULT_NOTE_FONT_SIZE: () => DEFAULT_NOTE_FONT_SIZE,
|
|
32
|
-
DoubleTapDetector: () => DoubleTapDetector,
|
|
33
|
-
ElementRenderer: () => ElementRenderer,
|
|
34
27
|
ElementStore: () => ElementStore,
|
|
35
28
|
EraserTool: () => EraserTool,
|
|
36
|
-
EventBus: () => EventBus,
|
|
37
29
|
HandTool: () => HandTool,
|
|
38
|
-
HistoryRecorder: () => HistoryRecorder,
|
|
39
30
|
HistoryStack: () => HistoryStack,
|
|
40
31
|
ImageTool: () => ImageTool,
|
|
41
|
-
InputFilter: () => InputFilter,
|
|
42
|
-
InputHandler: () => InputHandler,
|
|
43
32
|
LayerManager: () => LayerManager,
|
|
44
33
|
MeasureTool: () => MeasureTool,
|
|
45
|
-
NoteEditor: () => NoteEditor,
|
|
46
34
|
NoteTool: () => NoteTool,
|
|
47
|
-
NoteToolbar: () => NoteToolbar,
|
|
48
35
|
PencilTool: () => PencilTool,
|
|
49
|
-
Quadtree: () => Quadtree,
|
|
50
|
-
RemoveElementCommand: () => RemoveElementCommand,
|
|
51
|
-
RemoveLayerCommand: () => RemoveLayerCommand,
|
|
52
36
|
SelectTool: () => SelectTool,
|
|
53
37
|
ShapeTool: () => ShapeTool,
|
|
54
38
|
TemplateTool: () => TemplateTool,
|
|
55
39
|
TextTool: () => TextTool,
|
|
56
40
|
ToolManager: () => ToolManager,
|
|
57
|
-
UpdateElementCommand: () => UpdateElementCommand,
|
|
58
|
-
UpdateLayerCommand: () => UpdateLayerCommand,
|
|
59
41
|
VERSION: () => VERSION,
|
|
60
42
|
Viewport: () => Viewport,
|
|
61
43
|
boundsIntersect: () => boundsIntersect,
|
|
62
|
-
clearStaleBindings: () => clearStaleBindings,
|
|
63
44
|
createArrow: () => createArrow,
|
|
64
45
|
createGrid: () => createGrid,
|
|
65
46
|
createHtmlElement: () => createHtmlElement,
|
|
66
|
-
createId: () => createId,
|
|
67
47
|
createImage: () => createImage,
|
|
68
48
|
createNote: () => createNote,
|
|
69
49
|
createShape: () => createShape,
|
|
@@ -72,29 +52,20 @@ __export(index_exports, {
|
|
|
72
52
|
createText: () => createText,
|
|
73
53
|
drawHexPath: () => drawHexPath,
|
|
74
54
|
exportImage: () => exportImage,
|
|
75
|
-
exportState: () => exportState,
|
|
76
|
-
findBindTarget: () => findBindTarget,
|
|
77
|
-
findBoundArrows: () => findBoundArrows,
|
|
78
55
|
getActiveFormats: () => getActiveFormats,
|
|
79
56
|
getArrowBounds: () => getArrowBounds,
|
|
80
57
|
getArrowControlPoint: () => getArrowControlPoint,
|
|
81
58
|
getArrowMidpoint: () => getArrowMidpoint,
|
|
82
59
|
getArrowTangentAngle: () => getArrowTangentAngle,
|
|
83
60
|
getBendFromPoint: () => getBendFromPoint,
|
|
84
|
-
getEdgeIntersection: () => getEdgeIntersection,
|
|
85
61
|
getElementBounds: () => getElementBounds,
|
|
86
|
-
getElementCenter: () => getElementCenter,
|
|
87
62
|
getElementsBoundingBox: () => getElementsBoundingBox,
|
|
88
63
|
getHexCellsInCone: () => getHexCellsInCone,
|
|
89
64
|
getHexCellsInLine: () => getHexCellsInLine,
|
|
90
65
|
getHexCellsInRadius: () => getHexCellsInRadius,
|
|
91
66
|
getHexCellsInSquare: () => getHexCellsInSquare,
|
|
92
67
|
getHexDistance: () => getHexDistance,
|
|
93
|
-
isBindable: () => isBindable,
|
|
94
68
|
isNearBezier: () => isNearBezier,
|
|
95
|
-
isNoteContentEmpty: () => isNoteContentEmpty,
|
|
96
|
-
parseState: () => parseState,
|
|
97
|
-
sanitizeNoteHtml: () => sanitizeNoteHtml,
|
|
98
69
|
setFontSize: () => setFontSize,
|
|
99
70
|
smartSnap: () => smartSnap,
|
|
100
71
|
snapPoint: () => snapPoint,
|
|
@@ -102,188 +73,41 @@ __export(index_exports, {
|
|
|
102
73
|
toggleBold: () => toggleBold,
|
|
103
74
|
toggleItalic: () => toggleItalic,
|
|
104
75
|
toggleStrikethrough: () => toggleStrikethrough,
|
|
105
|
-
toggleUnderline: () => toggleUnderline
|
|
106
|
-
unbindArrow: () => unbindArrow,
|
|
107
|
-
updateBoundArrow: () => updateBoundArrow
|
|
76
|
+
toggleUnderline: () => toggleUnderline
|
|
108
77
|
});
|
|
109
78
|
module.exports = __toCommonJS(index_exports);
|
|
110
79
|
|
|
111
|
-
// src/core/
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
existing.add(listener);
|
|
118
|
-
} else {
|
|
119
|
-
const set = /* @__PURE__ */ new Set([listener]);
|
|
120
|
-
this.listeners.set(event, set);
|
|
121
|
-
}
|
|
122
|
-
return () => this.off(event, listener);
|
|
123
|
-
}
|
|
124
|
-
off(event, listener) {
|
|
125
|
-
this.listeners.get(event)?.delete(listener);
|
|
126
|
-
}
|
|
127
|
-
emit(event, data) {
|
|
128
|
-
this.listeners.get(event)?.forEach((listener) => {
|
|
129
|
-
try {
|
|
130
|
-
listener(data);
|
|
131
|
-
} catch (err) {
|
|
132
|
-
console.error(`[fieldnotes] listener error for "${String(event)}"`, err);
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
clear() {
|
|
137
|
-
this.listeners.clear();
|
|
138
|
-
}
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
// src/core/quadtree.ts
|
|
142
|
-
var MAX_ITEMS = 8;
|
|
143
|
-
var MAX_DEPTH = 8;
|
|
144
|
-
function intersects(a, b) {
|
|
145
|
-
return a.x <= b.x + b.w && a.x + a.w >= b.x && a.y <= b.y + b.h && a.y + a.h >= b.y;
|
|
80
|
+
// src/core/snap.ts
|
|
81
|
+
function snapPoint(point, gridSize) {
|
|
82
|
+
return {
|
|
83
|
+
x: Math.round(point.x / gridSize) * gridSize || 0,
|
|
84
|
+
y: Math.round(point.y / gridSize) * gridSize || 0
|
|
85
|
+
};
|
|
146
86
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
this.items.push(entry);
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
this.items.push(entry);
|
|
166
|
-
if (this.items.length > MAX_ITEMS && this.depth < MAX_DEPTH) {
|
|
167
|
-
this.split();
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
remove(id) {
|
|
171
|
-
const idx = this.items.findIndex((e) => e.id === id);
|
|
172
|
-
if (idx !== -1) {
|
|
173
|
-
this.items.splice(idx, 1);
|
|
174
|
-
return true;
|
|
175
|
-
}
|
|
176
|
-
if (this.children) {
|
|
177
|
-
for (const child of this.children) {
|
|
178
|
-
if (child.remove(id)) {
|
|
179
|
-
this.collapseIfEmpty();
|
|
180
|
-
return true;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
return false;
|
|
185
|
-
}
|
|
186
|
-
query(rect, result) {
|
|
187
|
-
if (!intersects(this.bounds, rect)) return;
|
|
188
|
-
for (const item of this.items) {
|
|
189
|
-
if (intersects(item.bounds, rect)) {
|
|
190
|
-
result.push(item.id);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
if (this.children) {
|
|
194
|
-
for (const child of this.children) {
|
|
195
|
-
child.query(rect, result);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
getChildIndex(itemBounds) {
|
|
200
|
-
const midX = this.bounds.x + this.bounds.w / 2;
|
|
201
|
-
const midY = this.bounds.y + this.bounds.h / 2;
|
|
202
|
-
const left = itemBounds.x >= this.bounds.x && itemBounds.x + itemBounds.w <= midX;
|
|
203
|
-
const right = itemBounds.x >= midX && itemBounds.x + itemBounds.w <= this.bounds.x + this.bounds.w;
|
|
204
|
-
const top = itemBounds.y >= this.bounds.y && itemBounds.y + itemBounds.h <= midY;
|
|
205
|
-
const bottom = itemBounds.y >= midY && itemBounds.y + itemBounds.h <= this.bounds.y + this.bounds.h;
|
|
206
|
-
if (left && top) return 0;
|
|
207
|
-
if (right && top) return 1;
|
|
208
|
-
if (left && bottom) return 2;
|
|
209
|
-
if (right && bottom) return 3;
|
|
210
|
-
return -1;
|
|
211
|
-
}
|
|
212
|
-
split() {
|
|
213
|
-
const { x, y, w, h } = this.bounds;
|
|
214
|
-
const halfW = w / 2;
|
|
215
|
-
const halfH = h / 2;
|
|
216
|
-
const d = this.depth + 1;
|
|
217
|
-
this.children = [
|
|
218
|
-
new _QuadNode({ x, y, w: halfW, h: halfH }, d),
|
|
219
|
-
new _QuadNode({ x: x + halfW, y, w: halfW, h: halfH }, d),
|
|
220
|
-
new _QuadNode({ x, y: y + halfH, w: halfW, h: halfH }, d),
|
|
221
|
-
new _QuadNode({ x: x + halfW, y: y + halfH, w: halfW, h: halfH }, d)
|
|
222
|
-
];
|
|
223
|
-
const remaining = [];
|
|
224
|
-
for (const item of this.items) {
|
|
225
|
-
const idx = this.getChildIndex(item.bounds);
|
|
226
|
-
if (idx !== -1) {
|
|
227
|
-
const target = this.children[idx];
|
|
228
|
-
if (target) target.insert(item);
|
|
229
|
-
} else {
|
|
230
|
-
remaining.push(item);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
this.items = remaining;
|
|
234
|
-
}
|
|
235
|
-
collapseIfEmpty() {
|
|
236
|
-
if (!this.children) return;
|
|
237
|
-
let totalItems = this.items.length;
|
|
238
|
-
for (const child of this.children) {
|
|
239
|
-
if (child.children) return;
|
|
240
|
-
totalItems += child.items.length;
|
|
241
|
-
}
|
|
242
|
-
if (totalItems <= MAX_ITEMS) {
|
|
243
|
-
for (const child of this.children) {
|
|
244
|
-
this.items.push(...child.items);
|
|
245
|
-
}
|
|
246
|
-
this.children = null;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
};
|
|
250
|
-
var Quadtree = class {
|
|
251
|
-
root;
|
|
252
|
-
_size = 0;
|
|
253
|
-
worldBounds;
|
|
254
|
-
constructor(worldBounds) {
|
|
255
|
-
this.worldBounds = worldBounds;
|
|
256
|
-
this.root = new QuadNode(worldBounds, 0);
|
|
257
|
-
}
|
|
258
|
-
get size() {
|
|
259
|
-
return this._size;
|
|
260
|
-
}
|
|
261
|
-
insert(id, bounds) {
|
|
262
|
-
this.root.insert({ id, bounds });
|
|
263
|
-
this._size++;
|
|
264
|
-
}
|
|
265
|
-
remove(id) {
|
|
266
|
-
if (this.root.remove(id)) {
|
|
267
|
-
this._size--;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
update(id, newBounds) {
|
|
271
|
-
this.remove(id);
|
|
272
|
-
this.insert(id, newBounds);
|
|
273
|
-
}
|
|
274
|
-
query(rect) {
|
|
275
|
-
const result = [];
|
|
276
|
-
this.root.query(rect, result);
|
|
277
|
-
return result;
|
|
278
|
-
}
|
|
279
|
-
queryPoint(point) {
|
|
280
|
-
return this.query({ x: point.x, y: point.y, w: 0, h: 0 });
|
|
87
|
+
function snapToHexCenter(point, cellSize, orientation) {
|
|
88
|
+
if (orientation === "pointy") {
|
|
89
|
+
const hexW = Math.sqrt(3) * cellSize;
|
|
90
|
+
const rowH = 1.5 * cellSize;
|
|
91
|
+
const row = Math.round(point.y / rowH);
|
|
92
|
+
const offsetX = row % 2 !== 0 ? hexW / 2 : 0;
|
|
93
|
+
const col = Math.round((point.x - offsetX) / hexW);
|
|
94
|
+
return { x: col * hexW + offsetX || 0, y: row * rowH || 0 };
|
|
95
|
+
} else {
|
|
96
|
+
const hexH = Math.sqrt(3) * cellSize;
|
|
97
|
+
const colW = 1.5 * cellSize;
|
|
98
|
+
const col = Math.round(point.x / colW);
|
|
99
|
+
const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
100
|
+
const row = Math.round((point.y - offsetY) / hexH);
|
|
101
|
+
return { x: col * colW || 0, y: row * hexH + offsetY || 0 };
|
|
281
102
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
103
|
+
}
|
|
104
|
+
function smartSnap(point, ctx) {
|
|
105
|
+
if (!ctx.snapToGrid || !ctx.gridSize) return point;
|
|
106
|
+
if (ctx.gridType === "hex" && ctx.hexOrientation) {
|
|
107
|
+
return snapToHexCenter(point, ctx.gridSize, ctx.hexOrientation);
|
|
285
108
|
}
|
|
286
|
-
|
|
109
|
+
return snapPoint(point, ctx.gridSize);
|
|
110
|
+
}
|
|
287
111
|
|
|
288
112
|
// src/elements/note-sanitizer.ts
|
|
289
113
|
var BOLD_TAGS = /* @__PURE__ */ new Set(["b", "strong"]);
|
|
@@ -540,38 +364,6 @@ function migrateElement(obj) {
|
|
|
540
364
|
}
|
|
541
365
|
}
|
|
542
366
|
|
|
543
|
-
// src/core/snap.ts
|
|
544
|
-
function snapPoint(point, gridSize) {
|
|
545
|
-
return {
|
|
546
|
-
x: Math.round(point.x / gridSize) * gridSize || 0,
|
|
547
|
-
y: Math.round(point.y / gridSize) * gridSize || 0
|
|
548
|
-
};
|
|
549
|
-
}
|
|
550
|
-
function snapToHexCenter(point, cellSize, orientation) {
|
|
551
|
-
if (orientation === "pointy") {
|
|
552
|
-
const hexW = Math.sqrt(3) * cellSize;
|
|
553
|
-
const rowH = 1.5 * cellSize;
|
|
554
|
-
const row = Math.round(point.y / rowH);
|
|
555
|
-
const offsetX = row % 2 !== 0 ? hexW / 2 : 0;
|
|
556
|
-
const col = Math.round((point.x - offsetX) / hexW);
|
|
557
|
-
return { x: col * hexW + offsetX || 0, y: row * rowH || 0 };
|
|
558
|
-
} else {
|
|
559
|
-
const hexH = Math.sqrt(3) * cellSize;
|
|
560
|
-
const colW = 1.5 * cellSize;
|
|
561
|
-
const col = Math.round(point.x / colW);
|
|
562
|
-
const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
563
|
-
const row = Math.round((point.y - offsetY) / hexH);
|
|
564
|
-
return { x: col * colW || 0, y: row * hexH + offsetY || 0 };
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
function smartSnap(point, ctx) {
|
|
568
|
-
if (!ctx.snapToGrid || !ctx.gridSize) return point;
|
|
569
|
-
if (ctx.gridType === "hex" && ctx.hexOrientation) {
|
|
570
|
-
return snapToHexCenter(point, ctx.gridSize, ctx.hexOrientation);
|
|
571
|
-
}
|
|
572
|
-
return snapPoint(point, ctx.gridSize);
|
|
573
|
-
}
|
|
574
|
-
|
|
575
367
|
// src/core/auto-save.ts
|
|
576
368
|
var DEFAULT_KEY = "fieldnotes-autosave";
|
|
577
369
|
var DEFAULT_DEBOUNCE_MS = 1e3;
|
|
@@ -735,143 +527,6 @@ var Camera = class {
|
|
|
735
527
|
}
|
|
736
528
|
};
|
|
737
529
|
|
|
738
|
-
// src/canvas/background.ts
|
|
739
|
-
var MIN_PATTERN_SPACING = 16;
|
|
740
|
-
var DEFAULTS = {
|
|
741
|
-
pattern: "dots",
|
|
742
|
-
spacing: 24,
|
|
743
|
-
color: "#d0d0d0",
|
|
744
|
-
dotRadius: 1,
|
|
745
|
-
lineWidth: 0.5
|
|
746
|
-
};
|
|
747
|
-
var Background = class {
|
|
748
|
-
pattern;
|
|
749
|
-
spacing;
|
|
750
|
-
color;
|
|
751
|
-
dotRadius;
|
|
752
|
-
lineWidth;
|
|
753
|
-
cachedCanvas = null;
|
|
754
|
-
cachedCtx = null;
|
|
755
|
-
lastZoom = -1;
|
|
756
|
-
lastOffsetX = -Infinity;
|
|
757
|
-
lastOffsetY = -Infinity;
|
|
758
|
-
lastWidth = 0;
|
|
759
|
-
lastHeight = 0;
|
|
760
|
-
constructor(options = {}) {
|
|
761
|
-
this.pattern = options.pattern ?? DEFAULTS.pattern;
|
|
762
|
-
this.spacing = options.spacing ?? DEFAULTS.spacing;
|
|
763
|
-
this.color = options.color ?? DEFAULTS.color;
|
|
764
|
-
this.dotRadius = options.dotRadius ?? DEFAULTS.dotRadius;
|
|
765
|
-
this.lineWidth = options.lineWidth ?? DEFAULTS.lineWidth;
|
|
766
|
-
}
|
|
767
|
-
render(ctx, camera) {
|
|
768
|
-
const { width, height } = ctx.canvas;
|
|
769
|
-
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
770
|
-
const cssWidth = width / dpr;
|
|
771
|
-
const cssHeight = height / dpr;
|
|
772
|
-
ctx.save();
|
|
773
|
-
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
774
|
-
ctx.clearRect(0, 0, cssWidth, cssHeight);
|
|
775
|
-
if (this.pattern === "none") {
|
|
776
|
-
ctx.restore();
|
|
777
|
-
return;
|
|
778
|
-
}
|
|
779
|
-
const spacing = this.adaptSpacing(this.spacing, camera.zoom);
|
|
780
|
-
const keyZoom = camera.zoom;
|
|
781
|
-
const keyX = Math.floor(camera.position.x % spacing);
|
|
782
|
-
const keyY = Math.floor(camera.position.y % spacing);
|
|
783
|
-
if (this.cachedCanvas !== null && keyZoom === this.lastZoom && keyX === this.lastOffsetX && keyY === this.lastOffsetY && cssWidth === this.lastWidth && cssHeight === this.lastHeight) {
|
|
784
|
-
ctx.drawImage(this.cachedCanvas, 0, 0);
|
|
785
|
-
ctx.restore();
|
|
786
|
-
return;
|
|
787
|
-
}
|
|
788
|
-
this.ensureCachedCanvas(cssWidth, cssHeight, dpr);
|
|
789
|
-
if (this.cachedCtx === null) {
|
|
790
|
-
if (this.pattern === "dots") {
|
|
791
|
-
this.renderDots(ctx, camera, cssWidth, cssHeight);
|
|
792
|
-
} else if (this.pattern === "grid") {
|
|
793
|
-
this.renderGrid(ctx, camera, cssWidth, cssHeight);
|
|
794
|
-
}
|
|
795
|
-
ctx.restore();
|
|
796
|
-
return;
|
|
797
|
-
}
|
|
798
|
-
const offCtx = this.cachedCtx;
|
|
799
|
-
offCtx.clearRect(0, 0, cssWidth, cssHeight);
|
|
800
|
-
if (this.pattern === "dots") {
|
|
801
|
-
this.renderDots(offCtx, camera, cssWidth, cssHeight);
|
|
802
|
-
} else if (this.pattern === "grid") {
|
|
803
|
-
this.renderGrid(offCtx, camera, cssWidth, cssHeight);
|
|
804
|
-
}
|
|
805
|
-
this.lastZoom = keyZoom;
|
|
806
|
-
this.lastOffsetX = keyX;
|
|
807
|
-
this.lastOffsetY = keyY;
|
|
808
|
-
this.lastWidth = cssWidth;
|
|
809
|
-
this.lastHeight = cssHeight;
|
|
810
|
-
ctx.drawImage(this.cachedCanvas, 0, 0);
|
|
811
|
-
ctx.restore();
|
|
812
|
-
}
|
|
813
|
-
ensureCachedCanvas(cssWidth, cssHeight, dpr) {
|
|
814
|
-
if (this.cachedCanvas !== null && this.lastWidth === cssWidth && this.lastHeight === cssHeight) {
|
|
815
|
-
return;
|
|
816
|
-
}
|
|
817
|
-
const physWidth = Math.round(cssWidth * dpr);
|
|
818
|
-
const physHeight = Math.round(cssHeight * dpr);
|
|
819
|
-
if (typeof OffscreenCanvas !== "undefined") {
|
|
820
|
-
this.cachedCanvas = new OffscreenCanvas(physWidth, physHeight);
|
|
821
|
-
} else if (typeof document !== "undefined") {
|
|
822
|
-
const el = document.createElement("canvas");
|
|
823
|
-
el.width = physWidth;
|
|
824
|
-
el.height = physHeight;
|
|
825
|
-
this.cachedCanvas = el;
|
|
826
|
-
} else {
|
|
827
|
-
this.cachedCanvas = null;
|
|
828
|
-
this.cachedCtx = null;
|
|
829
|
-
return;
|
|
830
|
-
}
|
|
831
|
-
const offCtx = this.cachedCanvas.getContext("2d");
|
|
832
|
-
if (offCtx !== null) {
|
|
833
|
-
offCtx.scale(dpr, dpr);
|
|
834
|
-
}
|
|
835
|
-
this.cachedCtx = offCtx;
|
|
836
|
-
this.lastZoom = -1;
|
|
837
|
-
}
|
|
838
|
-
adaptSpacing(baseSpacing, zoom) {
|
|
839
|
-
let spacing = baseSpacing * zoom;
|
|
840
|
-
while (spacing < MIN_PATTERN_SPACING) {
|
|
841
|
-
spacing *= 2;
|
|
842
|
-
}
|
|
843
|
-
return spacing;
|
|
844
|
-
}
|
|
845
|
-
renderDots(ctx, camera, width, height) {
|
|
846
|
-
const spacing = this.adaptSpacing(this.spacing, camera.zoom);
|
|
847
|
-
const offsetX = camera.position.x % spacing;
|
|
848
|
-
const offsetY = camera.position.y % spacing;
|
|
849
|
-
const radius = this.dotRadius * Math.min(camera.zoom, 2);
|
|
850
|
-
ctx.fillStyle = this.color;
|
|
851
|
-
ctx.beginPath();
|
|
852
|
-
for (let x = offsetX; x < width; x += spacing) {
|
|
853
|
-
for (let y = offsetY; y < height; y += spacing) {
|
|
854
|
-
ctx.moveTo(x + radius, y);
|
|
855
|
-
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
ctx.fill();
|
|
859
|
-
}
|
|
860
|
-
renderGrid(ctx, camera, width, height) {
|
|
861
|
-
const spacing = this.adaptSpacing(this.spacing, camera.zoom);
|
|
862
|
-
const offsetX = camera.position.x % spacing;
|
|
863
|
-
const offsetY = camera.position.y % spacing;
|
|
864
|
-
const lineW = this.lineWidth * Math.min(camera.zoom, 2);
|
|
865
|
-
ctx.fillStyle = this.color;
|
|
866
|
-
for (let x = offsetX; x < width; x += spacing) {
|
|
867
|
-
ctx.fillRect(x, 0, lineW, height);
|
|
868
|
-
}
|
|
869
|
-
for (let y = offsetY; y < height; y += spacing) {
|
|
870
|
-
ctx.fillRect(0, y, width, lineW);
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
};
|
|
874
|
-
|
|
875
530
|
// src/canvas/input-filter.ts
|
|
876
531
|
var InputFilter = class _InputFilter {
|
|
877
532
|
activePenId = null;
|
|
@@ -933,62 +588,334 @@ function createId(prefix) {
|
|
|
933
588
|
return `${prefix}_${Date.now().toString(36)}_${(counter++).toString(36)}`;
|
|
934
589
|
}
|
|
935
590
|
|
|
936
|
-
// src/
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
dispose() {
|
|
946
|
-
this.flushPendingNudge();
|
|
591
|
+
// src/core/geometry.ts
|
|
592
|
+
function distSqToSegment(p, a, b) {
|
|
593
|
+
const abx = b.x - a.x;
|
|
594
|
+
const aby = b.y - a.y;
|
|
595
|
+
const apx = p.x - a.x;
|
|
596
|
+
const apy = p.y - a.y;
|
|
597
|
+
const lenSq = abx * abx + aby * aby;
|
|
598
|
+
if (lenSq === 0) {
|
|
599
|
+
return apx * apx + apy * apy;
|
|
947
600
|
}
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
601
|
+
const t = Math.max(0, Math.min(1, (apx * abx + apy * aby) / lenSq));
|
|
602
|
+
const dx = p.x - (a.x + t * abx);
|
|
603
|
+
const dy = p.y - (a.y + t * aby);
|
|
604
|
+
return dx * dx + dy * dy;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// src/elements/arrow-geometry.ts
|
|
608
|
+
function getArrowControlPoint(from, to, bend) {
|
|
609
|
+
const midX = (from.x + to.x) / 2;
|
|
610
|
+
const midY = (from.y + to.y) / 2;
|
|
611
|
+
if (bend === 0) return { x: midX, y: midY };
|
|
612
|
+
const dx = to.x - from.x;
|
|
613
|
+
const dy = to.y - from.y;
|
|
614
|
+
const len = Math.sqrt(dx * dx + dy * dy);
|
|
615
|
+
if (len === 0) return { x: midX, y: midY };
|
|
616
|
+
const perpX = -dy / len;
|
|
617
|
+
const perpY = dx / len;
|
|
618
|
+
return {
|
|
619
|
+
x: midX + perpX * bend,
|
|
620
|
+
y: midY + perpY * bend
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
function getArrowMidpoint(from, to, bend) {
|
|
624
|
+
const cp = getArrowControlPoint(from, to, bend);
|
|
625
|
+
return {
|
|
626
|
+
x: 0.25 * from.x + 0.5 * cp.x + 0.25 * to.x,
|
|
627
|
+
y: 0.25 * from.y + 0.5 * cp.y + 0.25 * to.y
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
function getBendFromPoint(from, to, dragPoint) {
|
|
631
|
+
const midX = (from.x + to.x) / 2;
|
|
632
|
+
const midY = (from.y + to.y) / 2;
|
|
633
|
+
const dx = to.x - from.x;
|
|
634
|
+
const dy = to.y - from.y;
|
|
635
|
+
const len = Math.sqrt(dx * dx + dy * dy);
|
|
636
|
+
if (len === 0) return 0;
|
|
637
|
+
const perpX = -dy / len;
|
|
638
|
+
const perpY = dx / len;
|
|
639
|
+
return (dragPoint.x - midX) * perpX + (dragPoint.y - midY) * perpY;
|
|
640
|
+
}
|
|
641
|
+
function getArrowTangentAngle(from, to, bend, t) {
|
|
642
|
+
const cp = getArrowControlPoint(from, to, bend);
|
|
643
|
+
const tangentX = 2 * (1 - t) * (cp.x - from.x) + 2 * t * (to.x - cp.x);
|
|
644
|
+
const tangentY = 2 * (1 - t) * (cp.y - from.y) + 2 * t * (to.y - cp.y);
|
|
645
|
+
return Math.atan2(tangentY, tangentX);
|
|
646
|
+
}
|
|
647
|
+
function isNearBezier(point, from, to, bend, threshold) {
|
|
648
|
+
if (bend === 0) return isNearLine(point, from, to, threshold);
|
|
649
|
+
const cp = getArrowControlPoint(from, to, bend);
|
|
650
|
+
const segments = 20;
|
|
651
|
+
for (let i = 0; i < segments; i++) {
|
|
652
|
+
const t0 = i / segments;
|
|
653
|
+
const t1 = (i + 1) / segments;
|
|
654
|
+
const a = bezierPoint(from, cp, to, t0);
|
|
655
|
+
const b = bezierPoint(from, cp, to, t1);
|
|
656
|
+
if (isNearLine(point, a, b, threshold)) return true;
|
|
955
657
|
}
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
const
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
}
|
|
969
|
-
const moved = sel.tool.nudgeSelection(dx * step, dy * step, sel.ctx);
|
|
970
|
-
this.nudgeTimer = setTimeout(() => this.flushPendingNudge(), 400);
|
|
971
|
-
return moved;
|
|
658
|
+
return false;
|
|
659
|
+
}
|
|
660
|
+
function getArrowBounds(from, to, bend) {
|
|
661
|
+
if (bend === 0) {
|
|
662
|
+
const minX2 = Math.min(from.x, to.x);
|
|
663
|
+
const minY2 = Math.min(from.y, to.y);
|
|
664
|
+
return {
|
|
665
|
+
x: minX2,
|
|
666
|
+
y: minY2,
|
|
667
|
+
w: Math.abs(to.x - from.x),
|
|
668
|
+
h: Math.abs(to.y - from.y)
|
|
669
|
+
};
|
|
972
670
|
}
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
671
|
+
const cp = getArrowControlPoint(from, to, bend);
|
|
672
|
+
const steps = 20;
|
|
673
|
+
let minX = Math.min(from.x, to.x);
|
|
674
|
+
let minY = Math.min(from.y, to.y);
|
|
675
|
+
let maxX = Math.max(from.x, to.x);
|
|
676
|
+
let maxY = Math.max(from.y, to.y);
|
|
677
|
+
for (let i = 1; i < steps; i++) {
|
|
678
|
+
const t = i / steps;
|
|
679
|
+
const p = bezierPoint(from, cp, to, t);
|
|
680
|
+
if (p.x < minX) minX = p.x;
|
|
681
|
+
if (p.y < minY) minY = p.y;
|
|
682
|
+
if (p.x > maxX) maxX = p.x;
|
|
683
|
+
if (p.y > maxY) maxY = p.y;
|
|
982
684
|
}
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
685
|
+
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
686
|
+
}
|
|
687
|
+
function bezierPoint(from, cp, to, t) {
|
|
688
|
+
const mt = 1 - t;
|
|
689
|
+
return {
|
|
690
|
+
x: mt * mt * from.x + 2 * mt * t * cp.x + t * t * to.x,
|
|
691
|
+
y: mt * mt * from.y + 2 * mt * t * cp.y + t * t * to.y
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
function isNearLine(point, a, b, threshold) {
|
|
695
|
+
return distSqToSegment(point, a, b) <= threshold * threshold;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// src/elements/element-bounds.ts
|
|
699
|
+
var strokeBoundsCache = /* @__PURE__ */ new WeakMap();
|
|
700
|
+
function getElementBounds(element) {
|
|
701
|
+
if (element.type === "grid") return null;
|
|
702
|
+
if ("size" in element) {
|
|
703
|
+
return {
|
|
704
|
+
x: element.position.x,
|
|
705
|
+
y: element.position.y,
|
|
706
|
+
w: element.size.w,
|
|
707
|
+
h: element.size.h
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
if (element.type === "stroke") {
|
|
711
|
+
if (element.points.length === 0) return null;
|
|
712
|
+
const cached = strokeBoundsCache.get(element);
|
|
713
|
+
if (cached) return cached;
|
|
714
|
+
let minX = Infinity;
|
|
715
|
+
let minY = Infinity;
|
|
716
|
+
let maxX = -Infinity;
|
|
717
|
+
let maxY = -Infinity;
|
|
718
|
+
for (const p of element.points) {
|
|
719
|
+
const px = p.x + element.position.x;
|
|
720
|
+
const py = p.y + element.position.y;
|
|
721
|
+
if (px < minX) minX = px;
|
|
722
|
+
if (py < minY) minY = py;
|
|
723
|
+
if (px > maxX) maxX = px;
|
|
724
|
+
if (py > maxY) maxY = py;
|
|
725
|
+
}
|
|
726
|
+
const bounds = { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
727
|
+
strokeBoundsCache.set(element, bounds);
|
|
728
|
+
return bounds;
|
|
729
|
+
}
|
|
730
|
+
if (element.type === "arrow") {
|
|
731
|
+
return getArrowBoundsAnalytical(element.from, element.to, element.bend);
|
|
732
|
+
}
|
|
733
|
+
if (element.type === "template") {
|
|
734
|
+
return getTemplateBounds(element);
|
|
735
|
+
}
|
|
736
|
+
return null;
|
|
737
|
+
}
|
|
738
|
+
function getArrowBoundsAnalytical(from, to, bend) {
|
|
739
|
+
if (bend === 0) {
|
|
740
|
+
const minX2 = Math.min(from.x, to.x);
|
|
741
|
+
const minY2 = Math.min(from.y, to.y);
|
|
742
|
+
return {
|
|
743
|
+
x: minX2,
|
|
744
|
+
y: minY2,
|
|
745
|
+
w: Math.abs(to.x - from.x),
|
|
746
|
+
h: Math.abs(to.y - from.y)
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
const cp = getArrowControlPoint(from, to, bend);
|
|
750
|
+
let minX = Math.min(from.x, to.x);
|
|
751
|
+
let maxX = Math.max(from.x, to.x);
|
|
752
|
+
let minY = Math.min(from.y, to.y);
|
|
753
|
+
let maxY = Math.max(from.y, to.y);
|
|
754
|
+
const tx = from.x - 2 * cp.x + to.x;
|
|
755
|
+
if (tx !== 0) {
|
|
756
|
+
const t = (from.x - cp.x) / tx;
|
|
757
|
+
if (t > 0 && t < 1) {
|
|
758
|
+
const mt = 1 - t;
|
|
759
|
+
const x = mt * mt * from.x + 2 * mt * t * cp.x + t * t * to.x;
|
|
760
|
+
if (x < minX) minX = x;
|
|
761
|
+
if (x > maxX) maxX = x;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
const ty = from.y - 2 * cp.y + to.y;
|
|
765
|
+
if (ty !== 0) {
|
|
766
|
+
const t = (from.y - cp.y) / ty;
|
|
767
|
+
if (t > 0 && t < 1) {
|
|
768
|
+
const mt = 1 - t;
|
|
769
|
+
const y = mt * mt * from.y + 2 * mt * t * cp.y + t * t * to.y;
|
|
770
|
+
if (y < minY) minY = y;
|
|
771
|
+
if (y > maxY) maxY = y;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
775
|
+
}
|
|
776
|
+
function getTemplateBounds(el) {
|
|
777
|
+
const { x: cx, y: cy } = el.position;
|
|
778
|
+
const r = el.radius;
|
|
779
|
+
switch (el.templateShape) {
|
|
780
|
+
case "circle":
|
|
781
|
+
return { x: cx - r, y: cy - r, w: 2 * r, h: 2 * r };
|
|
782
|
+
case "square":
|
|
783
|
+
return { x: cx - r / 2, y: cy - r / 2, w: r, h: r };
|
|
784
|
+
case "cone": {
|
|
785
|
+
const halfAngle = Math.atan(0.5);
|
|
786
|
+
const tipX = cx;
|
|
787
|
+
const tipY = cy;
|
|
788
|
+
const leftX = cx + r * Math.cos(el.angle - halfAngle);
|
|
789
|
+
const leftY = cy + r * Math.sin(el.angle - halfAngle);
|
|
790
|
+
const rightX = cx + r * Math.cos(el.angle + halfAngle);
|
|
791
|
+
const rightY = cy + r * Math.sin(el.angle + halfAngle);
|
|
792
|
+
const farX = cx + r * Math.cos(el.angle);
|
|
793
|
+
const farY = cy + r * Math.sin(el.angle);
|
|
794
|
+
const xs = [tipX, leftX, rightX, farX];
|
|
795
|
+
const ys = [tipY, leftY, rightY, farY];
|
|
796
|
+
let minX = Infinity;
|
|
797
|
+
let minY = Infinity;
|
|
798
|
+
let maxX = -Infinity;
|
|
799
|
+
let maxY = -Infinity;
|
|
800
|
+
for (let i = 0; i < xs.length; i++) {
|
|
801
|
+
const px = xs[i];
|
|
802
|
+
const py = ys[i];
|
|
803
|
+
if (px !== void 0 && px < minX) minX = px;
|
|
804
|
+
if (px !== void 0 && px > maxX) maxX = px;
|
|
805
|
+
if (py !== void 0 && py < minY) minY = py;
|
|
806
|
+
if (py !== void 0 && py > maxY) maxY = py;
|
|
807
|
+
}
|
|
808
|
+
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
809
|
+
}
|
|
810
|
+
case "line": {
|
|
811
|
+
const halfW = r / 12;
|
|
812
|
+
const cos = Math.cos(el.angle);
|
|
813
|
+
const sin = Math.sin(el.angle);
|
|
814
|
+
const perpX = -sin * halfW;
|
|
815
|
+
const perpY = cos * halfW;
|
|
816
|
+
const x0 = cx + perpX;
|
|
817
|
+
const y0 = cy + perpY;
|
|
818
|
+
const x1 = cx + r * cos + perpX;
|
|
819
|
+
const y1 = cy + r * sin + perpY;
|
|
820
|
+
const x2 = cx + r * cos - perpX;
|
|
821
|
+
const y2 = cy + r * sin - perpY;
|
|
822
|
+
const x3 = cx - perpX;
|
|
823
|
+
const y3 = cy - perpY;
|
|
824
|
+
const minX = Math.min(x0, x1, x2, x3);
|
|
825
|
+
const minY = Math.min(y0, y1, y2, y3);
|
|
826
|
+
const maxX = Math.max(x0, x1, x2, x3);
|
|
827
|
+
const maxY = Math.max(y0, y1, y2, y3);
|
|
828
|
+
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
function transferStrokeBounds(prev, next) {
|
|
833
|
+
if (prev.type !== "stroke" || next.type !== "stroke") return;
|
|
834
|
+
if (prev.points !== next.points) return;
|
|
835
|
+
if (prev.position.x !== next.position.x || prev.position.y !== next.position.y) return;
|
|
836
|
+
const bounds = strokeBoundsCache.get(prev);
|
|
837
|
+
if (bounds) strokeBoundsCache.set(next, bounds);
|
|
838
|
+
}
|
|
839
|
+
function boundsIntersect(a, b) {
|
|
840
|
+
return a.x <= b.x + b.w && a.x + a.w >= b.x && a.y <= b.y + b.h && a.y + a.h >= b.y;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// src/elements/bounds.ts
|
|
844
|
+
function getElementsBoundingBox(elements) {
|
|
845
|
+
let minX = Infinity;
|
|
846
|
+
let minY = Infinity;
|
|
847
|
+
let maxX = -Infinity;
|
|
848
|
+
let maxY = -Infinity;
|
|
849
|
+
let found = false;
|
|
850
|
+
for (const el of elements) {
|
|
851
|
+
const b = getElementBounds(el);
|
|
852
|
+
if (!b) continue;
|
|
853
|
+
found = true;
|
|
854
|
+
if (b.x < minX) minX = b.x;
|
|
855
|
+
if (b.y < minY) minY = b.y;
|
|
856
|
+
if (b.x + b.w > maxX) maxX = b.x + b.w;
|
|
857
|
+
if (b.y + b.h > maxY) maxY = b.y + b.h;
|
|
858
|
+
}
|
|
859
|
+
if (!found) return null;
|
|
860
|
+
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// src/canvas/keyboard-actions.ts
|
|
864
|
+
var KeyboardActions = class {
|
|
865
|
+
constructor(deps) {
|
|
866
|
+
this.deps = deps;
|
|
867
|
+
}
|
|
868
|
+
clipboard = [];
|
|
869
|
+
pasteCount = 0;
|
|
870
|
+
nudgeTimer = null;
|
|
871
|
+
nudgeTxId = null;
|
|
872
|
+
dispose() {
|
|
873
|
+
this.flushPendingNudge();
|
|
874
|
+
}
|
|
875
|
+
selectTool() {
|
|
876
|
+
const tm = this.deps.getToolManager();
|
|
877
|
+
const ctx = this.deps.getToolContext();
|
|
878
|
+
if (!tm || !ctx) return null;
|
|
879
|
+
const tool = tm.activeTool;
|
|
880
|
+
if (tool?.name !== "select") return null;
|
|
881
|
+
return { tool, ctx };
|
|
882
|
+
}
|
|
883
|
+
nudge(dx, dy, byCell) {
|
|
884
|
+
if (this.deps.isToolActive()) return false;
|
|
885
|
+
const sel = this.selectTool();
|
|
886
|
+
if (!sel) return false;
|
|
887
|
+
if (sel.tool.selectedIds.length === 0) return false;
|
|
888
|
+
const step = byCell ? sel.ctx.gridSize ?? 10 : 1;
|
|
889
|
+
if (this.nudgeTimer === null) {
|
|
890
|
+
const recorder = this.deps.getHistoryRecorder();
|
|
891
|
+
recorder?.begin();
|
|
892
|
+
this.nudgeTxId = recorder?.currentTransactionId ?? null;
|
|
893
|
+
} else {
|
|
894
|
+
clearTimeout(this.nudgeTimer);
|
|
895
|
+
}
|
|
896
|
+
const moved = sel.tool.nudgeSelection(dx * step, dy * step, sel.ctx);
|
|
897
|
+
this.nudgeTimer = setTimeout(() => this.flushPendingNudge(), 400);
|
|
898
|
+
return moved;
|
|
899
|
+
}
|
|
900
|
+
flushPendingNudge() {
|
|
901
|
+
if (this.nudgeTimer === null) return;
|
|
902
|
+
clearTimeout(this.nudgeTimer);
|
|
903
|
+
this.nudgeTimer = null;
|
|
904
|
+
const recorder = this.deps.getHistoryRecorder();
|
|
905
|
+
if (this.nudgeTxId === null || recorder?.currentTransactionId === this.nudgeTxId) {
|
|
906
|
+
recorder?.commit();
|
|
907
|
+
}
|
|
908
|
+
this.nudgeTxId = null;
|
|
909
|
+
}
|
|
910
|
+
deleteSelected() {
|
|
911
|
+
if (this.deps.isToolActive()) return;
|
|
912
|
+
this.flushPendingNudge();
|
|
913
|
+
const sel = this.selectTool();
|
|
914
|
+
if (!sel) return;
|
|
915
|
+
const ids = sel.tool.selectedIds;
|
|
916
|
+
if (ids.length === 0) return;
|
|
917
|
+
const recorder = this.deps.getHistoryRecorder();
|
|
918
|
+
recorder?.begin();
|
|
992
919
|
for (const id of ids) {
|
|
993
920
|
sel.ctx.store.remove(id);
|
|
994
921
|
}
|
|
@@ -1038,8 +965,18 @@ var KeyboardActions = class {
|
|
|
1038
965
|
if (this.clipboard.length === 0) return;
|
|
1039
966
|
const sel = this.selectTool();
|
|
1040
967
|
if (!sel) return;
|
|
968
|
+
const cursor = this.deps.getLastPointerWorld?.() ?? null;
|
|
969
|
+
if (cursor) {
|
|
970
|
+
const bbox = getElementsBoundingBox(this.clipboard);
|
|
971
|
+
if (bbox) {
|
|
972
|
+
const centerX = bbox.x + bbox.w / 2;
|
|
973
|
+
const centerY = bbox.y + bbox.h / 2;
|
|
974
|
+
this.insertClones(this.clipboard, { x: cursor.x - centerX, y: cursor.y - centerY }, sel);
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
1041
978
|
this.pasteCount++;
|
|
1042
|
-
this.insertClones(this.clipboard, this.pasteCount * 20, sel);
|
|
979
|
+
this.insertClones(this.clipboard, { x: this.pasteCount * 20, y: this.pasteCount * 20 }, sel);
|
|
1043
980
|
}
|
|
1044
981
|
duplicate() {
|
|
1045
982
|
if (this.deps.isToolActive()) return;
|
|
@@ -1052,7 +989,7 @@ var KeyboardActions = class {
|
|
|
1052
989
|
if (el) source.push(el);
|
|
1053
990
|
}
|
|
1054
991
|
if (source.length === 0) return;
|
|
1055
|
-
this.insertClones(source, 20, sel);
|
|
992
|
+
this.insertClones(source, { x: 20, y: 20 }, sel);
|
|
1056
993
|
}
|
|
1057
994
|
deselect() {
|
|
1058
995
|
if (this.deps.isToolActive()) return;
|
|
@@ -1123,11 +1060,11 @@ var KeyboardActions = class {
|
|
|
1123
1060
|
const newId = idMap.get(el.id);
|
|
1124
1061
|
if (!newId) continue;
|
|
1125
1062
|
clone.id = newId;
|
|
1126
|
-
clone.position = { x: clone.position.x + offset, y: clone.position.y + offset };
|
|
1063
|
+
clone.position = { x: clone.position.x + offset.x, y: clone.position.y + offset.y };
|
|
1127
1064
|
if (clone.type === "arrow") {
|
|
1128
1065
|
const arrow = clone;
|
|
1129
|
-
arrow.from = { x: arrow.from.x + offset, y: arrow.from.y + offset };
|
|
1130
|
-
arrow.to = { x: arrow.to.x + offset, y: arrow.to.y + offset };
|
|
1066
|
+
arrow.from = { x: arrow.from.x + offset.x, y: arrow.from.y + offset.y };
|
|
1067
|
+
arrow.to = { x: arrow.to.x + offset.x, y: arrow.to.y + offset.y };
|
|
1131
1068
|
delete arrow.cachedControlPoint;
|
|
1132
1069
|
if (arrow.fromBinding) {
|
|
1133
1070
|
const newTarget = idMap.get(arrow.fromBinding.elementId);
|
|
@@ -1173,6 +1110,9 @@ var DEFAULT_BINDINGS = [
|
|
|
1173
1110
|
["z-front", ["mod+]"]],
|
|
1174
1111
|
["z-back", ["mod+["]],
|
|
1175
1112
|
["zoom-fit", ["shift+1"]],
|
|
1113
|
+
["zoom-in", ["mod+="]],
|
|
1114
|
+
["zoom-out", ["mod+-"]],
|
|
1115
|
+
["zoom-reset", ["mod+0"]],
|
|
1176
1116
|
["nudge-left", ["arrowleft"]],
|
|
1177
1117
|
["nudge-right", ["arrowright"]],
|
|
1178
1118
|
["nudge-up", ["arrowup"]],
|
|
@@ -1309,6 +1249,7 @@ var ShortcutMap = class {
|
|
|
1309
1249
|
|
|
1310
1250
|
// src/canvas/input-handler.ts
|
|
1311
1251
|
var ZOOM_SENSITIVITY = 1e-3;
|
|
1252
|
+
var ZOOM_STEP = 1.2;
|
|
1312
1253
|
var MIDDLE_BUTTON = 1;
|
|
1313
1254
|
var NUDGE_DELTAS = {
|
|
1314
1255
|
"nudge-left": [-1, 0],
|
|
@@ -1330,7 +1271,8 @@ var InputHandler = class {
|
|
|
1330
1271
|
getHistoryRecorder: () => this.historyRecorder,
|
|
1331
1272
|
getHistoryStack: () => this.historyStack,
|
|
1332
1273
|
isToolActive: () => this.isToolActive,
|
|
1333
|
-
fitToContent: options.fitToContent
|
|
1274
|
+
fitToContent: options.fitToContent,
|
|
1275
|
+
getLastPointerWorld: () => this.lastPointerWorld()
|
|
1334
1276
|
});
|
|
1335
1277
|
this.shortcutMap = new ShortcutMap(options.shortcuts?.bindings);
|
|
1336
1278
|
this.scope = options.shortcuts?.scope ?? "focus";
|
|
@@ -1386,11 +1328,21 @@ var InputHandler = class {
|
|
|
1386
1328
|
this.element.addEventListener("pointerdown", this.onPointerDown, opts);
|
|
1387
1329
|
this.element.addEventListener("pointermove", this.onPointerMove, opts);
|
|
1388
1330
|
this.element.addEventListener("pointerup", this.onPointerUp, opts);
|
|
1389
|
-
this.element.addEventListener("pointerleave", this.
|
|
1331
|
+
this.element.addEventListener("pointerleave", this.onPointerLeave, opts);
|
|
1390
1332
|
this.element.addEventListener("pointercancel", this.onPointerUp, opts);
|
|
1391
1333
|
window.addEventListener("keydown", this.onKeyDown, opts);
|
|
1392
1334
|
window.addEventListener("keyup", this.onKeyUp, opts);
|
|
1393
1335
|
}
|
|
1336
|
+
viewportCenter() {
|
|
1337
|
+
const rect = this.element.getBoundingClientRect();
|
|
1338
|
+
return { x: rect.width / 2, y: rect.height / 2 };
|
|
1339
|
+
}
|
|
1340
|
+
zoomByFactor(factor) {
|
|
1341
|
+
this.camera.zoomAt(this.camera.zoom * factor, this.viewportCenter());
|
|
1342
|
+
}
|
|
1343
|
+
zoomToLevel(level) {
|
|
1344
|
+
this.camera.zoomAt(level, this.viewportCenter());
|
|
1345
|
+
}
|
|
1394
1346
|
onWheel = (e) => {
|
|
1395
1347
|
e.preventDefault();
|
|
1396
1348
|
const rect = this.element.getBoundingClientRect();
|
|
@@ -1559,6 +1511,18 @@ var InputHandler = class {
|
|
|
1559
1511
|
e.preventDefault();
|
|
1560
1512
|
this.actions.zoomToFit();
|
|
1561
1513
|
return;
|
|
1514
|
+
case "zoom-in":
|
|
1515
|
+
e.preventDefault();
|
|
1516
|
+
this.zoomByFactor(ZOOM_STEP);
|
|
1517
|
+
return;
|
|
1518
|
+
case "zoom-out":
|
|
1519
|
+
e.preventDefault();
|
|
1520
|
+
this.zoomByFactor(1 / ZOOM_STEP);
|
|
1521
|
+
return;
|
|
1522
|
+
case "zoom-reset":
|
|
1523
|
+
e.preventDefault();
|
|
1524
|
+
this.zoomToLevel(1);
|
|
1525
|
+
return;
|
|
1562
1526
|
case "nudge-left":
|
|
1563
1527
|
case "nudge-right":
|
|
1564
1528
|
case "nudge-up":
|
|
@@ -1616,6 +1580,16 @@ var InputHandler = class {
|
|
|
1616
1580
|
midpoint(a, b) {
|
|
1617
1581
|
return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
|
|
1618
1582
|
}
|
|
1583
|
+
lastPointerWorld() {
|
|
1584
|
+
const e = this.lastPointerEvent;
|
|
1585
|
+
if (!e) return null;
|
|
1586
|
+
const rect = this.element.getBoundingClientRect();
|
|
1587
|
+
return this.camera.screenToWorld({ x: e.clientX - rect.left, y: e.clientY - rect.top });
|
|
1588
|
+
}
|
|
1589
|
+
onPointerLeave = (e) => {
|
|
1590
|
+
this.lastPointerEvent = null;
|
|
1591
|
+
this.onPointerUp(e);
|
|
1592
|
+
};
|
|
1619
1593
|
toPointerState(e) {
|
|
1620
1594
|
const rect = this.element.getBoundingClientRect();
|
|
1621
1595
|
return {
|
|
@@ -1667,299 +1641,319 @@ var InputHandler = class {
|
|
|
1667
1641
|
}
|
|
1668
1642
|
};
|
|
1669
1643
|
|
|
1670
|
-
// src/canvas/
|
|
1671
|
-
var
|
|
1672
|
-
var
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1644
|
+
// src/canvas/background.ts
|
|
1645
|
+
var MIN_PATTERN_SPACING = 16;
|
|
1646
|
+
var DEFAULTS = {
|
|
1647
|
+
pattern: "dots",
|
|
1648
|
+
spacing: 24,
|
|
1649
|
+
color: "#d0d0d0",
|
|
1650
|
+
dotRadius: 1,
|
|
1651
|
+
lineWidth: 0.5
|
|
1652
|
+
};
|
|
1653
|
+
var Background = class {
|
|
1654
|
+
pattern;
|
|
1655
|
+
spacing;
|
|
1656
|
+
color;
|
|
1657
|
+
dotRadius;
|
|
1658
|
+
lineWidth;
|
|
1659
|
+
cachedCanvas = null;
|
|
1660
|
+
cachedCtx = null;
|
|
1661
|
+
lastZoom = -1;
|
|
1662
|
+
lastOffsetX = -Infinity;
|
|
1663
|
+
lastOffsetY = -Infinity;
|
|
1664
|
+
lastWidth = 0;
|
|
1665
|
+
lastHeight = 0;
|
|
1666
|
+
constructor(options = {}) {
|
|
1667
|
+
this.pattern = options.pattern ?? DEFAULTS.pattern;
|
|
1668
|
+
this.spacing = options.spacing ?? DEFAULTS.spacing;
|
|
1669
|
+
this.color = options.color ?? DEFAULTS.color;
|
|
1670
|
+
this.dotRadius = options.dotRadius ?? DEFAULTS.dotRadius;
|
|
1671
|
+
this.lineWidth = options.lineWidth ?? DEFAULTS.lineWidth;
|
|
1683
1672
|
}
|
|
1684
|
-
|
|
1685
|
-
const
|
|
1686
|
-
const
|
|
1687
|
-
const
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1673
|
+
render(ctx, camera) {
|
|
1674
|
+
const { width, height } = ctx.canvas;
|
|
1675
|
+
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
1676
|
+
const cssWidth = width / dpr;
|
|
1677
|
+
const cssHeight = height / dpr;
|
|
1678
|
+
ctx.save();
|
|
1679
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
1680
|
+
ctx.clearRect(0, 0, cssWidth, cssHeight);
|
|
1681
|
+
if (this.pattern === "none") {
|
|
1682
|
+
ctx.restore();
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
const spacing = this.adaptSpacing(this.spacing, camera.zoom);
|
|
1686
|
+
const keyZoom = camera.zoom;
|
|
1687
|
+
const keyX = Math.floor(camera.position.x % spacing);
|
|
1688
|
+
const keyY = Math.floor(camera.position.y % spacing);
|
|
1689
|
+
if (this.cachedCanvas !== null && keyZoom === this.lastZoom && keyX === this.lastOffsetX && keyY === this.lastOffsetY && cssWidth === this.lastWidth && cssHeight === this.lastHeight) {
|
|
1690
|
+
ctx.drawImage(this.cachedCanvas, 0, 0);
|
|
1691
|
+
ctx.restore();
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
this.ensureCachedCanvas(cssWidth, cssHeight, dpr);
|
|
1695
|
+
if (this.cachedCtx === null) {
|
|
1696
|
+
if (this.pattern === "dots") {
|
|
1697
|
+
this.renderDots(ctx, camera, cssWidth, cssHeight);
|
|
1698
|
+
} else if (this.pattern === "grid") {
|
|
1699
|
+
this.renderGrid(ctx, camera, cssWidth, cssHeight);
|
|
1696
1700
|
}
|
|
1701
|
+
ctx.restore();
|
|
1702
|
+
return;
|
|
1697
1703
|
}
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
this.
|
|
1701
|
-
|
|
1702
|
-
|
|
1704
|
+
const offCtx = this.cachedCtx;
|
|
1705
|
+
offCtx.clearRect(0, 0, cssWidth, cssHeight);
|
|
1706
|
+
if (this.pattern === "dots") {
|
|
1707
|
+
this.renderDots(offCtx, camera, cssWidth, cssHeight);
|
|
1708
|
+
} else if (this.pattern === "grid") {
|
|
1709
|
+
this.renderGrid(offCtx, camera, cssWidth, cssHeight);
|
|
1710
|
+
}
|
|
1711
|
+
this.lastZoom = keyZoom;
|
|
1712
|
+
this.lastOffsetX = keyX;
|
|
1713
|
+
this.lastOffsetY = keyY;
|
|
1714
|
+
this.lastWidth = cssWidth;
|
|
1715
|
+
this.lastHeight = cssHeight;
|
|
1716
|
+
ctx.drawImage(this.cachedCanvas, 0, 0);
|
|
1717
|
+
ctx.restore();
|
|
1703
1718
|
}
|
|
1704
|
-
|
|
1705
|
-
this.
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1719
|
+
ensureCachedCanvas(cssWidth, cssHeight, dpr) {
|
|
1720
|
+
if (this.cachedCanvas !== null && this.lastWidth === cssWidth && this.lastHeight === cssHeight) {
|
|
1721
|
+
return;
|
|
1722
|
+
}
|
|
1723
|
+
const physWidth = Math.round(cssWidth * dpr);
|
|
1724
|
+
const physHeight = Math.round(cssHeight * dpr);
|
|
1725
|
+
if (typeof OffscreenCanvas !== "undefined") {
|
|
1726
|
+
this.cachedCanvas = new OffscreenCanvas(physWidth, physHeight);
|
|
1727
|
+
} else if (typeof document !== "undefined") {
|
|
1728
|
+
const el = document.createElement("canvas");
|
|
1729
|
+
el.width = physWidth;
|
|
1730
|
+
el.height = physHeight;
|
|
1731
|
+
this.cachedCanvas = el;
|
|
1732
|
+
} else {
|
|
1733
|
+
this.cachedCanvas = null;
|
|
1734
|
+
this.cachedCtx = null;
|
|
1735
|
+
return;
|
|
1736
|
+
}
|
|
1737
|
+
const offCtx = this.cachedCanvas.getContext("2d");
|
|
1738
|
+
if (offCtx !== null) {
|
|
1739
|
+
offCtx.scale(dpr, dpr);
|
|
1740
|
+
}
|
|
1741
|
+
this.cachedCtx = offCtx;
|
|
1742
|
+
this.lastZoom = -1;
|
|
1743
|
+
}
|
|
1744
|
+
adaptSpacing(baseSpacing, zoom) {
|
|
1745
|
+
let spacing = baseSpacing * zoom;
|
|
1746
|
+
while (spacing < MIN_PATTERN_SPACING) {
|
|
1747
|
+
spacing *= 2;
|
|
1748
|
+
}
|
|
1749
|
+
return spacing;
|
|
1750
|
+
}
|
|
1751
|
+
renderDots(ctx, camera, width, height) {
|
|
1752
|
+
const spacing = this.adaptSpacing(this.spacing, camera.zoom);
|
|
1753
|
+
const offsetX = camera.position.x % spacing;
|
|
1754
|
+
const offsetY = camera.position.y % spacing;
|
|
1755
|
+
const radius = this.dotRadius * Math.min(camera.zoom, 2);
|
|
1756
|
+
ctx.fillStyle = this.color;
|
|
1757
|
+
ctx.beginPath();
|
|
1758
|
+
for (let x = offsetX; x < width; x += spacing) {
|
|
1759
|
+
for (let y = offsetY; y < height; y += spacing) {
|
|
1760
|
+
ctx.moveTo(x + radius, y);
|
|
1761
|
+
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
ctx.fill();
|
|
1765
|
+
}
|
|
1766
|
+
renderGrid(ctx, camera, width, height) {
|
|
1767
|
+
const spacing = this.adaptSpacing(this.spacing, camera.zoom);
|
|
1768
|
+
const offsetX = camera.position.x % spacing;
|
|
1769
|
+
const offsetY = camera.position.y % spacing;
|
|
1770
|
+
const lineW = this.lineWidth * Math.min(camera.zoom, 2);
|
|
1771
|
+
ctx.fillStyle = this.color;
|
|
1772
|
+
for (let x = offsetX; x < width; x += spacing) {
|
|
1773
|
+
ctx.fillRect(x, 0, lineW, height);
|
|
1774
|
+
}
|
|
1775
|
+
for (let y = offsetY; y < height; y += spacing) {
|
|
1776
|
+
ctx.fillRect(0, y, width, lineW);
|
|
1777
|
+
}
|
|
1709
1778
|
}
|
|
1710
1779
|
};
|
|
1711
1780
|
|
|
1712
|
-
// src/core/
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1781
|
+
// src/core/event-bus.ts
|
|
1782
|
+
var EventBus = class {
|
|
1783
|
+
listeners = /* @__PURE__ */ new Map();
|
|
1784
|
+
on(event, listener) {
|
|
1785
|
+
const existing = this.listeners.get(event);
|
|
1786
|
+
if (existing) {
|
|
1787
|
+
existing.add(listener);
|
|
1788
|
+
} else {
|
|
1789
|
+
const set = /* @__PURE__ */ new Set([listener]);
|
|
1790
|
+
this.listeners.set(event, set);
|
|
1791
|
+
}
|
|
1792
|
+
return () => this.off(event, listener);
|
|
1721
1793
|
}
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1794
|
+
off(event, listener) {
|
|
1795
|
+
this.listeners.get(event)?.delete(listener);
|
|
1796
|
+
}
|
|
1797
|
+
emit(event, data) {
|
|
1798
|
+
this.listeners.get(event)?.forEach((listener) => {
|
|
1799
|
+
try {
|
|
1800
|
+
listener(data);
|
|
1801
|
+
} catch (err) {
|
|
1802
|
+
console.error(`[fieldnotes] listener error for "${String(event)}"`, err);
|
|
1803
|
+
}
|
|
1804
|
+
});
|
|
1805
|
+
}
|
|
1806
|
+
clear() {
|
|
1807
|
+
this.listeners.clear();
|
|
1808
|
+
}
|
|
1809
|
+
};
|
|
1727
1810
|
|
|
1728
|
-
// src/
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
const dx = to.x - from.x;
|
|
1734
|
-
const dy = to.y - from.y;
|
|
1735
|
-
const len = Math.sqrt(dx * dx + dy * dy);
|
|
1736
|
-
if (len === 0) return { x: midX, y: midY };
|
|
1737
|
-
const perpX = -dy / len;
|
|
1738
|
-
const perpY = dx / len;
|
|
1739
|
-
return {
|
|
1740
|
-
x: midX + perpX * bend,
|
|
1741
|
-
y: midY + perpY * bend
|
|
1742
|
-
};
|
|
1743
|
-
}
|
|
1744
|
-
function getArrowMidpoint(from, to, bend) {
|
|
1745
|
-
const cp = getArrowControlPoint(from, to, bend);
|
|
1746
|
-
return {
|
|
1747
|
-
x: 0.25 * from.x + 0.5 * cp.x + 0.25 * to.x,
|
|
1748
|
-
y: 0.25 * from.y + 0.5 * cp.y + 0.25 * to.y
|
|
1749
|
-
};
|
|
1750
|
-
}
|
|
1751
|
-
function getBendFromPoint(from, to, dragPoint) {
|
|
1752
|
-
const midX = (from.x + to.x) / 2;
|
|
1753
|
-
const midY = (from.y + to.y) / 2;
|
|
1754
|
-
const dx = to.x - from.x;
|
|
1755
|
-
const dy = to.y - from.y;
|
|
1756
|
-
const len = Math.sqrt(dx * dx + dy * dy);
|
|
1757
|
-
if (len === 0) return 0;
|
|
1758
|
-
const perpX = -dy / len;
|
|
1759
|
-
const perpY = dx / len;
|
|
1760
|
-
return (dragPoint.x - midX) * perpX + (dragPoint.y - midY) * perpY;
|
|
1761
|
-
}
|
|
1762
|
-
function getArrowTangentAngle(from, to, bend, t) {
|
|
1763
|
-
const cp = getArrowControlPoint(from, to, bend);
|
|
1764
|
-
const tangentX = 2 * (1 - t) * (cp.x - from.x) + 2 * t * (to.x - cp.x);
|
|
1765
|
-
const tangentY = 2 * (1 - t) * (cp.y - from.y) + 2 * t * (to.y - cp.y);
|
|
1766
|
-
return Math.atan2(tangentY, tangentX);
|
|
1811
|
+
// src/core/quadtree.ts
|
|
1812
|
+
var MAX_ITEMS = 8;
|
|
1813
|
+
var MAX_DEPTH = 8;
|
|
1814
|
+
function intersects(a, b) {
|
|
1815
|
+
return a.x <= b.x + b.w && a.x + a.w >= b.x && a.y <= b.y + b.h && a.y + a.h >= b.y;
|
|
1767
1816
|
}
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
for (let i = 0; i < segments; i++) {
|
|
1773
|
-
const t0 = i / segments;
|
|
1774
|
-
const t1 = (i + 1) / segments;
|
|
1775
|
-
const a = bezierPoint(from, cp, to, t0);
|
|
1776
|
-
const b = bezierPoint(from, cp, to, t1);
|
|
1777
|
-
if (isNearLine(point, a, b, threshold)) return true;
|
|
1817
|
+
var QuadNode = class _QuadNode {
|
|
1818
|
+
constructor(bounds, depth) {
|
|
1819
|
+
this.bounds = bounds;
|
|
1820
|
+
this.depth = depth;
|
|
1778
1821
|
}
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1822
|
+
items = [];
|
|
1823
|
+
children = null;
|
|
1824
|
+
insert(entry) {
|
|
1825
|
+
if (this.children) {
|
|
1826
|
+
const idx = this.getChildIndex(entry.bounds);
|
|
1827
|
+
if (idx !== -1) {
|
|
1828
|
+
const child = this.children[idx];
|
|
1829
|
+
if (child) child.insert(entry);
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
this.items.push(entry);
|
|
1833
|
+
return;
|
|
1834
|
+
}
|
|
1835
|
+
this.items.push(entry);
|
|
1836
|
+
if (this.items.length > MAX_ITEMS && this.depth < MAX_DEPTH) {
|
|
1837
|
+
this.split();
|
|
1838
|
+
}
|
|
1791
1839
|
}
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1840
|
+
remove(id) {
|
|
1841
|
+
const idx = this.items.findIndex((e) => e.id === id);
|
|
1842
|
+
if (idx !== -1) {
|
|
1843
|
+
this.items.splice(idx, 1);
|
|
1844
|
+
return true;
|
|
1845
|
+
}
|
|
1846
|
+
if (this.children) {
|
|
1847
|
+
for (const child of this.children) {
|
|
1848
|
+
if (child.remove(id)) {
|
|
1849
|
+
this.collapseIfEmpty();
|
|
1850
|
+
return true;
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
return false;
|
|
1805
1855
|
}
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
}
|
|
1818
|
-
|
|
1819
|
-
// src/elements/element-bounds.ts
|
|
1820
|
-
var strokeBoundsCache = /* @__PURE__ */ new WeakMap();
|
|
1821
|
-
function getElementBounds(element) {
|
|
1822
|
-
if (element.type === "grid") return null;
|
|
1823
|
-
if ("size" in element) {
|
|
1824
|
-
return {
|
|
1825
|
-
x: element.position.x,
|
|
1826
|
-
y: element.position.y,
|
|
1827
|
-
w: element.size.w,
|
|
1828
|
-
h: element.size.h
|
|
1829
|
-
};
|
|
1856
|
+
query(rect, result) {
|
|
1857
|
+
if (!intersects(this.bounds, rect)) return;
|
|
1858
|
+
for (const item of this.items) {
|
|
1859
|
+
if (intersects(item.bounds, rect)) {
|
|
1860
|
+
result.push(item.id);
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
if (this.children) {
|
|
1864
|
+
for (const child of this.children) {
|
|
1865
|
+
child.query(rect, result);
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1830
1868
|
}
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
const
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1869
|
+
getChildIndex(itemBounds) {
|
|
1870
|
+
const midX = this.bounds.x + this.bounds.w / 2;
|
|
1871
|
+
const midY = this.bounds.y + this.bounds.h / 2;
|
|
1872
|
+
const left = itemBounds.x >= this.bounds.x && itemBounds.x + itemBounds.w <= midX;
|
|
1873
|
+
const right = itemBounds.x >= midX && itemBounds.x + itemBounds.w <= this.bounds.x + this.bounds.w;
|
|
1874
|
+
const top = itemBounds.y >= this.bounds.y && itemBounds.y + itemBounds.h <= midY;
|
|
1875
|
+
const bottom = itemBounds.y >= midY && itemBounds.y + itemBounds.h <= this.bounds.y + this.bounds.h;
|
|
1876
|
+
if (left && top) return 0;
|
|
1877
|
+
if (right && top) return 1;
|
|
1878
|
+
if (left && bottom) return 2;
|
|
1879
|
+
if (right && bottom) return 3;
|
|
1880
|
+
return -1;
|
|
1881
|
+
}
|
|
1882
|
+
split() {
|
|
1883
|
+
const { x, y, w, h } = this.bounds;
|
|
1884
|
+
const halfW = w / 2;
|
|
1885
|
+
const halfH = h / 2;
|
|
1886
|
+
const d = this.depth + 1;
|
|
1887
|
+
this.children = [
|
|
1888
|
+
new _QuadNode({ x, y, w: halfW, h: halfH }, d),
|
|
1889
|
+
new _QuadNode({ x: x + halfW, y, w: halfW, h: halfH }, d),
|
|
1890
|
+
new _QuadNode({ x, y: y + halfH, w: halfW, h: halfH }, d),
|
|
1891
|
+
new _QuadNode({ x: x + halfW, y: y + halfH, w: halfW, h: halfH }, d)
|
|
1892
|
+
];
|
|
1893
|
+
const remaining = [];
|
|
1894
|
+
for (const item of this.items) {
|
|
1895
|
+
const idx = this.getChildIndex(item.bounds);
|
|
1896
|
+
if (idx !== -1) {
|
|
1897
|
+
const target = this.children[idx];
|
|
1898
|
+
if (target) target.insert(item);
|
|
1899
|
+
} else {
|
|
1900
|
+
remaining.push(item);
|
|
1901
|
+
}
|
|
1846
1902
|
}
|
|
1847
|
-
|
|
1848
|
-
strokeBoundsCache.set(element, bounds);
|
|
1849
|
-
return bounds;
|
|
1903
|
+
this.items = remaining;
|
|
1850
1904
|
}
|
|
1851
|
-
|
|
1852
|
-
|
|
1905
|
+
collapseIfEmpty() {
|
|
1906
|
+
if (!this.children) return;
|
|
1907
|
+
let totalItems = this.items.length;
|
|
1908
|
+
for (const child of this.children) {
|
|
1909
|
+
if (child.children) return;
|
|
1910
|
+
totalItems += child.items.length;
|
|
1911
|
+
}
|
|
1912
|
+
if (totalItems <= MAX_ITEMS) {
|
|
1913
|
+
for (const child of this.children) {
|
|
1914
|
+
this.items.push(...child.items);
|
|
1915
|
+
}
|
|
1916
|
+
this.children = null;
|
|
1917
|
+
}
|
|
1853
1918
|
}
|
|
1854
|
-
|
|
1855
|
-
|
|
1919
|
+
};
|
|
1920
|
+
var Quadtree = class {
|
|
1921
|
+
root;
|
|
1922
|
+
_size = 0;
|
|
1923
|
+
worldBounds;
|
|
1924
|
+
constructor(worldBounds) {
|
|
1925
|
+
this.worldBounds = worldBounds;
|
|
1926
|
+
this.root = new QuadNode(worldBounds, 0);
|
|
1856
1927
|
}
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
function getArrowBoundsAnalytical(from, to, bend) {
|
|
1860
|
-
if (bend === 0) {
|
|
1861
|
-
const minX2 = Math.min(from.x, to.x);
|
|
1862
|
-
const minY2 = Math.min(from.y, to.y);
|
|
1863
|
-
return {
|
|
1864
|
-
x: minX2,
|
|
1865
|
-
y: minY2,
|
|
1866
|
-
w: Math.abs(to.x - from.x),
|
|
1867
|
-
h: Math.abs(to.y - from.y)
|
|
1868
|
-
};
|
|
1928
|
+
get size() {
|
|
1929
|
+
return this._size;
|
|
1869
1930
|
}
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
let minY = Math.min(from.y, to.y);
|
|
1874
|
-
let maxY = Math.max(from.y, to.y);
|
|
1875
|
-
const tx = from.x - 2 * cp.x + to.x;
|
|
1876
|
-
if (tx !== 0) {
|
|
1877
|
-
const t = (from.x - cp.x) / tx;
|
|
1878
|
-
if (t > 0 && t < 1) {
|
|
1879
|
-
const mt = 1 - t;
|
|
1880
|
-
const x = mt * mt * from.x + 2 * mt * t * cp.x + t * t * to.x;
|
|
1881
|
-
if (x < minX) minX = x;
|
|
1882
|
-
if (x > maxX) maxX = x;
|
|
1883
|
-
}
|
|
1931
|
+
insert(id, bounds) {
|
|
1932
|
+
this.root.insert({ id, bounds });
|
|
1933
|
+
this._size++;
|
|
1884
1934
|
}
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
if (t > 0 && t < 1) {
|
|
1889
|
-
const mt = 1 - t;
|
|
1890
|
-
const y = mt * mt * from.y + 2 * mt * t * cp.y + t * t * to.y;
|
|
1891
|
-
if (y < minY) minY = y;
|
|
1892
|
-
if (y > maxY) maxY = y;
|
|
1935
|
+
remove(id) {
|
|
1936
|
+
if (this.root.remove(id)) {
|
|
1937
|
+
this._size--;
|
|
1893
1938
|
}
|
|
1894
1939
|
}
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
const { x: cx, y: cy } = el.position;
|
|
1899
|
-
const r = el.radius;
|
|
1900
|
-
switch (el.templateShape) {
|
|
1901
|
-
case "circle":
|
|
1902
|
-
return { x: cx - r, y: cy - r, w: 2 * r, h: 2 * r };
|
|
1903
|
-
case "square":
|
|
1904
|
-
return { x: cx - r / 2, y: cy - r / 2, w: r, h: r };
|
|
1905
|
-
case "cone": {
|
|
1906
|
-
const halfAngle = Math.atan(0.5);
|
|
1907
|
-
const tipX = cx;
|
|
1908
|
-
const tipY = cy;
|
|
1909
|
-
const leftX = cx + r * Math.cos(el.angle - halfAngle);
|
|
1910
|
-
const leftY = cy + r * Math.sin(el.angle - halfAngle);
|
|
1911
|
-
const rightX = cx + r * Math.cos(el.angle + halfAngle);
|
|
1912
|
-
const rightY = cy + r * Math.sin(el.angle + halfAngle);
|
|
1913
|
-
const farX = cx + r * Math.cos(el.angle);
|
|
1914
|
-
const farY = cy + r * Math.sin(el.angle);
|
|
1915
|
-
const xs = [tipX, leftX, rightX, farX];
|
|
1916
|
-
const ys = [tipY, leftY, rightY, farY];
|
|
1917
|
-
let minX = Infinity;
|
|
1918
|
-
let minY = Infinity;
|
|
1919
|
-
let maxX = -Infinity;
|
|
1920
|
-
let maxY = -Infinity;
|
|
1921
|
-
for (let i = 0; i < xs.length; i++) {
|
|
1922
|
-
const px = xs[i];
|
|
1923
|
-
const py = ys[i];
|
|
1924
|
-
if (px !== void 0 && px < minX) minX = px;
|
|
1925
|
-
if (px !== void 0 && px > maxX) maxX = px;
|
|
1926
|
-
if (py !== void 0 && py < minY) minY = py;
|
|
1927
|
-
if (py !== void 0 && py > maxY) maxY = py;
|
|
1928
|
-
}
|
|
1929
|
-
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
1930
|
-
}
|
|
1931
|
-
case "line": {
|
|
1932
|
-
const halfW = r / 12;
|
|
1933
|
-
const cos = Math.cos(el.angle);
|
|
1934
|
-
const sin = Math.sin(el.angle);
|
|
1935
|
-
const perpX = -sin * halfW;
|
|
1936
|
-
const perpY = cos * halfW;
|
|
1937
|
-
const x0 = cx + perpX;
|
|
1938
|
-
const y0 = cy + perpY;
|
|
1939
|
-
const x1 = cx + r * cos + perpX;
|
|
1940
|
-
const y1 = cy + r * sin + perpY;
|
|
1941
|
-
const x2 = cx + r * cos - perpX;
|
|
1942
|
-
const y2 = cy + r * sin - perpY;
|
|
1943
|
-
const x3 = cx - perpX;
|
|
1944
|
-
const y3 = cy - perpY;
|
|
1945
|
-
const minX = Math.min(x0, x1, x2, x3);
|
|
1946
|
-
const minY = Math.min(y0, y1, y2, y3);
|
|
1947
|
-
const maxX = Math.max(x0, x1, x2, x3);
|
|
1948
|
-
const maxY = Math.max(y0, y1, y2, y3);
|
|
1949
|
-
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
1950
|
-
}
|
|
1940
|
+
update(id, newBounds) {
|
|
1941
|
+
this.remove(id);
|
|
1942
|
+
this.insert(id, newBounds);
|
|
1951
1943
|
}
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
}
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1944
|
+
query(rect) {
|
|
1945
|
+
const result = [];
|
|
1946
|
+
this.root.query(rect, result);
|
|
1947
|
+
return result;
|
|
1948
|
+
}
|
|
1949
|
+
queryPoint(point) {
|
|
1950
|
+
return this.query({ x: point.x, y: point.y, w: 0, h: 0 });
|
|
1951
|
+
}
|
|
1952
|
+
clear() {
|
|
1953
|
+
this.root = new QuadNode(this.worldBounds, 0);
|
|
1954
|
+
this._size = 0;
|
|
1955
|
+
}
|
|
1956
|
+
};
|
|
1963
1957
|
|
|
1964
1958
|
// src/elements/stroke-smoothing.ts
|
|
1965
1959
|
var MIN_PRESSURE_SCALE = 0.2;
|
|
@@ -2354,51 +2348,6 @@ function updateBoundArrow(arrow, store) {
|
|
|
2354
2348
|
}
|
|
2355
2349
|
return Object.keys(updates).length > 0 ? updates : null;
|
|
2356
2350
|
}
|
|
2357
|
-
function clearStaleBindings(arrow, store) {
|
|
2358
|
-
const updates = {};
|
|
2359
|
-
let hasUpdates = false;
|
|
2360
|
-
if (arrow.fromBinding && !store.getById(arrow.fromBinding.elementId)) {
|
|
2361
|
-
updates.fromBinding = void 0;
|
|
2362
|
-
hasUpdates = true;
|
|
2363
|
-
}
|
|
2364
|
-
if (arrow.toBinding && !store.getById(arrow.toBinding.elementId)) {
|
|
2365
|
-
updates.toBinding = void 0;
|
|
2366
|
-
hasUpdates = true;
|
|
2367
|
-
}
|
|
2368
|
-
return hasUpdates ? updates : null;
|
|
2369
|
-
}
|
|
2370
|
-
function unbindArrow(arrow, store) {
|
|
2371
|
-
const updates = {};
|
|
2372
|
-
if (arrow.fromBinding) {
|
|
2373
|
-
const el = store.getById(arrow.fromBinding.elementId);
|
|
2374
|
-
const bounds = el ? getElementBounds(el) : null;
|
|
2375
|
-
if (bounds) {
|
|
2376
|
-
const angle = getArrowTangentAngle(arrow.from, arrow.to, arrow.bend, 0);
|
|
2377
|
-
const rayTarget = {
|
|
2378
|
-
x: arrow.from.x + Math.cos(angle) * 1e3,
|
|
2379
|
-
y: arrow.from.y + Math.sin(angle) * 1e3
|
|
2380
|
-
};
|
|
2381
|
-
const edge = getEdgeIntersection(bounds, rayTarget);
|
|
2382
|
-
updates.from = edge;
|
|
2383
|
-
updates.position = edge;
|
|
2384
|
-
}
|
|
2385
|
-
updates.fromBinding = void 0;
|
|
2386
|
-
}
|
|
2387
|
-
if (arrow.toBinding) {
|
|
2388
|
-
const el = store.getById(arrow.toBinding.elementId);
|
|
2389
|
-
const bounds = el ? getElementBounds(el) : null;
|
|
2390
|
-
if (bounds) {
|
|
2391
|
-
const angle = getArrowTangentAngle(arrow.from, arrow.to, arrow.bend, 1);
|
|
2392
|
-
const rayTarget = {
|
|
2393
|
-
x: arrow.to.x - Math.cos(angle) * 1e3,
|
|
2394
|
-
y: arrow.to.y - Math.sin(angle) * 1e3
|
|
2395
|
-
};
|
|
2396
|
-
updates.to = getEdgeIntersection(bounds, rayTarget);
|
|
2397
|
-
}
|
|
2398
|
-
updates.toBinding = void 0;
|
|
2399
|
-
}
|
|
2400
|
-
return updates;
|
|
2401
|
-
}
|
|
2402
2351
|
|
|
2403
2352
|
// src/elements/grid-renderer.ts
|
|
2404
2353
|
function getSquareGridLines(bounds, cellSize) {
|
|
@@ -3626,6 +3575,8 @@ var NoteEditor = class {
|
|
|
3626
3575
|
inputHandler = null;
|
|
3627
3576
|
pendingEditId = null;
|
|
3628
3577
|
onStopCallback = null;
|
|
3578
|
+
beginHistory = null;
|
|
3579
|
+
commitHistory = null;
|
|
3629
3580
|
toolbar;
|
|
3630
3581
|
placeholder;
|
|
3631
3582
|
constructor(options) {
|
|
@@ -3641,6 +3592,10 @@ var NoteEditor = class {
|
|
|
3641
3592
|
setOnStop(callback) {
|
|
3642
3593
|
this.onStopCallback = callback;
|
|
3643
3594
|
}
|
|
3595
|
+
setHistoryHooks(begin, commit) {
|
|
3596
|
+
this.beginHistory = begin;
|
|
3597
|
+
this.commitHistory = commit;
|
|
3598
|
+
}
|
|
3644
3599
|
startEditing(node, elementId, store) {
|
|
3645
3600
|
if (this.editingId === elementId) return;
|
|
3646
3601
|
if (this.editingId) {
|
|
@@ -3672,18 +3627,21 @@ var NoteEditor = class {
|
|
|
3672
3627
|
this.editingNode.removeAttribute("data-fn-empty");
|
|
3673
3628
|
const text = sanitizeNoteHtml(this.editingNode.innerHTML);
|
|
3674
3629
|
const current = store.getById(this.editingId);
|
|
3675
|
-
|
|
3676
|
-
store.update(this.editingId, { text });
|
|
3677
|
-
}
|
|
3630
|
+
const textChanged = !!current && (current.type === "note" || current.type === "text") && current.text !== text;
|
|
3678
3631
|
this.editingNode.contentEditable = "false";
|
|
3679
3632
|
Object.assign(this.editingNode.style, {
|
|
3680
3633
|
userSelect: "none",
|
|
3681
3634
|
cursor: "default"
|
|
3682
3635
|
});
|
|
3683
3636
|
this.toolbar?.hide();
|
|
3637
|
+
this.beginHistory?.();
|
|
3638
|
+
if (textChanged) {
|
|
3639
|
+
store.update(this.editingId, { text });
|
|
3640
|
+
}
|
|
3684
3641
|
if (this.editingId && this.onStopCallback) {
|
|
3685
3642
|
this.onStopCallback(this.editingId);
|
|
3686
3643
|
}
|
|
3644
|
+
this.commitHistory?.();
|
|
3687
3645
|
this.editingId = null;
|
|
3688
3646
|
this.editingNode = null;
|
|
3689
3647
|
this.blurHandler = null;
|
|
@@ -3756,26 +3714,6 @@ var NoteEditor = class {
|
|
|
3756
3714
|
}
|
|
3757
3715
|
};
|
|
3758
3716
|
|
|
3759
|
-
// src/elements/bounds.ts
|
|
3760
|
-
function getElementsBoundingBox(elements) {
|
|
3761
|
-
let minX = Infinity;
|
|
3762
|
-
let minY = Infinity;
|
|
3763
|
-
let maxX = -Infinity;
|
|
3764
|
-
let maxY = -Infinity;
|
|
3765
|
-
let found = false;
|
|
3766
|
-
for (const el of elements) {
|
|
3767
|
-
const b = getElementBounds(el);
|
|
3768
|
-
if (!b) continue;
|
|
3769
|
-
found = true;
|
|
3770
|
-
if (b.x < minX) minX = b.x;
|
|
3771
|
-
if (b.y < minY) minY = b.y;
|
|
3772
|
-
if (b.x + b.w > maxX) maxX = b.x + b.w;
|
|
3773
|
-
if (b.y + b.h > maxY) maxY = b.y + b.h;
|
|
3774
|
-
}
|
|
3775
|
-
if (!found) return null;
|
|
3776
|
-
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
3777
|
-
}
|
|
3778
|
-
|
|
3779
3717
|
// src/tools/tool-manager.ts
|
|
3780
3718
|
var ToolManager = class {
|
|
3781
3719
|
tools = /* @__PURE__ */ new Map();
|
|
@@ -4591,6 +4529,48 @@ var InteractMode = class {
|
|
|
4591
4529
|
};
|
|
4592
4530
|
};
|
|
4593
4531
|
|
|
4532
|
+
// src/canvas/double-tap-detector.ts
|
|
4533
|
+
var DEFAULT_TIMEOUT = 300;
|
|
4534
|
+
var DEFAULT_MAX_DISTANCE = 20;
|
|
4535
|
+
var DoubleTapDetector = class {
|
|
4536
|
+
timeout;
|
|
4537
|
+
maxDistance;
|
|
4538
|
+
lastTapTime = 0;
|
|
4539
|
+
lastTapX = 0;
|
|
4540
|
+
lastTapY = 0;
|
|
4541
|
+
hasPendingTap = false;
|
|
4542
|
+
constructor(options) {
|
|
4543
|
+
this.timeout = options?.timeout ?? DEFAULT_TIMEOUT;
|
|
4544
|
+
this.maxDistance = options?.maxDistance ?? DEFAULT_MAX_DISTANCE;
|
|
4545
|
+
}
|
|
4546
|
+
feed(e) {
|
|
4547
|
+
const now = Date.now();
|
|
4548
|
+
const x = e.clientX;
|
|
4549
|
+
const y = e.clientY;
|
|
4550
|
+
if (this.hasPendingTap) {
|
|
4551
|
+
const elapsed = now - this.lastTapTime;
|
|
4552
|
+
const dx = x - this.lastTapX;
|
|
4553
|
+
const dy = y - this.lastTapY;
|
|
4554
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
4555
|
+
if (elapsed <= this.timeout && dist <= this.maxDistance) {
|
|
4556
|
+
this.reset();
|
|
4557
|
+
return true;
|
|
4558
|
+
}
|
|
4559
|
+
}
|
|
4560
|
+
this.lastTapTime = now;
|
|
4561
|
+
this.lastTapX = x;
|
|
4562
|
+
this.lastTapY = y;
|
|
4563
|
+
this.hasPendingTap = true;
|
|
4564
|
+
return false;
|
|
4565
|
+
}
|
|
4566
|
+
reset() {
|
|
4567
|
+
this.hasPendingTap = false;
|
|
4568
|
+
this.lastTapTime = 0;
|
|
4569
|
+
this.lastTapX = 0;
|
|
4570
|
+
this.lastTapY = 0;
|
|
4571
|
+
}
|
|
4572
|
+
};
|
|
4573
|
+
|
|
4594
4574
|
// src/canvas/dom-node-manager.ts
|
|
4595
4575
|
var DomNodeManager = class {
|
|
4596
4576
|
domNodes = /* @__PURE__ */ new Map();
|
|
@@ -5288,6 +5268,10 @@ var Viewport = class {
|
|
|
5288
5268
|
placeholder: options.placeholder
|
|
5289
5269
|
});
|
|
5290
5270
|
this.noteEditor.setOnStop((id) => this.onTextEditStop(id));
|
|
5271
|
+
this.noteEditor.setHistoryHooks(
|
|
5272
|
+
() => this.historyRecorder.begin(),
|
|
5273
|
+
() => this.historyRecorder.commit()
|
|
5274
|
+
);
|
|
5291
5275
|
this.onHtmlElementMount = options.onHtmlElementMount;
|
|
5292
5276
|
this.dropHandler = options.onDrop;
|
|
5293
5277
|
this.history = new HistoryStack();
|
|
@@ -5304,6 +5288,7 @@ var Viewport = class {
|
|
|
5304
5288
|
requestRender: () => this.requestRender(),
|
|
5305
5289
|
switchTool: (name) => this.toolManager.setTool(name, this.toolContext),
|
|
5306
5290
|
editElement: (id) => this.startEditingElement(id),
|
|
5291
|
+
fitNoteHeight: (id) => this.fitNoteHeight(id),
|
|
5307
5292
|
setCursor: (cursor) => {
|
|
5308
5293
|
this.wrapper.style.cursor = cursor;
|
|
5309
5294
|
},
|
|
@@ -5638,31 +5623,38 @@ var Viewport = class {
|
|
|
5638
5623
|
this.noteEditor.startEditing(node, id, this.store);
|
|
5639
5624
|
}
|
|
5640
5625
|
}
|
|
5626
|
+
fitNoteHeight(elementId) {
|
|
5627
|
+
const element = this.store.getById(elementId);
|
|
5628
|
+
if (!element || element.type !== "note") return;
|
|
5629
|
+
if (isNoteContentEmpty(element.text)) return;
|
|
5630
|
+
const node = this.domNodeManager.getNode(elementId);
|
|
5631
|
+
if (!node) return;
|
|
5632
|
+
const measured = node.scrollHeight;
|
|
5633
|
+
if (measured > element.size.h) {
|
|
5634
|
+
this.store.update(elementId, { size: { w: element.size.w, h: measured } });
|
|
5635
|
+
}
|
|
5636
|
+
}
|
|
5641
5637
|
onTextEditStop(elementId) {
|
|
5642
5638
|
const element = this.store.getById(elementId);
|
|
5643
5639
|
if (!element) return;
|
|
5644
5640
|
if (element.type === "note") {
|
|
5645
5641
|
if (isNoteContentEmpty(element.text)) {
|
|
5646
|
-
this.historyRecorder.begin();
|
|
5647
5642
|
this.store.remove(elementId);
|
|
5648
|
-
|
|
5643
|
+
return;
|
|
5649
5644
|
}
|
|
5645
|
+
this.fitNoteHeight(elementId);
|
|
5650
5646
|
return;
|
|
5651
5647
|
}
|
|
5652
5648
|
if (element.type !== "text") return;
|
|
5653
5649
|
if (!element.text || element.text.trim() === "") {
|
|
5654
|
-
this.historyRecorder.begin();
|
|
5655
5650
|
this.store.remove(elementId);
|
|
5656
|
-
this.historyRecorder.commit();
|
|
5657
5651
|
return;
|
|
5658
5652
|
}
|
|
5659
5653
|
const node = this.domNodeManager.getNode(elementId);
|
|
5660
5654
|
if (node && "size" in element) {
|
|
5661
|
-
const
|
|
5662
|
-
if (
|
|
5663
|
-
this.store.update(elementId, {
|
|
5664
|
-
size: { w: element.size.w, h: measuredHeight }
|
|
5665
|
-
});
|
|
5655
|
+
const measured = node.scrollHeight;
|
|
5656
|
+
if (measured !== element.size.h) {
|
|
5657
|
+
this.store.update(elementId, { size: { w: element.size.w, h: measured } });
|
|
5666
5658
|
}
|
|
5667
5659
|
}
|
|
5668
5660
|
}
|
|
@@ -6379,8 +6371,13 @@ var SelectTool = class {
|
|
|
6379
6371
|
}
|
|
6380
6372
|
this.pendingSingleSelectId = null;
|
|
6381
6373
|
this.hasDragged = false;
|
|
6374
|
+
const resizedNoteId = this.mode.type === "resizing" ? this.mode.elementId : null;
|
|
6382
6375
|
this.mode = { type: "idle" };
|
|
6383
6376
|
ctx.setCursor?.("default");
|
|
6377
|
+
if (resizedNoteId !== null) {
|
|
6378
|
+
const el = ctx.store.getById(resizedNoteId);
|
|
6379
|
+
if (el?.type === "note") ctx.fitNoteHeight?.(resizedNoteId);
|
|
6380
|
+
}
|
|
6384
6381
|
}
|
|
6385
6382
|
onHover(state, ctx) {
|
|
6386
6383
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
@@ -7592,53 +7589,33 @@ var TemplateTool = class {
|
|
|
7592
7589
|
};
|
|
7593
7590
|
|
|
7594
7591
|
// src/index.ts
|
|
7595
|
-
var VERSION = "0.
|
|
7592
|
+
var VERSION = "0.26.0";
|
|
7596
7593
|
// Annotate the CommonJS export names for ESM import in node:
|
|
7597
7594
|
0 && (module.exports = {
|
|
7598
|
-
AddElementCommand,
|
|
7599
7595
|
ArrowTool,
|
|
7600
7596
|
AutoSave,
|
|
7601
|
-
Background,
|
|
7602
|
-
BatchCommand,
|
|
7603
7597
|
Camera,
|
|
7604
|
-
CreateLayerCommand,
|
|
7605
|
-
DEFAULT_FONT_SIZE_PRESETS,
|
|
7606
7598
|
DEFAULT_NOTE_FONT_SIZE,
|
|
7607
|
-
DoubleTapDetector,
|
|
7608
|
-
ElementRenderer,
|
|
7609
7599
|
ElementStore,
|
|
7610
7600
|
EraserTool,
|
|
7611
|
-
EventBus,
|
|
7612
7601
|
HandTool,
|
|
7613
|
-
HistoryRecorder,
|
|
7614
7602
|
HistoryStack,
|
|
7615
7603
|
ImageTool,
|
|
7616
|
-
InputFilter,
|
|
7617
|
-
InputHandler,
|
|
7618
7604
|
LayerManager,
|
|
7619
7605
|
MeasureTool,
|
|
7620
|
-
NoteEditor,
|
|
7621
7606
|
NoteTool,
|
|
7622
|
-
NoteToolbar,
|
|
7623
7607
|
PencilTool,
|
|
7624
|
-
Quadtree,
|
|
7625
|
-
RemoveElementCommand,
|
|
7626
|
-
RemoveLayerCommand,
|
|
7627
7608
|
SelectTool,
|
|
7628
7609
|
ShapeTool,
|
|
7629
7610
|
TemplateTool,
|
|
7630
7611
|
TextTool,
|
|
7631
7612
|
ToolManager,
|
|
7632
|
-
UpdateElementCommand,
|
|
7633
|
-
UpdateLayerCommand,
|
|
7634
7613
|
VERSION,
|
|
7635
7614
|
Viewport,
|
|
7636
7615
|
boundsIntersect,
|
|
7637
|
-
clearStaleBindings,
|
|
7638
7616
|
createArrow,
|
|
7639
7617
|
createGrid,
|
|
7640
7618
|
createHtmlElement,
|
|
7641
|
-
createId,
|
|
7642
7619
|
createImage,
|
|
7643
7620
|
createNote,
|
|
7644
7621
|
createShape,
|
|
@@ -7647,29 +7624,20 @@ var VERSION = "0.24.0";
|
|
|
7647
7624
|
createText,
|
|
7648
7625
|
drawHexPath,
|
|
7649
7626
|
exportImage,
|
|
7650
|
-
exportState,
|
|
7651
|
-
findBindTarget,
|
|
7652
|
-
findBoundArrows,
|
|
7653
7627
|
getActiveFormats,
|
|
7654
7628
|
getArrowBounds,
|
|
7655
7629
|
getArrowControlPoint,
|
|
7656
7630
|
getArrowMidpoint,
|
|
7657
7631
|
getArrowTangentAngle,
|
|
7658
7632
|
getBendFromPoint,
|
|
7659
|
-
getEdgeIntersection,
|
|
7660
7633
|
getElementBounds,
|
|
7661
|
-
getElementCenter,
|
|
7662
7634
|
getElementsBoundingBox,
|
|
7663
7635
|
getHexCellsInCone,
|
|
7664
7636
|
getHexCellsInLine,
|
|
7665
7637
|
getHexCellsInRadius,
|
|
7666
7638
|
getHexCellsInSquare,
|
|
7667
7639
|
getHexDistance,
|
|
7668
|
-
isBindable,
|
|
7669
7640
|
isNearBezier,
|
|
7670
|
-
isNoteContentEmpty,
|
|
7671
|
-
parseState,
|
|
7672
|
-
sanitizeNoteHtml,
|
|
7673
7641
|
setFontSize,
|
|
7674
7642
|
smartSnap,
|
|
7675
7643
|
snapPoint,
|
|
@@ -7677,8 +7645,6 @@ var VERSION = "0.24.0";
|
|
|
7677
7645
|
toggleBold,
|
|
7678
7646
|
toggleItalic,
|
|
7679
7647
|
toggleStrikethrough,
|
|
7680
|
-
toggleUnderline
|
|
7681
|
-
unbindArrow,
|
|
7682
|
-
updateBoundArrow
|
|
7648
|
+
toggleUnderline
|
|
7683
7649
|
});
|
|
7684
7650
|
//# sourceMappingURL=index.cjs.map
|