@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.js
CHANGED
|
@@ -1,179 +1,34 @@
|
|
|
1
|
-
// src/core/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
existing.add(listener);
|
|
8
|
-
} else {
|
|
9
|
-
const set = /* @__PURE__ */ new Set([listener]);
|
|
10
|
-
this.listeners.set(event, set);
|
|
11
|
-
}
|
|
12
|
-
return () => this.off(event, listener);
|
|
13
|
-
}
|
|
14
|
-
off(event, listener) {
|
|
15
|
-
this.listeners.get(event)?.delete(listener);
|
|
16
|
-
}
|
|
17
|
-
emit(event, data) {
|
|
18
|
-
this.listeners.get(event)?.forEach((listener) => {
|
|
19
|
-
try {
|
|
20
|
-
listener(data);
|
|
21
|
-
} catch (err) {
|
|
22
|
-
console.error(`[fieldnotes] listener error for "${String(event)}"`, err);
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
clear() {
|
|
27
|
-
this.listeners.clear();
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
// src/core/quadtree.ts
|
|
32
|
-
var MAX_ITEMS = 8;
|
|
33
|
-
var MAX_DEPTH = 8;
|
|
34
|
-
function intersects(a, b) {
|
|
35
|
-
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;
|
|
1
|
+
// src/core/snap.ts
|
|
2
|
+
function snapPoint(point, gridSize) {
|
|
3
|
+
return {
|
|
4
|
+
x: Math.round(point.x / gridSize) * gridSize || 0,
|
|
5
|
+
y: Math.round(point.y / gridSize) * gridSize || 0
|
|
6
|
+
};
|
|
36
7
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
this.items.push(entry);
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
this.items.push(entry);
|
|
56
|
-
if (this.items.length > MAX_ITEMS && this.depth < MAX_DEPTH) {
|
|
57
|
-
this.split();
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
remove(id) {
|
|
61
|
-
const idx = this.items.findIndex((e) => e.id === id);
|
|
62
|
-
if (idx !== -1) {
|
|
63
|
-
this.items.splice(idx, 1);
|
|
64
|
-
return true;
|
|
65
|
-
}
|
|
66
|
-
if (this.children) {
|
|
67
|
-
for (const child of this.children) {
|
|
68
|
-
if (child.remove(id)) {
|
|
69
|
-
this.collapseIfEmpty();
|
|
70
|
-
return true;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
query(rect, result) {
|
|
77
|
-
if (!intersects(this.bounds, rect)) return;
|
|
78
|
-
for (const item of this.items) {
|
|
79
|
-
if (intersects(item.bounds, rect)) {
|
|
80
|
-
result.push(item.id);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
if (this.children) {
|
|
84
|
-
for (const child of this.children) {
|
|
85
|
-
child.query(rect, result);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
getChildIndex(itemBounds) {
|
|
90
|
-
const midX = this.bounds.x + this.bounds.w / 2;
|
|
91
|
-
const midY = this.bounds.y + this.bounds.h / 2;
|
|
92
|
-
const left = itemBounds.x >= this.bounds.x && itemBounds.x + itemBounds.w <= midX;
|
|
93
|
-
const right = itemBounds.x >= midX && itemBounds.x + itemBounds.w <= this.bounds.x + this.bounds.w;
|
|
94
|
-
const top = itemBounds.y >= this.bounds.y && itemBounds.y + itemBounds.h <= midY;
|
|
95
|
-
const bottom = itemBounds.y >= midY && itemBounds.y + itemBounds.h <= this.bounds.y + this.bounds.h;
|
|
96
|
-
if (left && top) return 0;
|
|
97
|
-
if (right && top) return 1;
|
|
98
|
-
if (left && bottom) return 2;
|
|
99
|
-
if (right && bottom) return 3;
|
|
100
|
-
return -1;
|
|
101
|
-
}
|
|
102
|
-
split() {
|
|
103
|
-
const { x, y, w, h } = this.bounds;
|
|
104
|
-
const halfW = w / 2;
|
|
105
|
-
const halfH = h / 2;
|
|
106
|
-
const d = this.depth + 1;
|
|
107
|
-
this.children = [
|
|
108
|
-
new _QuadNode({ x, y, w: halfW, h: halfH }, d),
|
|
109
|
-
new _QuadNode({ x: x + halfW, y, w: halfW, h: halfH }, d),
|
|
110
|
-
new _QuadNode({ x, y: y + halfH, w: halfW, h: halfH }, d),
|
|
111
|
-
new _QuadNode({ x: x + halfW, y: y + halfH, w: halfW, h: halfH }, d)
|
|
112
|
-
];
|
|
113
|
-
const remaining = [];
|
|
114
|
-
for (const item of this.items) {
|
|
115
|
-
const idx = this.getChildIndex(item.bounds);
|
|
116
|
-
if (idx !== -1) {
|
|
117
|
-
const target = this.children[idx];
|
|
118
|
-
if (target) target.insert(item);
|
|
119
|
-
} else {
|
|
120
|
-
remaining.push(item);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
this.items = remaining;
|
|
124
|
-
}
|
|
125
|
-
collapseIfEmpty() {
|
|
126
|
-
if (!this.children) return;
|
|
127
|
-
let totalItems = this.items.length;
|
|
128
|
-
for (const child of this.children) {
|
|
129
|
-
if (child.children) return;
|
|
130
|
-
totalItems += child.items.length;
|
|
131
|
-
}
|
|
132
|
-
if (totalItems <= MAX_ITEMS) {
|
|
133
|
-
for (const child of this.children) {
|
|
134
|
-
this.items.push(...child.items);
|
|
135
|
-
}
|
|
136
|
-
this.children = null;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
};
|
|
140
|
-
var Quadtree = class {
|
|
141
|
-
root;
|
|
142
|
-
_size = 0;
|
|
143
|
-
worldBounds;
|
|
144
|
-
constructor(worldBounds) {
|
|
145
|
-
this.worldBounds = worldBounds;
|
|
146
|
-
this.root = new QuadNode(worldBounds, 0);
|
|
147
|
-
}
|
|
148
|
-
get size() {
|
|
149
|
-
return this._size;
|
|
150
|
-
}
|
|
151
|
-
insert(id, bounds) {
|
|
152
|
-
this.root.insert({ id, bounds });
|
|
153
|
-
this._size++;
|
|
154
|
-
}
|
|
155
|
-
remove(id) {
|
|
156
|
-
if (this.root.remove(id)) {
|
|
157
|
-
this._size--;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
update(id, newBounds) {
|
|
161
|
-
this.remove(id);
|
|
162
|
-
this.insert(id, newBounds);
|
|
163
|
-
}
|
|
164
|
-
query(rect) {
|
|
165
|
-
const result = [];
|
|
166
|
-
this.root.query(rect, result);
|
|
167
|
-
return result;
|
|
168
|
-
}
|
|
169
|
-
queryPoint(point) {
|
|
170
|
-
return this.query({ x: point.x, y: point.y, w: 0, h: 0 });
|
|
8
|
+
function snapToHexCenter(point, cellSize, orientation) {
|
|
9
|
+
if (orientation === "pointy") {
|
|
10
|
+
const hexW = Math.sqrt(3) * cellSize;
|
|
11
|
+
const rowH = 1.5 * cellSize;
|
|
12
|
+
const row = Math.round(point.y / rowH);
|
|
13
|
+
const offsetX = row % 2 !== 0 ? hexW / 2 : 0;
|
|
14
|
+
const col = Math.round((point.x - offsetX) / hexW);
|
|
15
|
+
return { x: col * hexW + offsetX || 0, y: row * rowH || 0 };
|
|
16
|
+
} else {
|
|
17
|
+
const hexH = Math.sqrt(3) * cellSize;
|
|
18
|
+
const colW = 1.5 * cellSize;
|
|
19
|
+
const col = Math.round(point.x / colW);
|
|
20
|
+
const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
21
|
+
const row = Math.round((point.y - offsetY) / hexH);
|
|
22
|
+
return { x: col * colW || 0, y: row * hexH + offsetY || 0 };
|
|
171
23
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
24
|
+
}
|
|
25
|
+
function smartSnap(point, ctx) {
|
|
26
|
+
if (!ctx.snapToGrid || !ctx.gridSize) return point;
|
|
27
|
+
if (ctx.gridType === "hex" && ctx.hexOrientation) {
|
|
28
|
+
return snapToHexCenter(point, ctx.gridSize, ctx.hexOrientation);
|
|
175
29
|
}
|
|
176
|
-
|
|
30
|
+
return snapPoint(point, ctx.gridSize);
|
|
31
|
+
}
|
|
177
32
|
|
|
178
33
|
// src/elements/note-sanitizer.ts
|
|
179
34
|
var BOLD_TAGS = /* @__PURE__ */ new Set(["b", "strong"]);
|
|
@@ -430,38 +285,6 @@ function migrateElement(obj) {
|
|
|
430
285
|
}
|
|
431
286
|
}
|
|
432
287
|
|
|
433
|
-
// src/core/snap.ts
|
|
434
|
-
function snapPoint(point, gridSize) {
|
|
435
|
-
return {
|
|
436
|
-
x: Math.round(point.x / gridSize) * gridSize || 0,
|
|
437
|
-
y: Math.round(point.y / gridSize) * gridSize || 0
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
function snapToHexCenter(point, cellSize, orientation) {
|
|
441
|
-
if (orientation === "pointy") {
|
|
442
|
-
const hexW = Math.sqrt(3) * cellSize;
|
|
443
|
-
const rowH = 1.5 * cellSize;
|
|
444
|
-
const row = Math.round(point.y / rowH);
|
|
445
|
-
const offsetX = row % 2 !== 0 ? hexW / 2 : 0;
|
|
446
|
-
const col = Math.round((point.x - offsetX) / hexW);
|
|
447
|
-
return { x: col * hexW + offsetX || 0, y: row * rowH || 0 };
|
|
448
|
-
} else {
|
|
449
|
-
const hexH = Math.sqrt(3) * cellSize;
|
|
450
|
-
const colW = 1.5 * cellSize;
|
|
451
|
-
const col = Math.round(point.x / colW);
|
|
452
|
-
const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
453
|
-
const row = Math.round((point.y - offsetY) / hexH);
|
|
454
|
-
return { x: col * colW || 0, y: row * hexH + offsetY || 0 };
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
function smartSnap(point, ctx) {
|
|
458
|
-
if (!ctx.snapToGrid || !ctx.gridSize) return point;
|
|
459
|
-
if (ctx.gridType === "hex" && ctx.hexOrientation) {
|
|
460
|
-
return snapToHexCenter(point, ctx.gridSize, ctx.hexOrientation);
|
|
461
|
-
}
|
|
462
|
-
return snapPoint(point, ctx.gridSize);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
288
|
// src/core/auto-save.ts
|
|
466
289
|
var DEFAULT_KEY = "fieldnotes-autosave";
|
|
467
290
|
var DEFAULT_DEBOUNCE_MS = 1e3;
|
|
@@ -625,143 +448,6 @@ var Camera = class {
|
|
|
625
448
|
}
|
|
626
449
|
};
|
|
627
450
|
|
|
628
|
-
// src/canvas/background.ts
|
|
629
|
-
var MIN_PATTERN_SPACING = 16;
|
|
630
|
-
var DEFAULTS = {
|
|
631
|
-
pattern: "dots",
|
|
632
|
-
spacing: 24,
|
|
633
|
-
color: "#d0d0d0",
|
|
634
|
-
dotRadius: 1,
|
|
635
|
-
lineWidth: 0.5
|
|
636
|
-
};
|
|
637
|
-
var Background = class {
|
|
638
|
-
pattern;
|
|
639
|
-
spacing;
|
|
640
|
-
color;
|
|
641
|
-
dotRadius;
|
|
642
|
-
lineWidth;
|
|
643
|
-
cachedCanvas = null;
|
|
644
|
-
cachedCtx = null;
|
|
645
|
-
lastZoom = -1;
|
|
646
|
-
lastOffsetX = -Infinity;
|
|
647
|
-
lastOffsetY = -Infinity;
|
|
648
|
-
lastWidth = 0;
|
|
649
|
-
lastHeight = 0;
|
|
650
|
-
constructor(options = {}) {
|
|
651
|
-
this.pattern = options.pattern ?? DEFAULTS.pattern;
|
|
652
|
-
this.spacing = options.spacing ?? DEFAULTS.spacing;
|
|
653
|
-
this.color = options.color ?? DEFAULTS.color;
|
|
654
|
-
this.dotRadius = options.dotRadius ?? DEFAULTS.dotRadius;
|
|
655
|
-
this.lineWidth = options.lineWidth ?? DEFAULTS.lineWidth;
|
|
656
|
-
}
|
|
657
|
-
render(ctx, camera) {
|
|
658
|
-
const { width, height } = ctx.canvas;
|
|
659
|
-
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
660
|
-
const cssWidth = width / dpr;
|
|
661
|
-
const cssHeight = height / dpr;
|
|
662
|
-
ctx.save();
|
|
663
|
-
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
664
|
-
ctx.clearRect(0, 0, cssWidth, cssHeight);
|
|
665
|
-
if (this.pattern === "none") {
|
|
666
|
-
ctx.restore();
|
|
667
|
-
return;
|
|
668
|
-
}
|
|
669
|
-
const spacing = this.adaptSpacing(this.spacing, camera.zoom);
|
|
670
|
-
const keyZoom = camera.zoom;
|
|
671
|
-
const keyX = Math.floor(camera.position.x % spacing);
|
|
672
|
-
const keyY = Math.floor(camera.position.y % spacing);
|
|
673
|
-
if (this.cachedCanvas !== null && keyZoom === this.lastZoom && keyX === this.lastOffsetX && keyY === this.lastOffsetY && cssWidth === this.lastWidth && cssHeight === this.lastHeight) {
|
|
674
|
-
ctx.drawImage(this.cachedCanvas, 0, 0);
|
|
675
|
-
ctx.restore();
|
|
676
|
-
return;
|
|
677
|
-
}
|
|
678
|
-
this.ensureCachedCanvas(cssWidth, cssHeight, dpr);
|
|
679
|
-
if (this.cachedCtx === null) {
|
|
680
|
-
if (this.pattern === "dots") {
|
|
681
|
-
this.renderDots(ctx, camera, cssWidth, cssHeight);
|
|
682
|
-
} else if (this.pattern === "grid") {
|
|
683
|
-
this.renderGrid(ctx, camera, cssWidth, cssHeight);
|
|
684
|
-
}
|
|
685
|
-
ctx.restore();
|
|
686
|
-
return;
|
|
687
|
-
}
|
|
688
|
-
const offCtx = this.cachedCtx;
|
|
689
|
-
offCtx.clearRect(0, 0, cssWidth, cssHeight);
|
|
690
|
-
if (this.pattern === "dots") {
|
|
691
|
-
this.renderDots(offCtx, camera, cssWidth, cssHeight);
|
|
692
|
-
} else if (this.pattern === "grid") {
|
|
693
|
-
this.renderGrid(offCtx, camera, cssWidth, cssHeight);
|
|
694
|
-
}
|
|
695
|
-
this.lastZoom = keyZoom;
|
|
696
|
-
this.lastOffsetX = keyX;
|
|
697
|
-
this.lastOffsetY = keyY;
|
|
698
|
-
this.lastWidth = cssWidth;
|
|
699
|
-
this.lastHeight = cssHeight;
|
|
700
|
-
ctx.drawImage(this.cachedCanvas, 0, 0);
|
|
701
|
-
ctx.restore();
|
|
702
|
-
}
|
|
703
|
-
ensureCachedCanvas(cssWidth, cssHeight, dpr) {
|
|
704
|
-
if (this.cachedCanvas !== null && this.lastWidth === cssWidth && this.lastHeight === cssHeight) {
|
|
705
|
-
return;
|
|
706
|
-
}
|
|
707
|
-
const physWidth = Math.round(cssWidth * dpr);
|
|
708
|
-
const physHeight = Math.round(cssHeight * dpr);
|
|
709
|
-
if (typeof OffscreenCanvas !== "undefined") {
|
|
710
|
-
this.cachedCanvas = new OffscreenCanvas(physWidth, physHeight);
|
|
711
|
-
} else if (typeof document !== "undefined") {
|
|
712
|
-
const el = document.createElement("canvas");
|
|
713
|
-
el.width = physWidth;
|
|
714
|
-
el.height = physHeight;
|
|
715
|
-
this.cachedCanvas = el;
|
|
716
|
-
} else {
|
|
717
|
-
this.cachedCanvas = null;
|
|
718
|
-
this.cachedCtx = null;
|
|
719
|
-
return;
|
|
720
|
-
}
|
|
721
|
-
const offCtx = this.cachedCanvas.getContext("2d");
|
|
722
|
-
if (offCtx !== null) {
|
|
723
|
-
offCtx.scale(dpr, dpr);
|
|
724
|
-
}
|
|
725
|
-
this.cachedCtx = offCtx;
|
|
726
|
-
this.lastZoom = -1;
|
|
727
|
-
}
|
|
728
|
-
adaptSpacing(baseSpacing, zoom) {
|
|
729
|
-
let spacing = baseSpacing * zoom;
|
|
730
|
-
while (spacing < MIN_PATTERN_SPACING) {
|
|
731
|
-
spacing *= 2;
|
|
732
|
-
}
|
|
733
|
-
return spacing;
|
|
734
|
-
}
|
|
735
|
-
renderDots(ctx, camera, width, height) {
|
|
736
|
-
const spacing = this.adaptSpacing(this.spacing, camera.zoom);
|
|
737
|
-
const offsetX = camera.position.x % spacing;
|
|
738
|
-
const offsetY = camera.position.y % spacing;
|
|
739
|
-
const radius = this.dotRadius * Math.min(camera.zoom, 2);
|
|
740
|
-
ctx.fillStyle = this.color;
|
|
741
|
-
ctx.beginPath();
|
|
742
|
-
for (let x = offsetX; x < width; x += spacing) {
|
|
743
|
-
for (let y = offsetY; y < height; y += spacing) {
|
|
744
|
-
ctx.moveTo(x + radius, y);
|
|
745
|
-
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
ctx.fill();
|
|
749
|
-
}
|
|
750
|
-
renderGrid(ctx, camera, width, height) {
|
|
751
|
-
const spacing = this.adaptSpacing(this.spacing, camera.zoom);
|
|
752
|
-
const offsetX = camera.position.x % spacing;
|
|
753
|
-
const offsetY = camera.position.y % spacing;
|
|
754
|
-
const lineW = this.lineWidth * Math.min(camera.zoom, 2);
|
|
755
|
-
ctx.fillStyle = this.color;
|
|
756
|
-
for (let x = offsetX; x < width; x += spacing) {
|
|
757
|
-
ctx.fillRect(x, 0, lineW, height);
|
|
758
|
-
}
|
|
759
|
-
for (let y = offsetY; y < height; y += spacing) {
|
|
760
|
-
ctx.fillRect(0, y, width, lineW);
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
};
|
|
764
|
-
|
|
765
451
|
// src/canvas/input-filter.ts
|
|
766
452
|
var InputFilter = class _InputFilter {
|
|
767
453
|
activePenId = null;
|
|
@@ -823,62 +509,334 @@ function createId(prefix) {
|
|
|
823
509
|
return `${prefix}_${Date.now().toString(36)}_${(counter++).toString(36)}`;
|
|
824
510
|
}
|
|
825
511
|
|
|
826
|
-
// src/
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
dispose() {
|
|
836
|
-
this.flushPendingNudge();
|
|
512
|
+
// src/core/geometry.ts
|
|
513
|
+
function distSqToSegment(p, a, b) {
|
|
514
|
+
const abx = b.x - a.x;
|
|
515
|
+
const aby = b.y - a.y;
|
|
516
|
+
const apx = p.x - a.x;
|
|
517
|
+
const apy = p.y - a.y;
|
|
518
|
+
const lenSq = abx * abx + aby * aby;
|
|
519
|
+
if (lenSq === 0) {
|
|
520
|
+
return apx * apx + apy * apy;
|
|
837
521
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
522
|
+
const t = Math.max(0, Math.min(1, (apx * abx + apy * aby) / lenSq));
|
|
523
|
+
const dx = p.x - (a.x + t * abx);
|
|
524
|
+
const dy = p.y - (a.y + t * aby);
|
|
525
|
+
return dx * dx + dy * dy;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// src/elements/arrow-geometry.ts
|
|
529
|
+
function getArrowControlPoint(from, to, bend) {
|
|
530
|
+
const midX = (from.x + to.x) / 2;
|
|
531
|
+
const midY = (from.y + to.y) / 2;
|
|
532
|
+
if (bend === 0) return { x: midX, y: midY };
|
|
533
|
+
const dx = to.x - from.x;
|
|
534
|
+
const dy = to.y - from.y;
|
|
535
|
+
const len = Math.sqrt(dx * dx + dy * dy);
|
|
536
|
+
if (len === 0) return { x: midX, y: midY };
|
|
537
|
+
const perpX = -dy / len;
|
|
538
|
+
const perpY = dx / len;
|
|
539
|
+
return {
|
|
540
|
+
x: midX + perpX * bend,
|
|
541
|
+
y: midY + perpY * bend
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
function getArrowMidpoint(from, to, bend) {
|
|
545
|
+
const cp = getArrowControlPoint(from, to, bend);
|
|
546
|
+
return {
|
|
547
|
+
x: 0.25 * from.x + 0.5 * cp.x + 0.25 * to.x,
|
|
548
|
+
y: 0.25 * from.y + 0.5 * cp.y + 0.25 * to.y
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
function getBendFromPoint(from, to, dragPoint) {
|
|
552
|
+
const midX = (from.x + to.x) / 2;
|
|
553
|
+
const midY = (from.y + to.y) / 2;
|
|
554
|
+
const dx = to.x - from.x;
|
|
555
|
+
const dy = to.y - from.y;
|
|
556
|
+
const len = Math.sqrt(dx * dx + dy * dy);
|
|
557
|
+
if (len === 0) return 0;
|
|
558
|
+
const perpX = -dy / len;
|
|
559
|
+
const perpY = dx / len;
|
|
560
|
+
return (dragPoint.x - midX) * perpX + (dragPoint.y - midY) * perpY;
|
|
561
|
+
}
|
|
562
|
+
function getArrowTangentAngle(from, to, bend, t) {
|
|
563
|
+
const cp = getArrowControlPoint(from, to, bend);
|
|
564
|
+
const tangentX = 2 * (1 - t) * (cp.x - from.x) + 2 * t * (to.x - cp.x);
|
|
565
|
+
const tangentY = 2 * (1 - t) * (cp.y - from.y) + 2 * t * (to.y - cp.y);
|
|
566
|
+
return Math.atan2(tangentY, tangentX);
|
|
567
|
+
}
|
|
568
|
+
function isNearBezier(point, from, to, bend, threshold) {
|
|
569
|
+
if (bend === 0) return isNearLine(point, from, to, threshold);
|
|
570
|
+
const cp = getArrowControlPoint(from, to, bend);
|
|
571
|
+
const segments = 20;
|
|
572
|
+
for (let i = 0; i < segments; i++) {
|
|
573
|
+
const t0 = i / segments;
|
|
574
|
+
const t1 = (i + 1) / segments;
|
|
575
|
+
const a = bezierPoint(from, cp, to, t0);
|
|
576
|
+
const b = bezierPoint(from, cp, to, t1);
|
|
577
|
+
if (isNearLine(point, a, b, threshold)) return true;
|
|
845
578
|
}
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
const
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
}
|
|
859
|
-
const moved = sel.tool.nudgeSelection(dx * step, dy * step, sel.ctx);
|
|
860
|
-
this.nudgeTimer = setTimeout(() => this.flushPendingNudge(), 400);
|
|
861
|
-
return moved;
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
function getArrowBounds(from, to, bend) {
|
|
582
|
+
if (bend === 0) {
|
|
583
|
+
const minX2 = Math.min(from.x, to.x);
|
|
584
|
+
const minY2 = Math.min(from.y, to.y);
|
|
585
|
+
return {
|
|
586
|
+
x: minX2,
|
|
587
|
+
y: minY2,
|
|
588
|
+
w: Math.abs(to.x - from.x),
|
|
589
|
+
h: Math.abs(to.y - from.y)
|
|
590
|
+
};
|
|
862
591
|
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
592
|
+
const cp = getArrowControlPoint(from, to, bend);
|
|
593
|
+
const steps = 20;
|
|
594
|
+
let minX = Math.min(from.x, to.x);
|
|
595
|
+
let minY = Math.min(from.y, to.y);
|
|
596
|
+
let maxX = Math.max(from.x, to.x);
|
|
597
|
+
let maxY = Math.max(from.y, to.y);
|
|
598
|
+
for (let i = 1; i < steps; i++) {
|
|
599
|
+
const t = i / steps;
|
|
600
|
+
const p = bezierPoint(from, cp, to, t);
|
|
601
|
+
if (p.x < minX) minX = p.x;
|
|
602
|
+
if (p.y < minY) minY = p.y;
|
|
603
|
+
if (p.x > maxX) maxX = p.x;
|
|
604
|
+
if (p.y > maxY) maxY = p.y;
|
|
872
605
|
}
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
606
|
+
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
607
|
+
}
|
|
608
|
+
function bezierPoint(from, cp, to, t) {
|
|
609
|
+
const mt = 1 - t;
|
|
610
|
+
return {
|
|
611
|
+
x: mt * mt * from.x + 2 * mt * t * cp.x + t * t * to.x,
|
|
612
|
+
y: mt * mt * from.y + 2 * mt * t * cp.y + t * t * to.y
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
function isNearLine(point, a, b, threshold) {
|
|
616
|
+
return distSqToSegment(point, a, b) <= threshold * threshold;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// src/elements/element-bounds.ts
|
|
620
|
+
var strokeBoundsCache = /* @__PURE__ */ new WeakMap();
|
|
621
|
+
function getElementBounds(element) {
|
|
622
|
+
if (element.type === "grid") return null;
|
|
623
|
+
if ("size" in element) {
|
|
624
|
+
return {
|
|
625
|
+
x: element.position.x,
|
|
626
|
+
y: element.position.y,
|
|
627
|
+
w: element.size.w,
|
|
628
|
+
h: element.size.h
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
if (element.type === "stroke") {
|
|
632
|
+
if (element.points.length === 0) return null;
|
|
633
|
+
const cached = strokeBoundsCache.get(element);
|
|
634
|
+
if (cached) return cached;
|
|
635
|
+
let minX = Infinity;
|
|
636
|
+
let minY = Infinity;
|
|
637
|
+
let maxX = -Infinity;
|
|
638
|
+
let maxY = -Infinity;
|
|
639
|
+
for (const p of element.points) {
|
|
640
|
+
const px = p.x + element.position.x;
|
|
641
|
+
const py = p.y + element.position.y;
|
|
642
|
+
if (px < minX) minX = px;
|
|
643
|
+
if (py < minY) minY = py;
|
|
644
|
+
if (px > maxX) maxX = px;
|
|
645
|
+
if (py > maxY) maxY = py;
|
|
646
|
+
}
|
|
647
|
+
const bounds = { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
648
|
+
strokeBoundsCache.set(element, bounds);
|
|
649
|
+
return bounds;
|
|
650
|
+
}
|
|
651
|
+
if (element.type === "arrow") {
|
|
652
|
+
return getArrowBoundsAnalytical(element.from, element.to, element.bend);
|
|
653
|
+
}
|
|
654
|
+
if (element.type === "template") {
|
|
655
|
+
return getTemplateBounds(element);
|
|
656
|
+
}
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
function getArrowBoundsAnalytical(from, to, bend) {
|
|
660
|
+
if (bend === 0) {
|
|
661
|
+
const minX2 = Math.min(from.x, to.x);
|
|
662
|
+
const minY2 = Math.min(from.y, to.y);
|
|
663
|
+
return {
|
|
664
|
+
x: minX2,
|
|
665
|
+
y: minY2,
|
|
666
|
+
w: Math.abs(to.x - from.x),
|
|
667
|
+
h: Math.abs(to.y - from.y)
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
const cp = getArrowControlPoint(from, to, bend);
|
|
671
|
+
let minX = Math.min(from.x, to.x);
|
|
672
|
+
let maxX = Math.max(from.x, to.x);
|
|
673
|
+
let minY = Math.min(from.y, to.y);
|
|
674
|
+
let maxY = Math.max(from.y, to.y);
|
|
675
|
+
const tx = from.x - 2 * cp.x + to.x;
|
|
676
|
+
if (tx !== 0) {
|
|
677
|
+
const t = (from.x - cp.x) / tx;
|
|
678
|
+
if (t > 0 && t < 1) {
|
|
679
|
+
const mt = 1 - t;
|
|
680
|
+
const x = mt * mt * from.x + 2 * mt * t * cp.x + t * t * to.x;
|
|
681
|
+
if (x < minX) minX = x;
|
|
682
|
+
if (x > maxX) maxX = x;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
const ty = from.y - 2 * cp.y + to.y;
|
|
686
|
+
if (ty !== 0) {
|
|
687
|
+
const t = (from.y - cp.y) / ty;
|
|
688
|
+
if (t > 0 && t < 1) {
|
|
689
|
+
const mt = 1 - t;
|
|
690
|
+
const y = mt * mt * from.y + 2 * mt * t * cp.y + t * t * to.y;
|
|
691
|
+
if (y < minY) minY = y;
|
|
692
|
+
if (y > maxY) maxY = y;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
696
|
+
}
|
|
697
|
+
function getTemplateBounds(el) {
|
|
698
|
+
const { x: cx, y: cy } = el.position;
|
|
699
|
+
const r = el.radius;
|
|
700
|
+
switch (el.templateShape) {
|
|
701
|
+
case "circle":
|
|
702
|
+
return { x: cx - r, y: cy - r, w: 2 * r, h: 2 * r };
|
|
703
|
+
case "square":
|
|
704
|
+
return { x: cx - r / 2, y: cy - r / 2, w: r, h: r };
|
|
705
|
+
case "cone": {
|
|
706
|
+
const halfAngle = Math.atan(0.5);
|
|
707
|
+
const tipX = cx;
|
|
708
|
+
const tipY = cy;
|
|
709
|
+
const leftX = cx + r * Math.cos(el.angle - halfAngle);
|
|
710
|
+
const leftY = cy + r * Math.sin(el.angle - halfAngle);
|
|
711
|
+
const rightX = cx + r * Math.cos(el.angle + halfAngle);
|
|
712
|
+
const rightY = cy + r * Math.sin(el.angle + halfAngle);
|
|
713
|
+
const farX = cx + r * Math.cos(el.angle);
|
|
714
|
+
const farY = cy + r * Math.sin(el.angle);
|
|
715
|
+
const xs = [tipX, leftX, rightX, farX];
|
|
716
|
+
const ys = [tipY, leftY, rightY, farY];
|
|
717
|
+
let minX = Infinity;
|
|
718
|
+
let minY = Infinity;
|
|
719
|
+
let maxX = -Infinity;
|
|
720
|
+
let maxY = -Infinity;
|
|
721
|
+
for (let i = 0; i < xs.length; i++) {
|
|
722
|
+
const px = xs[i];
|
|
723
|
+
const py = ys[i];
|
|
724
|
+
if (px !== void 0 && px < minX) minX = px;
|
|
725
|
+
if (px !== void 0 && px > maxX) maxX = px;
|
|
726
|
+
if (py !== void 0 && py < minY) minY = py;
|
|
727
|
+
if (py !== void 0 && py > maxY) maxY = py;
|
|
728
|
+
}
|
|
729
|
+
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
730
|
+
}
|
|
731
|
+
case "line": {
|
|
732
|
+
const halfW = r / 12;
|
|
733
|
+
const cos = Math.cos(el.angle);
|
|
734
|
+
const sin = Math.sin(el.angle);
|
|
735
|
+
const perpX = -sin * halfW;
|
|
736
|
+
const perpY = cos * halfW;
|
|
737
|
+
const x0 = cx + perpX;
|
|
738
|
+
const y0 = cy + perpY;
|
|
739
|
+
const x1 = cx + r * cos + perpX;
|
|
740
|
+
const y1 = cy + r * sin + perpY;
|
|
741
|
+
const x2 = cx + r * cos - perpX;
|
|
742
|
+
const y2 = cy + r * sin - perpY;
|
|
743
|
+
const x3 = cx - perpX;
|
|
744
|
+
const y3 = cy - perpY;
|
|
745
|
+
const minX = Math.min(x0, x1, x2, x3);
|
|
746
|
+
const minY = Math.min(y0, y1, y2, y3);
|
|
747
|
+
const maxX = Math.max(x0, x1, x2, x3);
|
|
748
|
+
const maxY = Math.max(y0, y1, y2, y3);
|
|
749
|
+
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
function transferStrokeBounds(prev, next) {
|
|
754
|
+
if (prev.type !== "stroke" || next.type !== "stroke") return;
|
|
755
|
+
if (prev.points !== next.points) return;
|
|
756
|
+
if (prev.position.x !== next.position.x || prev.position.y !== next.position.y) return;
|
|
757
|
+
const bounds = strokeBoundsCache.get(prev);
|
|
758
|
+
if (bounds) strokeBoundsCache.set(next, bounds);
|
|
759
|
+
}
|
|
760
|
+
function boundsIntersect(a, b) {
|
|
761
|
+
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;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// src/elements/bounds.ts
|
|
765
|
+
function getElementsBoundingBox(elements) {
|
|
766
|
+
let minX = Infinity;
|
|
767
|
+
let minY = Infinity;
|
|
768
|
+
let maxX = -Infinity;
|
|
769
|
+
let maxY = -Infinity;
|
|
770
|
+
let found = false;
|
|
771
|
+
for (const el of elements) {
|
|
772
|
+
const b = getElementBounds(el);
|
|
773
|
+
if (!b) continue;
|
|
774
|
+
found = true;
|
|
775
|
+
if (b.x < minX) minX = b.x;
|
|
776
|
+
if (b.y < minY) minY = b.y;
|
|
777
|
+
if (b.x + b.w > maxX) maxX = b.x + b.w;
|
|
778
|
+
if (b.y + b.h > maxY) maxY = b.y + b.h;
|
|
779
|
+
}
|
|
780
|
+
if (!found) return null;
|
|
781
|
+
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// src/canvas/keyboard-actions.ts
|
|
785
|
+
var KeyboardActions = class {
|
|
786
|
+
constructor(deps) {
|
|
787
|
+
this.deps = deps;
|
|
788
|
+
}
|
|
789
|
+
clipboard = [];
|
|
790
|
+
pasteCount = 0;
|
|
791
|
+
nudgeTimer = null;
|
|
792
|
+
nudgeTxId = null;
|
|
793
|
+
dispose() {
|
|
794
|
+
this.flushPendingNudge();
|
|
795
|
+
}
|
|
796
|
+
selectTool() {
|
|
797
|
+
const tm = this.deps.getToolManager();
|
|
798
|
+
const ctx = this.deps.getToolContext();
|
|
799
|
+
if (!tm || !ctx) return null;
|
|
800
|
+
const tool = tm.activeTool;
|
|
801
|
+
if (tool?.name !== "select") return null;
|
|
802
|
+
return { tool, ctx };
|
|
803
|
+
}
|
|
804
|
+
nudge(dx, dy, byCell) {
|
|
805
|
+
if (this.deps.isToolActive()) return false;
|
|
806
|
+
const sel = this.selectTool();
|
|
807
|
+
if (!sel) return false;
|
|
808
|
+
if (sel.tool.selectedIds.length === 0) return false;
|
|
809
|
+
const step = byCell ? sel.ctx.gridSize ?? 10 : 1;
|
|
810
|
+
if (this.nudgeTimer === null) {
|
|
811
|
+
const recorder = this.deps.getHistoryRecorder();
|
|
812
|
+
recorder?.begin();
|
|
813
|
+
this.nudgeTxId = recorder?.currentTransactionId ?? null;
|
|
814
|
+
} else {
|
|
815
|
+
clearTimeout(this.nudgeTimer);
|
|
816
|
+
}
|
|
817
|
+
const moved = sel.tool.nudgeSelection(dx * step, dy * step, sel.ctx);
|
|
818
|
+
this.nudgeTimer = setTimeout(() => this.flushPendingNudge(), 400);
|
|
819
|
+
return moved;
|
|
820
|
+
}
|
|
821
|
+
flushPendingNudge() {
|
|
822
|
+
if (this.nudgeTimer === null) return;
|
|
823
|
+
clearTimeout(this.nudgeTimer);
|
|
824
|
+
this.nudgeTimer = null;
|
|
825
|
+
const recorder = this.deps.getHistoryRecorder();
|
|
826
|
+
if (this.nudgeTxId === null || recorder?.currentTransactionId === this.nudgeTxId) {
|
|
827
|
+
recorder?.commit();
|
|
828
|
+
}
|
|
829
|
+
this.nudgeTxId = null;
|
|
830
|
+
}
|
|
831
|
+
deleteSelected() {
|
|
832
|
+
if (this.deps.isToolActive()) return;
|
|
833
|
+
this.flushPendingNudge();
|
|
834
|
+
const sel = this.selectTool();
|
|
835
|
+
if (!sel) return;
|
|
836
|
+
const ids = sel.tool.selectedIds;
|
|
837
|
+
if (ids.length === 0) return;
|
|
838
|
+
const recorder = this.deps.getHistoryRecorder();
|
|
839
|
+
recorder?.begin();
|
|
882
840
|
for (const id of ids) {
|
|
883
841
|
sel.ctx.store.remove(id);
|
|
884
842
|
}
|
|
@@ -928,8 +886,18 @@ var KeyboardActions = class {
|
|
|
928
886
|
if (this.clipboard.length === 0) return;
|
|
929
887
|
const sel = this.selectTool();
|
|
930
888
|
if (!sel) return;
|
|
889
|
+
const cursor = this.deps.getLastPointerWorld?.() ?? null;
|
|
890
|
+
if (cursor) {
|
|
891
|
+
const bbox = getElementsBoundingBox(this.clipboard);
|
|
892
|
+
if (bbox) {
|
|
893
|
+
const centerX = bbox.x + bbox.w / 2;
|
|
894
|
+
const centerY = bbox.y + bbox.h / 2;
|
|
895
|
+
this.insertClones(this.clipboard, { x: cursor.x - centerX, y: cursor.y - centerY }, sel);
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
931
899
|
this.pasteCount++;
|
|
932
|
-
this.insertClones(this.clipboard, this.pasteCount * 20, sel);
|
|
900
|
+
this.insertClones(this.clipboard, { x: this.pasteCount * 20, y: this.pasteCount * 20 }, sel);
|
|
933
901
|
}
|
|
934
902
|
duplicate() {
|
|
935
903
|
if (this.deps.isToolActive()) return;
|
|
@@ -942,7 +910,7 @@ var KeyboardActions = class {
|
|
|
942
910
|
if (el) source.push(el);
|
|
943
911
|
}
|
|
944
912
|
if (source.length === 0) return;
|
|
945
|
-
this.insertClones(source, 20, sel);
|
|
913
|
+
this.insertClones(source, { x: 20, y: 20 }, sel);
|
|
946
914
|
}
|
|
947
915
|
deselect() {
|
|
948
916
|
if (this.deps.isToolActive()) return;
|
|
@@ -1013,11 +981,11 @@ var KeyboardActions = class {
|
|
|
1013
981
|
const newId = idMap.get(el.id);
|
|
1014
982
|
if (!newId) continue;
|
|
1015
983
|
clone.id = newId;
|
|
1016
|
-
clone.position = { x: clone.position.x + offset, y: clone.position.y + offset };
|
|
984
|
+
clone.position = { x: clone.position.x + offset.x, y: clone.position.y + offset.y };
|
|
1017
985
|
if (clone.type === "arrow") {
|
|
1018
986
|
const arrow = clone;
|
|
1019
|
-
arrow.from = { x: arrow.from.x + offset, y: arrow.from.y + offset };
|
|
1020
|
-
arrow.to = { x: arrow.to.x + offset, y: arrow.to.y + offset };
|
|
987
|
+
arrow.from = { x: arrow.from.x + offset.x, y: arrow.from.y + offset.y };
|
|
988
|
+
arrow.to = { x: arrow.to.x + offset.x, y: arrow.to.y + offset.y };
|
|
1021
989
|
delete arrow.cachedControlPoint;
|
|
1022
990
|
if (arrow.fromBinding) {
|
|
1023
991
|
const newTarget = idMap.get(arrow.fromBinding.elementId);
|
|
@@ -1063,6 +1031,9 @@ var DEFAULT_BINDINGS = [
|
|
|
1063
1031
|
["z-front", ["mod+]"]],
|
|
1064
1032
|
["z-back", ["mod+["]],
|
|
1065
1033
|
["zoom-fit", ["shift+1"]],
|
|
1034
|
+
["zoom-in", ["mod+="]],
|
|
1035
|
+
["zoom-out", ["mod+-"]],
|
|
1036
|
+
["zoom-reset", ["mod+0"]],
|
|
1066
1037
|
["nudge-left", ["arrowleft"]],
|
|
1067
1038
|
["nudge-right", ["arrowright"]],
|
|
1068
1039
|
["nudge-up", ["arrowup"]],
|
|
@@ -1199,6 +1170,7 @@ var ShortcutMap = class {
|
|
|
1199
1170
|
|
|
1200
1171
|
// src/canvas/input-handler.ts
|
|
1201
1172
|
var ZOOM_SENSITIVITY = 1e-3;
|
|
1173
|
+
var ZOOM_STEP = 1.2;
|
|
1202
1174
|
var MIDDLE_BUTTON = 1;
|
|
1203
1175
|
var NUDGE_DELTAS = {
|
|
1204
1176
|
"nudge-left": [-1, 0],
|
|
@@ -1220,7 +1192,8 @@ var InputHandler = class {
|
|
|
1220
1192
|
getHistoryRecorder: () => this.historyRecorder,
|
|
1221
1193
|
getHistoryStack: () => this.historyStack,
|
|
1222
1194
|
isToolActive: () => this.isToolActive,
|
|
1223
|
-
fitToContent: options.fitToContent
|
|
1195
|
+
fitToContent: options.fitToContent,
|
|
1196
|
+
getLastPointerWorld: () => this.lastPointerWorld()
|
|
1224
1197
|
});
|
|
1225
1198
|
this.shortcutMap = new ShortcutMap(options.shortcuts?.bindings);
|
|
1226
1199
|
this.scope = options.shortcuts?.scope ?? "focus";
|
|
@@ -1276,11 +1249,21 @@ var InputHandler = class {
|
|
|
1276
1249
|
this.element.addEventListener("pointerdown", this.onPointerDown, opts);
|
|
1277
1250
|
this.element.addEventListener("pointermove", this.onPointerMove, opts);
|
|
1278
1251
|
this.element.addEventListener("pointerup", this.onPointerUp, opts);
|
|
1279
|
-
this.element.addEventListener("pointerleave", this.
|
|
1252
|
+
this.element.addEventListener("pointerleave", this.onPointerLeave, opts);
|
|
1280
1253
|
this.element.addEventListener("pointercancel", this.onPointerUp, opts);
|
|
1281
1254
|
window.addEventListener("keydown", this.onKeyDown, opts);
|
|
1282
1255
|
window.addEventListener("keyup", this.onKeyUp, opts);
|
|
1283
1256
|
}
|
|
1257
|
+
viewportCenter() {
|
|
1258
|
+
const rect = this.element.getBoundingClientRect();
|
|
1259
|
+
return { x: rect.width / 2, y: rect.height / 2 };
|
|
1260
|
+
}
|
|
1261
|
+
zoomByFactor(factor) {
|
|
1262
|
+
this.camera.zoomAt(this.camera.zoom * factor, this.viewportCenter());
|
|
1263
|
+
}
|
|
1264
|
+
zoomToLevel(level) {
|
|
1265
|
+
this.camera.zoomAt(level, this.viewportCenter());
|
|
1266
|
+
}
|
|
1284
1267
|
onWheel = (e) => {
|
|
1285
1268
|
e.preventDefault();
|
|
1286
1269
|
const rect = this.element.getBoundingClientRect();
|
|
@@ -1449,6 +1432,18 @@ var InputHandler = class {
|
|
|
1449
1432
|
e.preventDefault();
|
|
1450
1433
|
this.actions.zoomToFit();
|
|
1451
1434
|
return;
|
|
1435
|
+
case "zoom-in":
|
|
1436
|
+
e.preventDefault();
|
|
1437
|
+
this.zoomByFactor(ZOOM_STEP);
|
|
1438
|
+
return;
|
|
1439
|
+
case "zoom-out":
|
|
1440
|
+
e.preventDefault();
|
|
1441
|
+
this.zoomByFactor(1 / ZOOM_STEP);
|
|
1442
|
+
return;
|
|
1443
|
+
case "zoom-reset":
|
|
1444
|
+
e.preventDefault();
|
|
1445
|
+
this.zoomToLevel(1);
|
|
1446
|
+
return;
|
|
1452
1447
|
case "nudge-left":
|
|
1453
1448
|
case "nudge-right":
|
|
1454
1449
|
case "nudge-up":
|
|
@@ -1506,6 +1501,16 @@ var InputHandler = class {
|
|
|
1506
1501
|
midpoint(a, b) {
|
|
1507
1502
|
return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
|
|
1508
1503
|
}
|
|
1504
|
+
lastPointerWorld() {
|
|
1505
|
+
const e = this.lastPointerEvent;
|
|
1506
|
+
if (!e) return null;
|
|
1507
|
+
const rect = this.element.getBoundingClientRect();
|
|
1508
|
+
return this.camera.screenToWorld({ x: e.clientX - rect.left, y: e.clientY - rect.top });
|
|
1509
|
+
}
|
|
1510
|
+
onPointerLeave = (e) => {
|
|
1511
|
+
this.lastPointerEvent = null;
|
|
1512
|
+
this.onPointerUp(e);
|
|
1513
|
+
};
|
|
1509
1514
|
toPointerState(e) {
|
|
1510
1515
|
const rect = this.element.getBoundingClientRect();
|
|
1511
1516
|
return {
|
|
@@ -1557,299 +1562,319 @@ var InputHandler = class {
|
|
|
1557
1562
|
}
|
|
1558
1563
|
};
|
|
1559
1564
|
|
|
1560
|
-
// src/canvas/
|
|
1561
|
-
var
|
|
1562
|
-
var
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1565
|
+
// src/canvas/background.ts
|
|
1566
|
+
var MIN_PATTERN_SPACING = 16;
|
|
1567
|
+
var DEFAULTS = {
|
|
1568
|
+
pattern: "dots",
|
|
1569
|
+
spacing: 24,
|
|
1570
|
+
color: "#d0d0d0",
|
|
1571
|
+
dotRadius: 1,
|
|
1572
|
+
lineWidth: 0.5
|
|
1573
|
+
};
|
|
1574
|
+
var Background = class {
|
|
1575
|
+
pattern;
|
|
1576
|
+
spacing;
|
|
1577
|
+
color;
|
|
1578
|
+
dotRadius;
|
|
1579
|
+
lineWidth;
|
|
1580
|
+
cachedCanvas = null;
|
|
1581
|
+
cachedCtx = null;
|
|
1582
|
+
lastZoom = -1;
|
|
1583
|
+
lastOffsetX = -Infinity;
|
|
1584
|
+
lastOffsetY = -Infinity;
|
|
1585
|
+
lastWidth = 0;
|
|
1586
|
+
lastHeight = 0;
|
|
1587
|
+
constructor(options = {}) {
|
|
1588
|
+
this.pattern = options.pattern ?? DEFAULTS.pattern;
|
|
1589
|
+
this.spacing = options.spacing ?? DEFAULTS.spacing;
|
|
1590
|
+
this.color = options.color ?? DEFAULTS.color;
|
|
1591
|
+
this.dotRadius = options.dotRadius ?? DEFAULTS.dotRadius;
|
|
1592
|
+
this.lineWidth = options.lineWidth ?? DEFAULTS.lineWidth;
|
|
1573
1593
|
}
|
|
1574
|
-
|
|
1575
|
-
const
|
|
1576
|
-
const
|
|
1577
|
-
const
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1594
|
+
render(ctx, camera) {
|
|
1595
|
+
const { width, height } = ctx.canvas;
|
|
1596
|
+
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
1597
|
+
const cssWidth = width / dpr;
|
|
1598
|
+
const cssHeight = height / dpr;
|
|
1599
|
+
ctx.save();
|
|
1600
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
1601
|
+
ctx.clearRect(0, 0, cssWidth, cssHeight);
|
|
1602
|
+
if (this.pattern === "none") {
|
|
1603
|
+
ctx.restore();
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
const spacing = this.adaptSpacing(this.spacing, camera.zoom);
|
|
1607
|
+
const keyZoom = camera.zoom;
|
|
1608
|
+
const keyX = Math.floor(camera.position.x % spacing);
|
|
1609
|
+
const keyY = Math.floor(camera.position.y % spacing);
|
|
1610
|
+
if (this.cachedCanvas !== null && keyZoom === this.lastZoom && keyX === this.lastOffsetX && keyY === this.lastOffsetY && cssWidth === this.lastWidth && cssHeight === this.lastHeight) {
|
|
1611
|
+
ctx.drawImage(this.cachedCanvas, 0, 0);
|
|
1612
|
+
ctx.restore();
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1615
|
+
this.ensureCachedCanvas(cssWidth, cssHeight, dpr);
|
|
1616
|
+
if (this.cachedCtx === null) {
|
|
1617
|
+
if (this.pattern === "dots") {
|
|
1618
|
+
this.renderDots(ctx, camera, cssWidth, cssHeight);
|
|
1619
|
+
} else if (this.pattern === "grid") {
|
|
1620
|
+
this.renderGrid(ctx, camera, cssWidth, cssHeight);
|
|
1586
1621
|
}
|
|
1622
|
+
ctx.restore();
|
|
1623
|
+
return;
|
|
1587
1624
|
}
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
this.
|
|
1591
|
-
|
|
1592
|
-
|
|
1625
|
+
const offCtx = this.cachedCtx;
|
|
1626
|
+
offCtx.clearRect(0, 0, cssWidth, cssHeight);
|
|
1627
|
+
if (this.pattern === "dots") {
|
|
1628
|
+
this.renderDots(offCtx, camera, cssWidth, cssHeight);
|
|
1629
|
+
} else if (this.pattern === "grid") {
|
|
1630
|
+
this.renderGrid(offCtx, camera, cssWidth, cssHeight);
|
|
1631
|
+
}
|
|
1632
|
+
this.lastZoom = keyZoom;
|
|
1633
|
+
this.lastOffsetX = keyX;
|
|
1634
|
+
this.lastOffsetY = keyY;
|
|
1635
|
+
this.lastWidth = cssWidth;
|
|
1636
|
+
this.lastHeight = cssHeight;
|
|
1637
|
+
ctx.drawImage(this.cachedCanvas, 0, 0);
|
|
1638
|
+
ctx.restore();
|
|
1593
1639
|
}
|
|
1594
|
-
|
|
1595
|
-
this.
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1640
|
+
ensureCachedCanvas(cssWidth, cssHeight, dpr) {
|
|
1641
|
+
if (this.cachedCanvas !== null && this.lastWidth === cssWidth && this.lastHeight === cssHeight) {
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
const physWidth = Math.round(cssWidth * dpr);
|
|
1645
|
+
const physHeight = Math.round(cssHeight * dpr);
|
|
1646
|
+
if (typeof OffscreenCanvas !== "undefined") {
|
|
1647
|
+
this.cachedCanvas = new OffscreenCanvas(physWidth, physHeight);
|
|
1648
|
+
} else if (typeof document !== "undefined") {
|
|
1649
|
+
const el = document.createElement("canvas");
|
|
1650
|
+
el.width = physWidth;
|
|
1651
|
+
el.height = physHeight;
|
|
1652
|
+
this.cachedCanvas = el;
|
|
1653
|
+
} else {
|
|
1654
|
+
this.cachedCanvas = null;
|
|
1655
|
+
this.cachedCtx = null;
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
const offCtx = this.cachedCanvas.getContext("2d");
|
|
1659
|
+
if (offCtx !== null) {
|
|
1660
|
+
offCtx.scale(dpr, dpr);
|
|
1661
|
+
}
|
|
1662
|
+
this.cachedCtx = offCtx;
|
|
1663
|
+
this.lastZoom = -1;
|
|
1664
|
+
}
|
|
1665
|
+
adaptSpacing(baseSpacing, zoom) {
|
|
1666
|
+
let spacing = baseSpacing * zoom;
|
|
1667
|
+
while (spacing < MIN_PATTERN_SPACING) {
|
|
1668
|
+
spacing *= 2;
|
|
1669
|
+
}
|
|
1670
|
+
return spacing;
|
|
1671
|
+
}
|
|
1672
|
+
renderDots(ctx, camera, width, height) {
|
|
1673
|
+
const spacing = this.adaptSpacing(this.spacing, camera.zoom);
|
|
1674
|
+
const offsetX = camera.position.x % spacing;
|
|
1675
|
+
const offsetY = camera.position.y % spacing;
|
|
1676
|
+
const radius = this.dotRadius * Math.min(camera.zoom, 2);
|
|
1677
|
+
ctx.fillStyle = this.color;
|
|
1678
|
+
ctx.beginPath();
|
|
1679
|
+
for (let x = offsetX; x < width; x += spacing) {
|
|
1680
|
+
for (let y = offsetY; y < height; y += spacing) {
|
|
1681
|
+
ctx.moveTo(x + radius, y);
|
|
1682
|
+
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
ctx.fill();
|
|
1686
|
+
}
|
|
1687
|
+
renderGrid(ctx, camera, width, height) {
|
|
1688
|
+
const spacing = this.adaptSpacing(this.spacing, camera.zoom);
|
|
1689
|
+
const offsetX = camera.position.x % spacing;
|
|
1690
|
+
const offsetY = camera.position.y % spacing;
|
|
1691
|
+
const lineW = this.lineWidth * Math.min(camera.zoom, 2);
|
|
1692
|
+
ctx.fillStyle = this.color;
|
|
1693
|
+
for (let x = offsetX; x < width; x += spacing) {
|
|
1694
|
+
ctx.fillRect(x, 0, lineW, height);
|
|
1695
|
+
}
|
|
1696
|
+
for (let y = offsetY; y < height; y += spacing) {
|
|
1697
|
+
ctx.fillRect(0, y, width, lineW);
|
|
1698
|
+
}
|
|
1599
1699
|
}
|
|
1600
1700
|
};
|
|
1601
1701
|
|
|
1602
|
-
// src/core/
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1702
|
+
// src/core/event-bus.ts
|
|
1703
|
+
var EventBus = class {
|
|
1704
|
+
listeners = /* @__PURE__ */ new Map();
|
|
1705
|
+
on(event, listener) {
|
|
1706
|
+
const existing = this.listeners.get(event);
|
|
1707
|
+
if (existing) {
|
|
1708
|
+
existing.add(listener);
|
|
1709
|
+
} else {
|
|
1710
|
+
const set = /* @__PURE__ */ new Set([listener]);
|
|
1711
|
+
this.listeners.set(event, set);
|
|
1712
|
+
}
|
|
1713
|
+
return () => this.off(event, listener);
|
|
1611
1714
|
}
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1715
|
+
off(event, listener) {
|
|
1716
|
+
this.listeners.get(event)?.delete(listener);
|
|
1717
|
+
}
|
|
1718
|
+
emit(event, data) {
|
|
1719
|
+
this.listeners.get(event)?.forEach((listener) => {
|
|
1720
|
+
try {
|
|
1721
|
+
listener(data);
|
|
1722
|
+
} catch (err) {
|
|
1723
|
+
console.error(`[fieldnotes] listener error for "${String(event)}"`, err);
|
|
1724
|
+
}
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
clear() {
|
|
1728
|
+
this.listeners.clear();
|
|
1729
|
+
}
|
|
1730
|
+
};
|
|
1617
1731
|
|
|
1618
|
-
// src/
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
const dx = to.x - from.x;
|
|
1624
|
-
const dy = to.y - from.y;
|
|
1625
|
-
const len = Math.sqrt(dx * dx + dy * dy);
|
|
1626
|
-
if (len === 0) return { x: midX, y: midY };
|
|
1627
|
-
const perpX = -dy / len;
|
|
1628
|
-
const perpY = dx / len;
|
|
1629
|
-
return {
|
|
1630
|
-
x: midX + perpX * bend,
|
|
1631
|
-
y: midY + perpY * bend
|
|
1632
|
-
};
|
|
1633
|
-
}
|
|
1634
|
-
function getArrowMidpoint(from, to, bend) {
|
|
1635
|
-
const cp = getArrowControlPoint(from, to, bend);
|
|
1636
|
-
return {
|
|
1637
|
-
x: 0.25 * from.x + 0.5 * cp.x + 0.25 * to.x,
|
|
1638
|
-
y: 0.25 * from.y + 0.5 * cp.y + 0.25 * to.y
|
|
1639
|
-
};
|
|
1640
|
-
}
|
|
1641
|
-
function getBendFromPoint(from, to, dragPoint) {
|
|
1642
|
-
const midX = (from.x + to.x) / 2;
|
|
1643
|
-
const midY = (from.y + to.y) / 2;
|
|
1644
|
-
const dx = to.x - from.x;
|
|
1645
|
-
const dy = to.y - from.y;
|
|
1646
|
-
const len = Math.sqrt(dx * dx + dy * dy);
|
|
1647
|
-
if (len === 0) return 0;
|
|
1648
|
-
const perpX = -dy / len;
|
|
1649
|
-
const perpY = dx / len;
|
|
1650
|
-
return (dragPoint.x - midX) * perpX + (dragPoint.y - midY) * perpY;
|
|
1651
|
-
}
|
|
1652
|
-
function getArrowTangentAngle(from, to, bend, t) {
|
|
1653
|
-
const cp = getArrowControlPoint(from, to, bend);
|
|
1654
|
-
const tangentX = 2 * (1 - t) * (cp.x - from.x) + 2 * t * (to.x - cp.x);
|
|
1655
|
-
const tangentY = 2 * (1 - t) * (cp.y - from.y) + 2 * t * (to.y - cp.y);
|
|
1656
|
-
return Math.atan2(tangentY, tangentX);
|
|
1732
|
+
// src/core/quadtree.ts
|
|
1733
|
+
var MAX_ITEMS = 8;
|
|
1734
|
+
var MAX_DEPTH = 8;
|
|
1735
|
+
function intersects(a, b) {
|
|
1736
|
+
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;
|
|
1657
1737
|
}
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
for (let i = 0; i < segments; i++) {
|
|
1663
|
-
const t0 = i / segments;
|
|
1664
|
-
const t1 = (i + 1) / segments;
|
|
1665
|
-
const a = bezierPoint(from, cp, to, t0);
|
|
1666
|
-
const b = bezierPoint(from, cp, to, t1);
|
|
1667
|
-
if (isNearLine(point, a, b, threshold)) return true;
|
|
1738
|
+
var QuadNode = class _QuadNode {
|
|
1739
|
+
constructor(bounds, depth) {
|
|
1740
|
+
this.bounds = bounds;
|
|
1741
|
+
this.depth = depth;
|
|
1668
1742
|
}
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1743
|
+
items = [];
|
|
1744
|
+
children = null;
|
|
1745
|
+
insert(entry) {
|
|
1746
|
+
if (this.children) {
|
|
1747
|
+
const idx = this.getChildIndex(entry.bounds);
|
|
1748
|
+
if (idx !== -1) {
|
|
1749
|
+
const child = this.children[idx];
|
|
1750
|
+
if (child) child.insert(entry);
|
|
1751
|
+
return;
|
|
1752
|
+
}
|
|
1753
|
+
this.items.push(entry);
|
|
1754
|
+
return;
|
|
1755
|
+
}
|
|
1756
|
+
this.items.push(entry);
|
|
1757
|
+
if (this.items.length > MAX_ITEMS && this.depth < MAX_DEPTH) {
|
|
1758
|
+
this.split();
|
|
1759
|
+
}
|
|
1681
1760
|
}
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1761
|
+
remove(id) {
|
|
1762
|
+
const idx = this.items.findIndex((e) => e.id === id);
|
|
1763
|
+
if (idx !== -1) {
|
|
1764
|
+
this.items.splice(idx, 1);
|
|
1765
|
+
return true;
|
|
1766
|
+
}
|
|
1767
|
+
if (this.children) {
|
|
1768
|
+
for (const child of this.children) {
|
|
1769
|
+
if (child.remove(id)) {
|
|
1770
|
+
this.collapseIfEmpty();
|
|
1771
|
+
return true;
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
return false;
|
|
1695
1776
|
}
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
|
-
// src/elements/element-bounds.ts
|
|
1710
|
-
var strokeBoundsCache = /* @__PURE__ */ new WeakMap();
|
|
1711
|
-
function getElementBounds(element) {
|
|
1712
|
-
if (element.type === "grid") return null;
|
|
1713
|
-
if ("size" in element) {
|
|
1714
|
-
return {
|
|
1715
|
-
x: element.position.x,
|
|
1716
|
-
y: element.position.y,
|
|
1717
|
-
w: element.size.w,
|
|
1718
|
-
h: element.size.h
|
|
1719
|
-
};
|
|
1777
|
+
query(rect, result) {
|
|
1778
|
+
if (!intersects(this.bounds, rect)) return;
|
|
1779
|
+
for (const item of this.items) {
|
|
1780
|
+
if (intersects(item.bounds, rect)) {
|
|
1781
|
+
result.push(item.id);
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
if (this.children) {
|
|
1785
|
+
for (const child of this.children) {
|
|
1786
|
+
child.query(rect, result);
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1720
1789
|
}
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
const
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1790
|
+
getChildIndex(itemBounds) {
|
|
1791
|
+
const midX = this.bounds.x + this.bounds.w / 2;
|
|
1792
|
+
const midY = this.bounds.y + this.bounds.h / 2;
|
|
1793
|
+
const left = itemBounds.x >= this.bounds.x && itemBounds.x + itemBounds.w <= midX;
|
|
1794
|
+
const right = itemBounds.x >= midX && itemBounds.x + itemBounds.w <= this.bounds.x + this.bounds.w;
|
|
1795
|
+
const top = itemBounds.y >= this.bounds.y && itemBounds.y + itemBounds.h <= midY;
|
|
1796
|
+
const bottom = itemBounds.y >= midY && itemBounds.y + itemBounds.h <= this.bounds.y + this.bounds.h;
|
|
1797
|
+
if (left && top) return 0;
|
|
1798
|
+
if (right && top) return 1;
|
|
1799
|
+
if (left && bottom) return 2;
|
|
1800
|
+
if (right && bottom) return 3;
|
|
1801
|
+
return -1;
|
|
1802
|
+
}
|
|
1803
|
+
split() {
|
|
1804
|
+
const { x, y, w, h } = this.bounds;
|
|
1805
|
+
const halfW = w / 2;
|
|
1806
|
+
const halfH = h / 2;
|
|
1807
|
+
const d = this.depth + 1;
|
|
1808
|
+
this.children = [
|
|
1809
|
+
new _QuadNode({ x, y, w: halfW, h: halfH }, d),
|
|
1810
|
+
new _QuadNode({ x: x + halfW, y, w: halfW, h: halfH }, d),
|
|
1811
|
+
new _QuadNode({ x, y: y + halfH, w: halfW, h: halfH }, d),
|
|
1812
|
+
new _QuadNode({ x: x + halfW, y: y + halfH, w: halfW, h: halfH }, d)
|
|
1813
|
+
];
|
|
1814
|
+
const remaining = [];
|
|
1815
|
+
for (const item of this.items) {
|
|
1816
|
+
const idx = this.getChildIndex(item.bounds);
|
|
1817
|
+
if (idx !== -1) {
|
|
1818
|
+
const target = this.children[idx];
|
|
1819
|
+
if (target) target.insert(item);
|
|
1820
|
+
} else {
|
|
1821
|
+
remaining.push(item);
|
|
1822
|
+
}
|
|
1736
1823
|
}
|
|
1737
|
-
|
|
1738
|
-
strokeBoundsCache.set(element, bounds);
|
|
1739
|
-
return bounds;
|
|
1824
|
+
this.items = remaining;
|
|
1740
1825
|
}
|
|
1741
|
-
|
|
1742
|
-
|
|
1826
|
+
collapseIfEmpty() {
|
|
1827
|
+
if (!this.children) return;
|
|
1828
|
+
let totalItems = this.items.length;
|
|
1829
|
+
for (const child of this.children) {
|
|
1830
|
+
if (child.children) return;
|
|
1831
|
+
totalItems += child.items.length;
|
|
1832
|
+
}
|
|
1833
|
+
if (totalItems <= MAX_ITEMS) {
|
|
1834
|
+
for (const child of this.children) {
|
|
1835
|
+
this.items.push(...child.items);
|
|
1836
|
+
}
|
|
1837
|
+
this.children = null;
|
|
1838
|
+
}
|
|
1743
1839
|
}
|
|
1744
|
-
|
|
1745
|
-
|
|
1840
|
+
};
|
|
1841
|
+
var Quadtree = class {
|
|
1842
|
+
root;
|
|
1843
|
+
_size = 0;
|
|
1844
|
+
worldBounds;
|
|
1845
|
+
constructor(worldBounds) {
|
|
1846
|
+
this.worldBounds = worldBounds;
|
|
1847
|
+
this.root = new QuadNode(worldBounds, 0);
|
|
1746
1848
|
}
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
function getArrowBoundsAnalytical(from, to, bend) {
|
|
1750
|
-
if (bend === 0) {
|
|
1751
|
-
const minX2 = Math.min(from.x, to.x);
|
|
1752
|
-
const minY2 = Math.min(from.y, to.y);
|
|
1753
|
-
return {
|
|
1754
|
-
x: minX2,
|
|
1755
|
-
y: minY2,
|
|
1756
|
-
w: Math.abs(to.x - from.x),
|
|
1757
|
-
h: Math.abs(to.y - from.y)
|
|
1758
|
-
};
|
|
1849
|
+
get size() {
|
|
1850
|
+
return this._size;
|
|
1759
1851
|
}
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
let minY = Math.min(from.y, to.y);
|
|
1764
|
-
let maxY = Math.max(from.y, to.y);
|
|
1765
|
-
const tx = from.x - 2 * cp.x + to.x;
|
|
1766
|
-
if (tx !== 0) {
|
|
1767
|
-
const t = (from.x - cp.x) / tx;
|
|
1768
|
-
if (t > 0 && t < 1) {
|
|
1769
|
-
const mt = 1 - t;
|
|
1770
|
-
const x = mt * mt * from.x + 2 * mt * t * cp.x + t * t * to.x;
|
|
1771
|
-
if (x < minX) minX = x;
|
|
1772
|
-
if (x > maxX) maxX = x;
|
|
1773
|
-
}
|
|
1852
|
+
insert(id, bounds) {
|
|
1853
|
+
this.root.insert({ id, bounds });
|
|
1854
|
+
this._size++;
|
|
1774
1855
|
}
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
if (t > 0 && t < 1) {
|
|
1779
|
-
const mt = 1 - t;
|
|
1780
|
-
const y = mt * mt * from.y + 2 * mt * t * cp.y + t * t * to.y;
|
|
1781
|
-
if (y < minY) minY = y;
|
|
1782
|
-
if (y > maxY) maxY = y;
|
|
1856
|
+
remove(id) {
|
|
1857
|
+
if (this.root.remove(id)) {
|
|
1858
|
+
this._size--;
|
|
1783
1859
|
}
|
|
1784
1860
|
}
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
const { x: cx, y: cy } = el.position;
|
|
1789
|
-
const r = el.radius;
|
|
1790
|
-
switch (el.templateShape) {
|
|
1791
|
-
case "circle":
|
|
1792
|
-
return { x: cx - r, y: cy - r, w: 2 * r, h: 2 * r };
|
|
1793
|
-
case "square":
|
|
1794
|
-
return { x: cx - r / 2, y: cy - r / 2, w: r, h: r };
|
|
1795
|
-
case "cone": {
|
|
1796
|
-
const halfAngle = Math.atan(0.5);
|
|
1797
|
-
const tipX = cx;
|
|
1798
|
-
const tipY = cy;
|
|
1799
|
-
const leftX = cx + r * Math.cos(el.angle - halfAngle);
|
|
1800
|
-
const leftY = cy + r * Math.sin(el.angle - halfAngle);
|
|
1801
|
-
const rightX = cx + r * Math.cos(el.angle + halfAngle);
|
|
1802
|
-
const rightY = cy + r * Math.sin(el.angle + halfAngle);
|
|
1803
|
-
const farX = cx + r * Math.cos(el.angle);
|
|
1804
|
-
const farY = cy + r * Math.sin(el.angle);
|
|
1805
|
-
const xs = [tipX, leftX, rightX, farX];
|
|
1806
|
-
const ys = [tipY, leftY, rightY, farY];
|
|
1807
|
-
let minX = Infinity;
|
|
1808
|
-
let minY = Infinity;
|
|
1809
|
-
let maxX = -Infinity;
|
|
1810
|
-
let maxY = -Infinity;
|
|
1811
|
-
for (let i = 0; i < xs.length; i++) {
|
|
1812
|
-
const px = xs[i];
|
|
1813
|
-
const py = ys[i];
|
|
1814
|
-
if (px !== void 0 && px < minX) minX = px;
|
|
1815
|
-
if (px !== void 0 && px > maxX) maxX = px;
|
|
1816
|
-
if (py !== void 0 && py < minY) minY = py;
|
|
1817
|
-
if (py !== void 0 && py > maxY) maxY = py;
|
|
1818
|
-
}
|
|
1819
|
-
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
1820
|
-
}
|
|
1821
|
-
case "line": {
|
|
1822
|
-
const halfW = r / 12;
|
|
1823
|
-
const cos = Math.cos(el.angle);
|
|
1824
|
-
const sin = Math.sin(el.angle);
|
|
1825
|
-
const perpX = -sin * halfW;
|
|
1826
|
-
const perpY = cos * halfW;
|
|
1827
|
-
const x0 = cx + perpX;
|
|
1828
|
-
const y0 = cy + perpY;
|
|
1829
|
-
const x1 = cx + r * cos + perpX;
|
|
1830
|
-
const y1 = cy + r * sin + perpY;
|
|
1831
|
-
const x2 = cx + r * cos - perpX;
|
|
1832
|
-
const y2 = cy + r * sin - perpY;
|
|
1833
|
-
const x3 = cx - perpX;
|
|
1834
|
-
const y3 = cy - perpY;
|
|
1835
|
-
const minX = Math.min(x0, x1, x2, x3);
|
|
1836
|
-
const minY = Math.min(y0, y1, y2, y3);
|
|
1837
|
-
const maxX = Math.max(x0, x1, x2, x3);
|
|
1838
|
-
const maxY = Math.max(y0, y1, y2, y3);
|
|
1839
|
-
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
1840
|
-
}
|
|
1861
|
+
update(id, newBounds) {
|
|
1862
|
+
this.remove(id);
|
|
1863
|
+
this.insert(id, newBounds);
|
|
1841
1864
|
}
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
}
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1865
|
+
query(rect) {
|
|
1866
|
+
const result = [];
|
|
1867
|
+
this.root.query(rect, result);
|
|
1868
|
+
return result;
|
|
1869
|
+
}
|
|
1870
|
+
queryPoint(point) {
|
|
1871
|
+
return this.query({ x: point.x, y: point.y, w: 0, h: 0 });
|
|
1872
|
+
}
|
|
1873
|
+
clear() {
|
|
1874
|
+
this.root = new QuadNode(this.worldBounds, 0);
|
|
1875
|
+
this._size = 0;
|
|
1876
|
+
}
|
|
1877
|
+
};
|
|
1853
1878
|
|
|
1854
1879
|
// src/elements/stroke-smoothing.ts
|
|
1855
1880
|
var MIN_PRESSURE_SCALE = 0.2;
|
|
@@ -2244,51 +2269,6 @@ function updateBoundArrow(arrow, store) {
|
|
|
2244
2269
|
}
|
|
2245
2270
|
return Object.keys(updates).length > 0 ? updates : null;
|
|
2246
2271
|
}
|
|
2247
|
-
function clearStaleBindings(arrow, store) {
|
|
2248
|
-
const updates = {};
|
|
2249
|
-
let hasUpdates = false;
|
|
2250
|
-
if (arrow.fromBinding && !store.getById(arrow.fromBinding.elementId)) {
|
|
2251
|
-
updates.fromBinding = void 0;
|
|
2252
|
-
hasUpdates = true;
|
|
2253
|
-
}
|
|
2254
|
-
if (arrow.toBinding && !store.getById(arrow.toBinding.elementId)) {
|
|
2255
|
-
updates.toBinding = void 0;
|
|
2256
|
-
hasUpdates = true;
|
|
2257
|
-
}
|
|
2258
|
-
return hasUpdates ? updates : null;
|
|
2259
|
-
}
|
|
2260
|
-
function unbindArrow(arrow, store) {
|
|
2261
|
-
const updates = {};
|
|
2262
|
-
if (arrow.fromBinding) {
|
|
2263
|
-
const el = store.getById(arrow.fromBinding.elementId);
|
|
2264
|
-
const bounds = el ? getElementBounds(el) : null;
|
|
2265
|
-
if (bounds) {
|
|
2266
|
-
const angle = getArrowTangentAngle(arrow.from, arrow.to, arrow.bend, 0);
|
|
2267
|
-
const rayTarget = {
|
|
2268
|
-
x: arrow.from.x + Math.cos(angle) * 1e3,
|
|
2269
|
-
y: arrow.from.y + Math.sin(angle) * 1e3
|
|
2270
|
-
};
|
|
2271
|
-
const edge = getEdgeIntersection(bounds, rayTarget);
|
|
2272
|
-
updates.from = edge;
|
|
2273
|
-
updates.position = edge;
|
|
2274
|
-
}
|
|
2275
|
-
updates.fromBinding = void 0;
|
|
2276
|
-
}
|
|
2277
|
-
if (arrow.toBinding) {
|
|
2278
|
-
const el = store.getById(arrow.toBinding.elementId);
|
|
2279
|
-
const bounds = el ? getElementBounds(el) : null;
|
|
2280
|
-
if (bounds) {
|
|
2281
|
-
const angle = getArrowTangentAngle(arrow.from, arrow.to, arrow.bend, 1);
|
|
2282
|
-
const rayTarget = {
|
|
2283
|
-
x: arrow.to.x - Math.cos(angle) * 1e3,
|
|
2284
|
-
y: arrow.to.y - Math.sin(angle) * 1e3
|
|
2285
|
-
};
|
|
2286
|
-
updates.to = getEdgeIntersection(bounds, rayTarget);
|
|
2287
|
-
}
|
|
2288
|
-
updates.toBinding = void 0;
|
|
2289
|
-
}
|
|
2290
|
-
return updates;
|
|
2291
|
-
}
|
|
2292
2272
|
|
|
2293
2273
|
// src/elements/grid-renderer.ts
|
|
2294
2274
|
function getSquareGridLines(bounds, cellSize) {
|
|
@@ -3516,6 +3496,8 @@ var NoteEditor = class {
|
|
|
3516
3496
|
inputHandler = null;
|
|
3517
3497
|
pendingEditId = null;
|
|
3518
3498
|
onStopCallback = null;
|
|
3499
|
+
beginHistory = null;
|
|
3500
|
+
commitHistory = null;
|
|
3519
3501
|
toolbar;
|
|
3520
3502
|
placeholder;
|
|
3521
3503
|
constructor(options) {
|
|
@@ -3531,6 +3513,10 @@ var NoteEditor = class {
|
|
|
3531
3513
|
setOnStop(callback) {
|
|
3532
3514
|
this.onStopCallback = callback;
|
|
3533
3515
|
}
|
|
3516
|
+
setHistoryHooks(begin, commit) {
|
|
3517
|
+
this.beginHistory = begin;
|
|
3518
|
+
this.commitHistory = commit;
|
|
3519
|
+
}
|
|
3534
3520
|
startEditing(node, elementId, store) {
|
|
3535
3521
|
if (this.editingId === elementId) return;
|
|
3536
3522
|
if (this.editingId) {
|
|
@@ -3562,18 +3548,21 @@ var NoteEditor = class {
|
|
|
3562
3548
|
this.editingNode.removeAttribute("data-fn-empty");
|
|
3563
3549
|
const text = sanitizeNoteHtml(this.editingNode.innerHTML);
|
|
3564
3550
|
const current = store.getById(this.editingId);
|
|
3565
|
-
|
|
3566
|
-
store.update(this.editingId, { text });
|
|
3567
|
-
}
|
|
3551
|
+
const textChanged = !!current && (current.type === "note" || current.type === "text") && current.text !== text;
|
|
3568
3552
|
this.editingNode.contentEditable = "false";
|
|
3569
3553
|
Object.assign(this.editingNode.style, {
|
|
3570
3554
|
userSelect: "none",
|
|
3571
3555
|
cursor: "default"
|
|
3572
3556
|
});
|
|
3573
3557
|
this.toolbar?.hide();
|
|
3558
|
+
this.beginHistory?.();
|
|
3559
|
+
if (textChanged) {
|
|
3560
|
+
store.update(this.editingId, { text });
|
|
3561
|
+
}
|
|
3574
3562
|
if (this.editingId && this.onStopCallback) {
|
|
3575
3563
|
this.onStopCallback(this.editingId);
|
|
3576
3564
|
}
|
|
3565
|
+
this.commitHistory?.();
|
|
3577
3566
|
this.editingId = null;
|
|
3578
3567
|
this.editingNode = null;
|
|
3579
3568
|
this.blurHandler = null;
|
|
@@ -3646,26 +3635,6 @@ var NoteEditor = class {
|
|
|
3646
3635
|
}
|
|
3647
3636
|
};
|
|
3648
3637
|
|
|
3649
|
-
// src/elements/bounds.ts
|
|
3650
|
-
function getElementsBoundingBox(elements) {
|
|
3651
|
-
let minX = Infinity;
|
|
3652
|
-
let minY = Infinity;
|
|
3653
|
-
let maxX = -Infinity;
|
|
3654
|
-
let maxY = -Infinity;
|
|
3655
|
-
let found = false;
|
|
3656
|
-
for (const el of elements) {
|
|
3657
|
-
const b = getElementBounds(el);
|
|
3658
|
-
if (!b) continue;
|
|
3659
|
-
found = true;
|
|
3660
|
-
if (b.x < minX) minX = b.x;
|
|
3661
|
-
if (b.y < minY) minY = b.y;
|
|
3662
|
-
if (b.x + b.w > maxX) maxX = b.x + b.w;
|
|
3663
|
-
if (b.y + b.h > maxY) maxY = b.y + b.h;
|
|
3664
|
-
}
|
|
3665
|
-
if (!found) return null;
|
|
3666
|
-
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
3667
|
-
}
|
|
3668
|
-
|
|
3669
3638
|
// src/tools/tool-manager.ts
|
|
3670
3639
|
var ToolManager = class {
|
|
3671
3640
|
tools = /* @__PURE__ */ new Map();
|
|
@@ -4481,6 +4450,48 @@ var InteractMode = class {
|
|
|
4481
4450
|
};
|
|
4482
4451
|
};
|
|
4483
4452
|
|
|
4453
|
+
// src/canvas/double-tap-detector.ts
|
|
4454
|
+
var DEFAULT_TIMEOUT = 300;
|
|
4455
|
+
var DEFAULT_MAX_DISTANCE = 20;
|
|
4456
|
+
var DoubleTapDetector = class {
|
|
4457
|
+
timeout;
|
|
4458
|
+
maxDistance;
|
|
4459
|
+
lastTapTime = 0;
|
|
4460
|
+
lastTapX = 0;
|
|
4461
|
+
lastTapY = 0;
|
|
4462
|
+
hasPendingTap = false;
|
|
4463
|
+
constructor(options) {
|
|
4464
|
+
this.timeout = options?.timeout ?? DEFAULT_TIMEOUT;
|
|
4465
|
+
this.maxDistance = options?.maxDistance ?? DEFAULT_MAX_DISTANCE;
|
|
4466
|
+
}
|
|
4467
|
+
feed(e) {
|
|
4468
|
+
const now = Date.now();
|
|
4469
|
+
const x = e.clientX;
|
|
4470
|
+
const y = e.clientY;
|
|
4471
|
+
if (this.hasPendingTap) {
|
|
4472
|
+
const elapsed = now - this.lastTapTime;
|
|
4473
|
+
const dx = x - this.lastTapX;
|
|
4474
|
+
const dy = y - this.lastTapY;
|
|
4475
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
4476
|
+
if (elapsed <= this.timeout && dist <= this.maxDistance) {
|
|
4477
|
+
this.reset();
|
|
4478
|
+
return true;
|
|
4479
|
+
}
|
|
4480
|
+
}
|
|
4481
|
+
this.lastTapTime = now;
|
|
4482
|
+
this.lastTapX = x;
|
|
4483
|
+
this.lastTapY = y;
|
|
4484
|
+
this.hasPendingTap = true;
|
|
4485
|
+
return false;
|
|
4486
|
+
}
|
|
4487
|
+
reset() {
|
|
4488
|
+
this.hasPendingTap = false;
|
|
4489
|
+
this.lastTapTime = 0;
|
|
4490
|
+
this.lastTapX = 0;
|
|
4491
|
+
this.lastTapY = 0;
|
|
4492
|
+
}
|
|
4493
|
+
};
|
|
4494
|
+
|
|
4484
4495
|
// src/canvas/dom-node-manager.ts
|
|
4485
4496
|
var DomNodeManager = class {
|
|
4486
4497
|
domNodes = /* @__PURE__ */ new Map();
|
|
@@ -5178,6 +5189,10 @@ var Viewport = class {
|
|
|
5178
5189
|
placeholder: options.placeholder
|
|
5179
5190
|
});
|
|
5180
5191
|
this.noteEditor.setOnStop((id) => this.onTextEditStop(id));
|
|
5192
|
+
this.noteEditor.setHistoryHooks(
|
|
5193
|
+
() => this.historyRecorder.begin(),
|
|
5194
|
+
() => this.historyRecorder.commit()
|
|
5195
|
+
);
|
|
5181
5196
|
this.onHtmlElementMount = options.onHtmlElementMount;
|
|
5182
5197
|
this.dropHandler = options.onDrop;
|
|
5183
5198
|
this.history = new HistoryStack();
|
|
@@ -5194,6 +5209,7 @@ var Viewport = class {
|
|
|
5194
5209
|
requestRender: () => this.requestRender(),
|
|
5195
5210
|
switchTool: (name) => this.toolManager.setTool(name, this.toolContext),
|
|
5196
5211
|
editElement: (id) => this.startEditingElement(id),
|
|
5212
|
+
fitNoteHeight: (id) => this.fitNoteHeight(id),
|
|
5197
5213
|
setCursor: (cursor) => {
|
|
5198
5214
|
this.wrapper.style.cursor = cursor;
|
|
5199
5215
|
},
|
|
@@ -5528,31 +5544,38 @@ var Viewport = class {
|
|
|
5528
5544
|
this.noteEditor.startEditing(node, id, this.store);
|
|
5529
5545
|
}
|
|
5530
5546
|
}
|
|
5547
|
+
fitNoteHeight(elementId) {
|
|
5548
|
+
const element = this.store.getById(elementId);
|
|
5549
|
+
if (!element || element.type !== "note") return;
|
|
5550
|
+
if (isNoteContentEmpty(element.text)) return;
|
|
5551
|
+
const node = this.domNodeManager.getNode(elementId);
|
|
5552
|
+
if (!node) return;
|
|
5553
|
+
const measured = node.scrollHeight;
|
|
5554
|
+
if (measured > element.size.h) {
|
|
5555
|
+
this.store.update(elementId, { size: { w: element.size.w, h: measured } });
|
|
5556
|
+
}
|
|
5557
|
+
}
|
|
5531
5558
|
onTextEditStop(elementId) {
|
|
5532
5559
|
const element = this.store.getById(elementId);
|
|
5533
5560
|
if (!element) return;
|
|
5534
5561
|
if (element.type === "note") {
|
|
5535
5562
|
if (isNoteContentEmpty(element.text)) {
|
|
5536
|
-
this.historyRecorder.begin();
|
|
5537
5563
|
this.store.remove(elementId);
|
|
5538
|
-
|
|
5564
|
+
return;
|
|
5539
5565
|
}
|
|
5566
|
+
this.fitNoteHeight(elementId);
|
|
5540
5567
|
return;
|
|
5541
5568
|
}
|
|
5542
5569
|
if (element.type !== "text") return;
|
|
5543
5570
|
if (!element.text || element.text.trim() === "") {
|
|
5544
|
-
this.historyRecorder.begin();
|
|
5545
5571
|
this.store.remove(elementId);
|
|
5546
|
-
this.historyRecorder.commit();
|
|
5547
5572
|
return;
|
|
5548
5573
|
}
|
|
5549
5574
|
const node = this.domNodeManager.getNode(elementId);
|
|
5550
5575
|
if (node && "size" in element) {
|
|
5551
|
-
const
|
|
5552
|
-
if (
|
|
5553
|
-
this.store.update(elementId, {
|
|
5554
|
-
size: { w: element.size.w, h: measuredHeight }
|
|
5555
|
-
});
|
|
5576
|
+
const measured = node.scrollHeight;
|
|
5577
|
+
if (measured !== element.size.h) {
|
|
5578
|
+
this.store.update(elementId, { size: { w: element.size.w, h: measured } });
|
|
5556
5579
|
}
|
|
5557
5580
|
}
|
|
5558
5581
|
}
|
|
@@ -6269,8 +6292,13 @@ var SelectTool = class {
|
|
|
6269
6292
|
}
|
|
6270
6293
|
this.pendingSingleSelectId = null;
|
|
6271
6294
|
this.hasDragged = false;
|
|
6295
|
+
const resizedNoteId = this.mode.type === "resizing" ? this.mode.elementId : null;
|
|
6272
6296
|
this.mode = { type: "idle" };
|
|
6273
6297
|
ctx.setCursor?.("default");
|
|
6298
|
+
if (resizedNoteId !== null) {
|
|
6299
|
+
const el = ctx.store.getById(resizedNoteId);
|
|
6300
|
+
if (el?.type === "note") ctx.fitNoteHeight?.(resizedNoteId);
|
|
6301
|
+
}
|
|
6274
6302
|
}
|
|
6275
6303
|
onHover(state, ctx) {
|
|
6276
6304
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
@@ -7482,52 +7510,32 @@ var TemplateTool = class {
|
|
|
7482
7510
|
};
|
|
7483
7511
|
|
|
7484
7512
|
// src/index.ts
|
|
7485
|
-
var VERSION = "0.
|
|
7513
|
+
var VERSION = "0.26.0";
|
|
7486
7514
|
export {
|
|
7487
|
-
AddElementCommand,
|
|
7488
7515
|
ArrowTool,
|
|
7489
7516
|
AutoSave,
|
|
7490
|
-
Background,
|
|
7491
|
-
BatchCommand,
|
|
7492
7517
|
Camera,
|
|
7493
|
-
CreateLayerCommand,
|
|
7494
|
-
DEFAULT_FONT_SIZE_PRESETS,
|
|
7495
7518
|
DEFAULT_NOTE_FONT_SIZE,
|
|
7496
|
-
DoubleTapDetector,
|
|
7497
|
-
ElementRenderer,
|
|
7498
7519
|
ElementStore,
|
|
7499
7520
|
EraserTool,
|
|
7500
|
-
EventBus,
|
|
7501
7521
|
HandTool,
|
|
7502
|
-
HistoryRecorder,
|
|
7503
7522
|
HistoryStack,
|
|
7504
7523
|
ImageTool,
|
|
7505
|
-
InputFilter,
|
|
7506
|
-
InputHandler,
|
|
7507
7524
|
LayerManager,
|
|
7508
7525
|
MeasureTool,
|
|
7509
|
-
NoteEditor,
|
|
7510
7526
|
NoteTool,
|
|
7511
|
-
NoteToolbar,
|
|
7512
7527
|
PencilTool,
|
|
7513
|
-
Quadtree,
|
|
7514
|
-
RemoveElementCommand,
|
|
7515
|
-
RemoveLayerCommand,
|
|
7516
7528
|
SelectTool,
|
|
7517
7529
|
ShapeTool,
|
|
7518
7530
|
TemplateTool,
|
|
7519
7531
|
TextTool,
|
|
7520
7532
|
ToolManager,
|
|
7521
|
-
UpdateElementCommand,
|
|
7522
|
-
UpdateLayerCommand,
|
|
7523
7533
|
VERSION,
|
|
7524
7534
|
Viewport,
|
|
7525
7535
|
boundsIntersect,
|
|
7526
|
-
clearStaleBindings,
|
|
7527
7536
|
createArrow,
|
|
7528
7537
|
createGrid,
|
|
7529
7538
|
createHtmlElement,
|
|
7530
|
-
createId,
|
|
7531
7539
|
createImage,
|
|
7532
7540
|
createNote,
|
|
7533
7541
|
createShape,
|
|
@@ -7536,29 +7544,20 @@ export {
|
|
|
7536
7544
|
createText,
|
|
7537
7545
|
drawHexPath,
|
|
7538
7546
|
exportImage,
|
|
7539
|
-
exportState,
|
|
7540
|
-
findBindTarget,
|
|
7541
|
-
findBoundArrows,
|
|
7542
7547
|
getActiveFormats,
|
|
7543
7548
|
getArrowBounds,
|
|
7544
7549
|
getArrowControlPoint,
|
|
7545
7550
|
getArrowMidpoint,
|
|
7546
7551
|
getArrowTangentAngle,
|
|
7547
7552
|
getBendFromPoint,
|
|
7548
|
-
getEdgeIntersection,
|
|
7549
7553
|
getElementBounds,
|
|
7550
|
-
getElementCenter,
|
|
7551
7554
|
getElementsBoundingBox,
|
|
7552
7555
|
getHexCellsInCone,
|
|
7553
7556
|
getHexCellsInLine,
|
|
7554
7557
|
getHexCellsInRadius,
|
|
7555
7558
|
getHexCellsInSquare,
|
|
7556
7559
|
getHexDistance,
|
|
7557
|
-
isBindable,
|
|
7558
7560
|
isNearBezier,
|
|
7559
|
-
isNoteContentEmpty,
|
|
7560
|
-
parseState,
|
|
7561
|
-
sanitizeNoteHtml,
|
|
7562
7561
|
setFontSize,
|
|
7563
7562
|
smartSnap,
|
|
7564
7563
|
snapPoint,
|
|
@@ -7566,8 +7565,6 @@ export {
|
|
|
7566
7565
|
toggleBold,
|
|
7567
7566
|
toggleItalic,
|
|
7568
7567
|
toggleStrikethrough,
|
|
7569
|
-
toggleUnderline
|
|
7570
|
-
unbindArrow,
|
|
7571
|
-
updateBoundArrow
|
|
7568
|
+
toggleUnderline
|
|
7572
7569
|
};
|
|
7573
7570
|
//# sourceMappingURL=index.js.map
|