@fieldnotes/core 0.3.0 → 0.4.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/dist/index.cjs +603 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +79 -4
- package/dist/index.d.ts +79 -4
- package/dist/index.js +591 -27
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -40,26 +40,37 @@ __export(index_exports, {
|
|
|
40
40
|
PencilTool: () => PencilTool,
|
|
41
41
|
RemoveElementCommand: () => RemoveElementCommand,
|
|
42
42
|
SelectTool: () => SelectTool,
|
|
43
|
+
ShapeTool: () => ShapeTool,
|
|
43
44
|
TextTool: () => TextTool,
|
|
44
45
|
ToolManager: () => ToolManager,
|
|
45
46
|
UpdateElementCommand: () => UpdateElementCommand,
|
|
46
47
|
VERSION: () => VERSION,
|
|
47
48
|
Viewport: () => Viewport,
|
|
49
|
+
clearStaleBindings: () => clearStaleBindings,
|
|
48
50
|
createArrow: () => createArrow,
|
|
49
51
|
createHtmlElement: () => createHtmlElement,
|
|
50
52
|
createId: () => createId,
|
|
51
53
|
createImage: () => createImage,
|
|
52
54
|
createNote: () => createNote,
|
|
55
|
+
createShape: () => createShape,
|
|
53
56
|
createStroke: () => createStroke,
|
|
54
57
|
createText: () => createText,
|
|
55
58
|
exportState: () => exportState,
|
|
59
|
+
findBindTarget: () => findBindTarget,
|
|
60
|
+
findBoundArrows: () => findBoundArrows,
|
|
56
61
|
getArrowBounds: () => getArrowBounds,
|
|
57
62
|
getArrowControlPoint: () => getArrowControlPoint,
|
|
58
63
|
getArrowMidpoint: () => getArrowMidpoint,
|
|
59
64
|
getArrowTangentAngle: () => getArrowTangentAngle,
|
|
60
65
|
getBendFromPoint: () => getBendFromPoint,
|
|
66
|
+
getEdgeIntersection: () => getEdgeIntersection,
|
|
67
|
+
getElementBounds: () => getElementBounds,
|
|
68
|
+
getElementCenter: () => getElementCenter,
|
|
69
|
+
isBindable: () => isBindable,
|
|
61
70
|
isNearBezier: () => isNearBezier,
|
|
62
|
-
parseState: () => parseState
|
|
71
|
+
parseState: () => parseState,
|
|
72
|
+
unbindArrow: () => unbindArrow,
|
|
73
|
+
updateBoundArrow: () => updateBoundArrow
|
|
63
74
|
});
|
|
64
75
|
module.exports = __toCommonJS(index_exports);
|
|
65
76
|
|
|
@@ -133,8 +144,9 @@ function validateState(data) {
|
|
|
133
144
|
validateElement(el);
|
|
134
145
|
migrateElement(el);
|
|
135
146
|
}
|
|
147
|
+
cleanBindings(obj["elements"]);
|
|
136
148
|
}
|
|
137
|
-
var VALID_TYPES = /* @__PURE__ */ new Set(["stroke", "note", "arrow", "image", "html", "text"]);
|
|
149
|
+
var VALID_TYPES = /* @__PURE__ */ new Set(["stroke", "note", "arrow", "image", "html", "text", "shape"]);
|
|
138
150
|
function validateElement(el) {
|
|
139
151
|
if (!el || typeof el !== "object") {
|
|
140
152
|
throw new Error("Invalid element: expected an object");
|
|
@@ -150,6 +162,20 @@ function validateElement(el) {
|
|
|
150
162
|
throw new Error("Invalid element: missing zIndex");
|
|
151
163
|
}
|
|
152
164
|
}
|
|
165
|
+
function cleanBindings(elements) {
|
|
166
|
+
const ids = new Set(elements.map((el) => el["id"]));
|
|
167
|
+
for (const el of elements) {
|
|
168
|
+
if (el["type"] !== "arrow") continue;
|
|
169
|
+
const fromBinding = el["fromBinding"];
|
|
170
|
+
if (fromBinding && !ids.has(fromBinding["elementId"])) {
|
|
171
|
+
el["fromBinding"] = void 0;
|
|
172
|
+
}
|
|
173
|
+
const toBinding = el["toBinding"];
|
|
174
|
+
if (toBinding && !ids.has(toBinding["elementId"])) {
|
|
175
|
+
el["toBinding"] = void 0;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
153
179
|
function migrateElement(obj) {
|
|
154
180
|
if (obj["type"] === "arrow" && typeof obj["bend"] !== "number") {
|
|
155
181
|
obj["bend"] = 0;
|
|
@@ -161,6 +187,9 @@ function migrateElement(obj) {
|
|
|
161
187
|
}
|
|
162
188
|
}
|
|
163
189
|
}
|
|
190
|
+
if (obj["type"] === "shape" && typeof obj["shape"] !== "string") {
|
|
191
|
+
obj["shape"] = "rectangle";
|
|
192
|
+
}
|
|
164
193
|
}
|
|
165
194
|
|
|
166
195
|
// src/core/auto-save.ts
|
|
@@ -743,6 +772,134 @@ function isNearLine(point, a, b, threshold) {
|
|
|
743
772
|
return Math.hypot(point.x - projX, point.y - projY) <= threshold;
|
|
744
773
|
}
|
|
745
774
|
|
|
775
|
+
// src/elements/arrow-binding.ts
|
|
776
|
+
var BINDABLE_TYPES = /* @__PURE__ */ new Set(["note", "text", "image", "html", "shape"]);
|
|
777
|
+
function isBindable(element) {
|
|
778
|
+
return BINDABLE_TYPES.has(element.type);
|
|
779
|
+
}
|
|
780
|
+
function getElementCenter(element) {
|
|
781
|
+
if (!("size" in element)) {
|
|
782
|
+
throw new Error(`getElementCenter: element type "${element.type}" has no size`);
|
|
783
|
+
}
|
|
784
|
+
return {
|
|
785
|
+
x: element.position.x + element.size.w / 2,
|
|
786
|
+
y: element.position.y + element.size.h / 2
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
function getElementBounds(element) {
|
|
790
|
+
if (!("size" in element)) return null;
|
|
791
|
+
return {
|
|
792
|
+
x: element.position.x,
|
|
793
|
+
y: element.position.y,
|
|
794
|
+
w: element.size.w,
|
|
795
|
+
h: element.size.h
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
function getEdgeIntersection(bounds, outsidePoint) {
|
|
799
|
+
const cx = bounds.x + bounds.w / 2;
|
|
800
|
+
const cy = bounds.y + bounds.h / 2;
|
|
801
|
+
const dx = outsidePoint.x - cx;
|
|
802
|
+
const dy = outsidePoint.y - cy;
|
|
803
|
+
if (dx === 0 && dy === 0) return { x: cx, y: cy };
|
|
804
|
+
const halfW = bounds.w / 2;
|
|
805
|
+
const halfH = bounds.h / 2;
|
|
806
|
+
const scaleX = dx !== 0 ? halfW / Math.abs(dx) : Infinity;
|
|
807
|
+
const scaleY = dy !== 0 ? halfH / Math.abs(dy) : Infinity;
|
|
808
|
+
const scale = Math.min(scaleX, scaleY);
|
|
809
|
+
return {
|
|
810
|
+
x: cx + dx * scale,
|
|
811
|
+
y: cy + dy * scale
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
function findBindTarget(point, store, threshold, excludeId) {
|
|
815
|
+
let closest = null;
|
|
816
|
+
let closestDist = Infinity;
|
|
817
|
+
for (const el of store.getAll()) {
|
|
818
|
+
if (!isBindable(el)) continue;
|
|
819
|
+
if (excludeId && el.id === excludeId) continue;
|
|
820
|
+
const bounds = getElementBounds(el);
|
|
821
|
+
if (!bounds) continue;
|
|
822
|
+
const dist = distToBounds(point, bounds);
|
|
823
|
+
if (dist <= threshold && dist < closestDist) {
|
|
824
|
+
closest = el;
|
|
825
|
+
closestDist = dist;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
return closest;
|
|
829
|
+
}
|
|
830
|
+
function distToBounds(point, bounds) {
|
|
831
|
+
const clampedX = Math.max(bounds.x, Math.min(point.x, bounds.x + bounds.w));
|
|
832
|
+
const clampedY = Math.max(bounds.y, Math.min(point.y, bounds.y + bounds.h));
|
|
833
|
+
return Math.hypot(point.x - clampedX, point.y - clampedY);
|
|
834
|
+
}
|
|
835
|
+
function findBoundArrows(elementId, store) {
|
|
836
|
+
return store.getElementsByType("arrow").filter((a) => a.fromBinding?.elementId === elementId || a.toBinding?.elementId === elementId);
|
|
837
|
+
}
|
|
838
|
+
function updateBoundArrow(arrow, store) {
|
|
839
|
+
if (!arrow.fromBinding && !arrow.toBinding) return null;
|
|
840
|
+
const updates = {};
|
|
841
|
+
if (arrow.fromBinding) {
|
|
842
|
+
const el = store.getById(arrow.fromBinding.elementId);
|
|
843
|
+
if (el) {
|
|
844
|
+
const center = getElementCenter(el);
|
|
845
|
+
updates.from = center;
|
|
846
|
+
updates.position = center;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
if (arrow.toBinding) {
|
|
850
|
+
const el = store.getById(arrow.toBinding.elementId);
|
|
851
|
+
if (el) {
|
|
852
|
+
updates.to = getElementCenter(el);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
return Object.keys(updates).length > 0 ? updates : null;
|
|
856
|
+
}
|
|
857
|
+
function clearStaleBindings(arrow, store) {
|
|
858
|
+
const updates = {};
|
|
859
|
+
let hasUpdates = false;
|
|
860
|
+
if (arrow.fromBinding && !store.getById(arrow.fromBinding.elementId)) {
|
|
861
|
+
updates.fromBinding = void 0;
|
|
862
|
+
hasUpdates = true;
|
|
863
|
+
}
|
|
864
|
+
if (arrow.toBinding && !store.getById(arrow.toBinding.elementId)) {
|
|
865
|
+
updates.toBinding = void 0;
|
|
866
|
+
hasUpdates = true;
|
|
867
|
+
}
|
|
868
|
+
return hasUpdates ? updates : null;
|
|
869
|
+
}
|
|
870
|
+
function unbindArrow(arrow, store) {
|
|
871
|
+
const updates = {};
|
|
872
|
+
if (arrow.fromBinding) {
|
|
873
|
+
const el = store.getById(arrow.fromBinding.elementId);
|
|
874
|
+
const bounds = el ? getElementBounds(el) : null;
|
|
875
|
+
if (bounds) {
|
|
876
|
+
const angle = getArrowTangentAngle(arrow.from, arrow.to, arrow.bend, 0);
|
|
877
|
+
const rayTarget = {
|
|
878
|
+
x: arrow.from.x + Math.cos(angle) * 1e3,
|
|
879
|
+
y: arrow.from.y + Math.sin(angle) * 1e3
|
|
880
|
+
};
|
|
881
|
+
const edge = getEdgeIntersection(bounds, rayTarget);
|
|
882
|
+
updates.from = edge;
|
|
883
|
+
updates.position = edge;
|
|
884
|
+
}
|
|
885
|
+
updates.fromBinding = void 0;
|
|
886
|
+
}
|
|
887
|
+
if (arrow.toBinding) {
|
|
888
|
+
const el = store.getById(arrow.toBinding.elementId);
|
|
889
|
+
const bounds = el ? getElementBounds(el) : null;
|
|
890
|
+
if (bounds) {
|
|
891
|
+
const angle = getArrowTangentAngle(arrow.from, arrow.to, arrow.bend, 1);
|
|
892
|
+
const rayTarget = {
|
|
893
|
+
x: arrow.to.x - Math.cos(angle) * 1e3,
|
|
894
|
+
y: arrow.to.y - Math.sin(angle) * 1e3
|
|
895
|
+
};
|
|
896
|
+
updates.to = getEdgeIntersection(bounds, rayTarget);
|
|
897
|
+
}
|
|
898
|
+
updates.toBinding = void 0;
|
|
899
|
+
}
|
|
900
|
+
return updates;
|
|
901
|
+
}
|
|
902
|
+
|
|
746
903
|
// src/elements/stroke-smoothing.ts
|
|
747
904
|
var MIN_PRESSURE_SCALE = 0.2;
|
|
748
905
|
function pressureToWidth(pressure, baseWidth) {
|
|
@@ -821,6 +978,10 @@ var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "image", "html", "text"
|
|
|
821
978
|
var ARROWHEAD_LENGTH = 12;
|
|
822
979
|
var ARROWHEAD_ANGLE = Math.PI / 6;
|
|
823
980
|
var ElementRenderer = class {
|
|
981
|
+
store = null;
|
|
982
|
+
setStore(store) {
|
|
983
|
+
this.store = store;
|
|
984
|
+
}
|
|
824
985
|
isDomElement(element) {
|
|
825
986
|
return DOM_ELEMENT_TYPES.has(element.type);
|
|
826
987
|
}
|
|
@@ -832,6 +993,9 @@ var ElementRenderer = class {
|
|
|
832
993
|
case "arrow":
|
|
833
994
|
this.renderArrow(ctx, element);
|
|
834
995
|
break;
|
|
996
|
+
case "shape":
|
|
997
|
+
this.renderShape(ctx, element);
|
|
998
|
+
break;
|
|
835
999
|
}
|
|
836
1000
|
}
|
|
837
1001
|
renderStroke(ctx, stroke) {
|
|
@@ -854,38 +1018,119 @@ var ElementRenderer = class {
|
|
|
854
1018
|
ctx.restore();
|
|
855
1019
|
}
|
|
856
1020
|
renderArrow(ctx, arrow) {
|
|
1021
|
+
const { visualFrom, visualTo } = this.getVisualEndpoints(arrow);
|
|
857
1022
|
ctx.save();
|
|
858
1023
|
ctx.strokeStyle = arrow.color;
|
|
859
1024
|
ctx.lineWidth = arrow.width;
|
|
860
1025
|
ctx.lineCap = "round";
|
|
1026
|
+
if (arrow.fromBinding || arrow.toBinding) {
|
|
1027
|
+
ctx.setLineDash([8, 4]);
|
|
1028
|
+
}
|
|
861
1029
|
ctx.beginPath();
|
|
862
|
-
ctx.moveTo(
|
|
1030
|
+
ctx.moveTo(visualFrom.x, visualFrom.y);
|
|
863
1031
|
if (arrow.bend !== 0) {
|
|
864
1032
|
const cp = getArrowControlPoint(arrow.from, arrow.to, arrow.bend);
|
|
865
|
-
ctx.quadraticCurveTo(cp.x, cp.y,
|
|
1033
|
+
ctx.quadraticCurveTo(cp.x, cp.y, visualTo.x, visualTo.y);
|
|
866
1034
|
} else {
|
|
867
|
-
ctx.lineTo(
|
|
1035
|
+
ctx.lineTo(visualTo.x, visualTo.y);
|
|
868
1036
|
}
|
|
869
1037
|
ctx.stroke();
|
|
870
|
-
this.renderArrowhead(ctx, arrow);
|
|
1038
|
+
this.renderArrowhead(ctx, arrow, visualTo);
|
|
871
1039
|
ctx.restore();
|
|
872
1040
|
}
|
|
873
|
-
renderArrowhead(ctx, arrow) {
|
|
1041
|
+
renderArrowhead(ctx, arrow, tip) {
|
|
874
1042
|
const angle = getArrowTangentAngle(arrow.from, arrow.to, arrow.bend, 1);
|
|
875
1043
|
ctx.beginPath();
|
|
876
|
-
ctx.moveTo(
|
|
1044
|
+
ctx.moveTo(tip.x, tip.y);
|
|
877
1045
|
ctx.lineTo(
|
|
878
|
-
|
|
879
|
-
|
|
1046
|
+
tip.x - ARROWHEAD_LENGTH * Math.cos(angle - ARROWHEAD_ANGLE),
|
|
1047
|
+
tip.y - ARROWHEAD_LENGTH * Math.sin(angle - ARROWHEAD_ANGLE)
|
|
880
1048
|
);
|
|
881
1049
|
ctx.lineTo(
|
|
882
|
-
|
|
883
|
-
|
|
1050
|
+
tip.x - ARROWHEAD_LENGTH * Math.cos(angle + ARROWHEAD_ANGLE),
|
|
1051
|
+
tip.y - ARROWHEAD_LENGTH * Math.sin(angle + ARROWHEAD_ANGLE)
|
|
884
1052
|
);
|
|
885
1053
|
ctx.closePath();
|
|
886
1054
|
ctx.fillStyle = arrow.color;
|
|
887
1055
|
ctx.fill();
|
|
888
1056
|
}
|
|
1057
|
+
getVisualEndpoints(arrow) {
|
|
1058
|
+
let visualFrom = arrow.from;
|
|
1059
|
+
let visualTo = arrow.to;
|
|
1060
|
+
if (!this.store) return { visualFrom, visualTo };
|
|
1061
|
+
if (arrow.fromBinding) {
|
|
1062
|
+
const el = this.store.getById(arrow.fromBinding.elementId);
|
|
1063
|
+
if (el) {
|
|
1064
|
+
const bounds = getElementBounds(el);
|
|
1065
|
+
if (bounds) {
|
|
1066
|
+
const tangentAngle = getArrowTangentAngle(arrow.from, arrow.to, arrow.bend, 0);
|
|
1067
|
+
const rayTarget = {
|
|
1068
|
+
x: arrow.from.x + Math.cos(tangentAngle) * 1e3,
|
|
1069
|
+
y: arrow.from.y + Math.sin(tangentAngle) * 1e3
|
|
1070
|
+
};
|
|
1071
|
+
visualFrom = getEdgeIntersection(bounds, rayTarget);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
if (arrow.toBinding) {
|
|
1076
|
+
const el = this.store.getById(arrow.toBinding.elementId);
|
|
1077
|
+
if (el) {
|
|
1078
|
+
const bounds = getElementBounds(el);
|
|
1079
|
+
if (bounds) {
|
|
1080
|
+
const tangentAngle = getArrowTangentAngle(arrow.from, arrow.to, arrow.bend, 1);
|
|
1081
|
+
const rayTarget = {
|
|
1082
|
+
x: arrow.to.x - Math.cos(tangentAngle) * 1e3,
|
|
1083
|
+
y: arrow.to.y - Math.sin(tangentAngle) * 1e3
|
|
1084
|
+
};
|
|
1085
|
+
visualTo = getEdgeIntersection(bounds, rayTarget);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
return { visualFrom, visualTo };
|
|
1090
|
+
}
|
|
1091
|
+
renderShape(ctx, shape) {
|
|
1092
|
+
ctx.save();
|
|
1093
|
+
if (shape.fillColor !== "none") {
|
|
1094
|
+
ctx.fillStyle = shape.fillColor;
|
|
1095
|
+
this.fillShapePath(ctx, shape);
|
|
1096
|
+
}
|
|
1097
|
+
if (shape.strokeWidth > 0) {
|
|
1098
|
+
ctx.strokeStyle = shape.strokeColor;
|
|
1099
|
+
ctx.lineWidth = shape.strokeWidth;
|
|
1100
|
+
this.strokeShapePath(ctx, shape);
|
|
1101
|
+
}
|
|
1102
|
+
ctx.restore();
|
|
1103
|
+
}
|
|
1104
|
+
fillShapePath(ctx, shape) {
|
|
1105
|
+
switch (shape.shape) {
|
|
1106
|
+
case "rectangle":
|
|
1107
|
+
ctx.fillRect(shape.position.x, shape.position.y, shape.size.w, shape.size.h);
|
|
1108
|
+
break;
|
|
1109
|
+
case "ellipse": {
|
|
1110
|
+
const cx = shape.position.x + shape.size.w / 2;
|
|
1111
|
+
const cy = shape.position.y + shape.size.h / 2;
|
|
1112
|
+
ctx.beginPath();
|
|
1113
|
+
ctx.ellipse(cx, cy, shape.size.w / 2, shape.size.h / 2, 0, 0, Math.PI * 2);
|
|
1114
|
+
ctx.fill();
|
|
1115
|
+
break;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
strokeShapePath(ctx, shape) {
|
|
1120
|
+
switch (shape.shape) {
|
|
1121
|
+
case "rectangle":
|
|
1122
|
+
ctx.strokeRect(shape.position.x, shape.position.y, shape.size.w, shape.size.h);
|
|
1123
|
+
break;
|
|
1124
|
+
case "ellipse": {
|
|
1125
|
+
const cx = shape.position.x + shape.size.w / 2;
|
|
1126
|
+
const cy = shape.position.y + shape.size.h / 2;
|
|
1127
|
+
ctx.beginPath();
|
|
1128
|
+
ctx.ellipse(cx, cy, shape.size.w / 2, shape.size.h / 2, 0, 0, Math.PI * 2);
|
|
1129
|
+
ctx.stroke();
|
|
1130
|
+
break;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
889
1134
|
};
|
|
890
1135
|
|
|
891
1136
|
// src/elements/note-editor.ts
|
|
@@ -1256,7 +1501,7 @@ function createNote(input) {
|
|
|
1256
1501
|
};
|
|
1257
1502
|
}
|
|
1258
1503
|
function createArrow(input) {
|
|
1259
|
-
|
|
1504
|
+
const result = {
|
|
1260
1505
|
id: createId("arrow"),
|
|
1261
1506
|
type: "arrow",
|
|
1262
1507
|
position: input.position ?? { x: 0, y: 0 },
|
|
@@ -1268,6 +1513,9 @@ function createArrow(input) {
|
|
|
1268
1513
|
color: input.color ?? "#000000",
|
|
1269
1514
|
width: input.width ?? 2
|
|
1270
1515
|
};
|
|
1516
|
+
if (input.fromBinding) result.fromBinding = input.fromBinding;
|
|
1517
|
+
if (input.toBinding) result.toBinding = input.toBinding;
|
|
1518
|
+
return result;
|
|
1271
1519
|
}
|
|
1272
1520
|
function createImage(input) {
|
|
1273
1521
|
return {
|
|
@@ -1290,6 +1538,20 @@ function createHtmlElement(input) {
|
|
|
1290
1538
|
size: input.size
|
|
1291
1539
|
};
|
|
1292
1540
|
}
|
|
1541
|
+
function createShape(input) {
|
|
1542
|
+
return {
|
|
1543
|
+
id: createId("shape"),
|
|
1544
|
+
type: "shape",
|
|
1545
|
+
position: input.position,
|
|
1546
|
+
zIndex: input.zIndex ?? 0,
|
|
1547
|
+
locked: input.locked ?? false,
|
|
1548
|
+
shape: input.shape ?? "rectangle",
|
|
1549
|
+
size: input.size,
|
|
1550
|
+
strokeColor: input.strokeColor ?? "#000000",
|
|
1551
|
+
strokeWidth: input.strokeWidth ?? 2,
|
|
1552
|
+
fillColor: input.fillColor ?? "none"
|
|
1553
|
+
};
|
|
1554
|
+
}
|
|
1293
1555
|
function createText(input) {
|
|
1294
1556
|
return {
|
|
1295
1557
|
id: createId("text"),
|
|
@@ -1314,6 +1576,7 @@ var Viewport = class {
|
|
|
1314
1576
|
this.store = new ElementStore();
|
|
1315
1577
|
this.toolManager = new ToolManager();
|
|
1316
1578
|
this.renderer = new ElementRenderer();
|
|
1579
|
+
this.renderer.setStore(this.store);
|
|
1317
1580
|
this.noteEditor = new NoteEditor();
|
|
1318
1581
|
this.noteEditor.setOnStop((id) => this.onTextEditStop(id));
|
|
1319
1582
|
this.history = new HistoryStack();
|
|
@@ -1346,7 +1609,10 @@ var Viewport = class {
|
|
|
1346
1609
|
});
|
|
1347
1610
|
this.unsubStore = [
|
|
1348
1611
|
this.store.on("add", () => this.requestRender()),
|
|
1349
|
-
this.store.on("remove", (el) =>
|
|
1612
|
+
this.store.on("remove", (el) => {
|
|
1613
|
+
this.unbindArrowsFrom(el);
|
|
1614
|
+
this.removeDomNode(el.id);
|
|
1615
|
+
}),
|
|
1350
1616
|
this.store.on("update", () => this.requestRender()),
|
|
1351
1617
|
this.store.on("clear", () => this.clearDomNodes())
|
|
1352
1618
|
];
|
|
@@ -1721,6 +1987,40 @@ var Viewport = class {
|
|
|
1721
1987
|
}
|
|
1722
1988
|
}
|
|
1723
1989
|
}
|
|
1990
|
+
unbindArrowsFrom(removedElement) {
|
|
1991
|
+
const boundArrows = findBoundArrows(removedElement.id, this.store);
|
|
1992
|
+
const bounds = getElementBounds(removedElement);
|
|
1993
|
+
for (const arrow of boundArrows) {
|
|
1994
|
+
const updates = {};
|
|
1995
|
+
if (arrow.fromBinding?.elementId === removedElement.id) {
|
|
1996
|
+
updates.fromBinding = void 0;
|
|
1997
|
+
if (bounds) {
|
|
1998
|
+
const angle = getArrowTangentAngle(arrow.from, arrow.to, arrow.bend, 0);
|
|
1999
|
+
const rayTarget = {
|
|
2000
|
+
x: arrow.from.x + Math.cos(angle) * 1e3,
|
|
2001
|
+
y: arrow.from.y + Math.sin(angle) * 1e3
|
|
2002
|
+
};
|
|
2003
|
+
const edge = getEdgeIntersection(bounds, rayTarget);
|
|
2004
|
+
updates.from = edge;
|
|
2005
|
+
updates.position = edge;
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
if (arrow.toBinding?.elementId === removedElement.id) {
|
|
2009
|
+
updates.toBinding = void 0;
|
|
2010
|
+
if (bounds) {
|
|
2011
|
+
const angle = getArrowTangentAngle(arrow.from, arrow.to, arrow.bend, 1);
|
|
2012
|
+
const rayTarget = {
|
|
2013
|
+
x: arrow.to.x - Math.cos(angle) * 1e3,
|
|
2014
|
+
y: arrow.to.y - Math.sin(angle) * 1e3
|
|
2015
|
+
};
|
|
2016
|
+
updates.to = getEdgeIntersection(bounds, rayTarget);
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
if (Object.keys(updates).length > 0) {
|
|
2020
|
+
this.store.update(arrow.id, updates);
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
1724
2024
|
removeDomNode(id) {
|
|
1725
2025
|
this.htmlContent.delete(id);
|
|
1726
2026
|
const node = this.domNodes.get(id);
|
|
@@ -1949,6 +2249,7 @@ var EraserTool = class {
|
|
|
1949
2249
|
};
|
|
1950
2250
|
|
|
1951
2251
|
// src/tools/arrow-handles.ts
|
|
2252
|
+
var BIND_THRESHOLD = 20;
|
|
1952
2253
|
var HANDLE_RADIUS = 5;
|
|
1953
2254
|
var HANDLE_HIT_PADDING = 4;
|
|
1954
2255
|
var ARROW_HANDLE_CURSORS = {
|
|
@@ -1989,18 +2290,44 @@ function hitTestArrowHandles(world, selectedIds, ctx) {
|
|
|
1989
2290
|
function applyArrowHandleDrag(handle, elementId, world, ctx) {
|
|
1990
2291
|
const el = ctx.store.getById(elementId);
|
|
1991
2292
|
if (!el || el.type !== "arrow") return;
|
|
2293
|
+
const threshold = BIND_THRESHOLD / ctx.camera.zoom;
|
|
1992
2294
|
switch (handle) {
|
|
1993
|
-
case "start":
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
2295
|
+
case "start": {
|
|
2296
|
+
const excludeId = el.toBinding?.elementId;
|
|
2297
|
+
const target = findBindTarget(world, ctx.store, threshold, excludeId);
|
|
2298
|
+
if (target) {
|
|
2299
|
+
const center = getElementCenter(target);
|
|
2300
|
+
ctx.store.update(elementId, {
|
|
2301
|
+
from: center,
|
|
2302
|
+
position: center,
|
|
2303
|
+
fromBinding: { elementId: target.id }
|
|
2304
|
+
});
|
|
2305
|
+
} else {
|
|
2306
|
+
ctx.store.update(elementId, {
|
|
2307
|
+
from: { x: world.x, y: world.y },
|
|
2308
|
+
position: { x: world.x, y: world.y },
|
|
2309
|
+
fromBinding: void 0
|
|
2310
|
+
});
|
|
2311
|
+
}
|
|
1998
2312
|
break;
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2313
|
+
}
|
|
2314
|
+
case "end": {
|
|
2315
|
+
const excludeId = el.fromBinding?.elementId;
|
|
2316
|
+
const target = findBindTarget(world, ctx.store, threshold, excludeId);
|
|
2317
|
+
if (target) {
|
|
2318
|
+
const center = getElementCenter(target);
|
|
2319
|
+
ctx.store.update(elementId, {
|
|
2320
|
+
to: center,
|
|
2321
|
+
toBinding: { elementId: target.id }
|
|
2322
|
+
});
|
|
2323
|
+
} else {
|
|
2324
|
+
ctx.store.update(elementId, {
|
|
2325
|
+
to: { x: world.x, y: world.y },
|
|
2326
|
+
toBinding: void 0
|
|
2327
|
+
});
|
|
2328
|
+
}
|
|
2003
2329
|
break;
|
|
2330
|
+
}
|
|
2004
2331
|
case "mid": {
|
|
2005
2332
|
const bend = getBendFromPoint(el.from, el.to, world);
|
|
2006
2333
|
ctx.store.update(elementId, { bend });
|
|
@@ -2009,6 +2336,16 @@ function applyArrowHandleDrag(handle, elementId, world, ctx) {
|
|
|
2009
2336
|
}
|
|
2010
2337
|
ctx.requestRender();
|
|
2011
2338
|
}
|
|
2339
|
+
function getArrowHandleDragTarget(handle, elementId, world, ctx) {
|
|
2340
|
+
if (handle === "mid") return null;
|
|
2341
|
+
const el = ctx.store.getById(elementId);
|
|
2342
|
+
if (!el || el.type !== "arrow") return null;
|
|
2343
|
+
const threshold = BIND_THRESHOLD / ctx.camera.zoom;
|
|
2344
|
+
const excludeId = handle === "start" ? el.toBinding?.elementId : el.fromBinding?.elementId;
|
|
2345
|
+
const target = findBindTarget(world, ctx.store, threshold, excludeId);
|
|
2346
|
+
if (!target) return null;
|
|
2347
|
+
return getElementBounds(target);
|
|
2348
|
+
}
|
|
2012
2349
|
function renderArrowHandles(canvasCtx, arrow, zoom) {
|
|
2013
2350
|
const radius = HANDLE_RADIUS / zoom;
|
|
2014
2351
|
const handles = getArrowHandlePositions(arrow);
|
|
@@ -2119,6 +2456,9 @@ var SelectTool = class {
|
|
|
2119
2456
|
const el = ctx.store.getById(id);
|
|
2120
2457
|
if (!el || el.locked) continue;
|
|
2121
2458
|
if (el.type === "arrow") {
|
|
2459
|
+
if (el.fromBinding || el.toBinding) {
|
|
2460
|
+
continue;
|
|
2461
|
+
}
|
|
2122
2462
|
ctx.store.update(id, {
|
|
2123
2463
|
position: { x: el.position.x + dx, y: el.position.y + dy },
|
|
2124
2464
|
from: { x: el.from.x + dx, y: el.from.y + dy },
|
|
@@ -2130,6 +2470,23 @@ var SelectTool = class {
|
|
|
2130
2470
|
});
|
|
2131
2471
|
}
|
|
2132
2472
|
}
|
|
2473
|
+
const movedNonArrowIds = /* @__PURE__ */ new Set();
|
|
2474
|
+
for (const id of this._selectedIds) {
|
|
2475
|
+
const el = ctx.store.getById(id);
|
|
2476
|
+
if (el && el.type !== "arrow") movedNonArrowIds.add(id);
|
|
2477
|
+
}
|
|
2478
|
+
if (movedNonArrowIds.size > 0) {
|
|
2479
|
+
const updatedArrows = /* @__PURE__ */ new Set();
|
|
2480
|
+
for (const id of movedNonArrowIds) {
|
|
2481
|
+
const boundArrows = findBoundArrows(id, ctx.store);
|
|
2482
|
+
for (const ba of boundArrows) {
|
|
2483
|
+
if (updatedArrows.has(ba.id)) continue;
|
|
2484
|
+
updatedArrows.add(ba.id);
|
|
2485
|
+
const updates = updateBoundArrow(ba, ctx.store);
|
|
2486
|
+
if (updates) ctx.store.update(ba.id, updates);
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2133
2490
|
ctx.requestRender();
|
|
2134
2491
|
return;
|
|
2135
2492
|
}
|
|
@@ -2158,6 +2515,22 @@ var SelectTool = class {
|
|
|
2158
2515
|
renderOverlay(canvasCtx) {
|
|
2159
2516
|
this.renderMarquee(canvasCtx);
|
|
2160
2517
|
this.renderSelectionBoxes(canvasCtx);
|
|
2518
|
+
if (this.mode.type === "arrow-handle" && this.ctx) {
|
|
2519
|
+
const target = getArrowHandleDragTarget(
|
|
2520
|
+
this.mode.handle,
|
|
2521
|
+
this.mode.elementId,
|
|
2522
|
+
this.currentWorld,
|
|
2523
|
+
this.ctx
|
|
2524
|
+
);
|
|
2525
|
+
if (target) {
|
|
2526
|
+
canvasCtx.save();
|
|
2527
|
+
canvasCtx.strokeStyle = "#2196F3";
|
|
2528
|
+
canvasCtx.lineWidth = 2 / this.ctx.camera.zoom;
|
|
2529
|
+
canvasCtx.setLineDash([]);
|
|
2530
|
+
canvasCtx.strokeRect(target.x, target.y, target.w, target.h);
|
|
2531
|
+
canvasCtx.restore();
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2161
2534
|
}
|
|
2162
2535
|
updateHoverCursor(world, ctx) {
|
|
2163
2536
|
const arrowHit = hitTestArrowHandles(world, this._selectedIds, ctx);
|
|
@@ -2216,6 +2589,11 @@ var SelectTool = class {
|
|
|
2216
2589
|
position: { x, y },
|
|
2217
2590
|
size: { w, h }
|
|
2218
2591
|
});
|
|
2592
|
+
const boundArrows = findBoundArrows(this.mode.elementId, ctx.store);
|
|
2593
|
+
for (const ba of boundArrows) {
|
|
2594
|
+
const updates = updateBoundArrow(ba, ctx.store);
|
|
2595
|
+
if (updates) ctx.store.update(ba.id, updates);
|
|
2596
|
+
}
|
|
2219
2597
|
ctx.requestRender();
|
|
2220
2598
|
}
|
|
2221
2599
|
hitTestResizeHandle(world, ctx) {
|
|
@@ -2270,6 +2648,7 @@ var SelectTool = class {
|
|
|
2270
2648
|
if (!el) continue;
|
|
2271
2649
|
if (el.type === "arrow") {
|
|
2272
2650
|
renderArrowHandles(canvasCtx, el, zoom);
|
|
2651
|
+
this.renderBindingHighlights(canvasCtx, el, zoom);
|
|
2273
2652
|
continue;
|
|
2274
2653
|
}
|
|
2275
2654
|
const bounds = this.getElementBounds(el);
|
|
@@ -2299,6 +2678,26 @@ var SelectTool = class {
|
|
|
2299
2678
|
}
|
|
2300
2679
|
canvasCtx.restore();
|
|
2301
2680
|
}
|
|
2681
|
+
renderBindingHighlights(canvasCtx, arrow, zoom) {
|
|
2682
|
+
if (!this.ctx) return;
|
|
2683
|
+
if (!arrow.fromBinding && !arrow.toBinding) return;
|
|
2684
|
+
const pad = SELECTION_PAD / zoom;
|
|
2685
|
+
canvasCtx.save();
|
|
2686
|
+
canvasCtx.strokeStyle = "#2196F3";
|
|
2687
|
+
canvasCtx.lineWidth = 2 / zoom;
|
|
2688
|
+
canvasCtx.setLineDash([]);
|
|
2689
|
+
const drawn = /* @__PURE__ */ new Set();
|
|
2690
|
+
for (const binding of [arrow.fromBinding, arrow.toBinding]) {
|
|
2691
|
+
if (!binding || drawn.has(binding.elementId)) continue;
|
|
2692
|
+
drawn.add(binding.elementId);
|
|
2693
|
+
const target = this.ctx.store.getById(binding.elementId);
|
|
2694
|
+
if (!target) continue;
|
|
2695
|
+
const bounds = getElementBounds(target);
|
|
2696
|
+
if (!bounds) continue;
|
|
2697
|
+
canvasCtx.strokeRect(bounds.x - pad, bounds.y - pad, bounds.w + pad * 2, bounds.h + pad * 2);
|
|
2698
|
+
}
|
|
2699
|
+
canvasCtx.restore();
|
|
2700
|
+
}
|
|
2302
2701
|
getMarqueeRect() {
|
|
2303
2702
|
if (this.mode.type !== "marquee") return null;
|
|
2304
2703
|
const { start } = this.mode;
|
|
@@ -2372,6 +2771,7 @@ var SelectTool = class {
|
|
|
2372
2771
|
};
|
|
2373
2772
|
|
|
2374
2773
|
// src/tools/arrow-tool.ts
|
|
2774
|
+
var BIND_THRESHOLD2 = 20;
|
|
2375
2775
|
var ArrowTool = class {
|
|
2376
2776
|
name = "arrow";
|
|
2377
2777
|
drawing = false;
|
|
@@ -2379,6 +2779,9 @@ var ArrowTool = class {
|
|
|
2379
2779
|
end = { x: 0, y: 0 };
|
|
2380
2780
|
color;
|
|
2381
2781
|
width;
|
|
2782
|
+
fromBinding;
|
|
2783
|
+
fromTarget = null;
|
|
2784
|
+
toTarget = null;
|
|
2382
2785
|
constructor(options = {}) {
|
|
2383
2786
|
this.color = options.color ?? "#000000";
|
|
2384
2787
|
this.width = options.width ?? 2;
|
|
@@ -2389,12 +2792,34 @@ var ArrowTool = class {
|
|
|
2389
2792
|
}
|
|
2390
2793
|
onPointerDown(state, ctx) {
|
|
2391
2794
|
this.drawing = true;
|
|
2392
|
-
|
|
2795
|
+
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
2796
|
+
const threshold = BIND_THRESHOLD2 / ctx.camera.zoom;
|
|
2797
|
+
const target = findBindTarget(world, ctx.store, threshold);
|
|
2798
|
+
if (target) {
|
|
2799
|
+
this.start = getElementCenter(target);
|
|
2800
|
+
this.fromBinding = { elementId: target.id };
|
|
2801
|
+
this.fromTarget = target;
|
|
2802
|
+
} else {
|
|
2803
|
+
this.start = world;
|
|
2804
|
+
this.fromBinding = void 0;
|
|
2805
|
+
this.fromTarget = null;
|
|
2806
|
+
}
|
|
2393
2807
|
this.end = { ...this.start };
|
|
2808
|
+
this.toTarget = null;
|
|
2394
2809
|
}
|
|
2395
2810
|
onPointerMove(state, ctx) {
|
|
2396
2811
|
if (!this.drawing) return;
|
|
2397
|
-
|
|
2812
|
+
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
2813
|
+
const threshold = BIND_THRESHOLD2 / ctx.camera.zoom;
|
|
2814
|
+
const excludeId = this.fromBinding?.elementId;
|
|
2815
|
+
const target = findBindTarget(world, ctx.store, threshold, excludeId);
|
|
2816
|
+
if (target) {
|
|
2817
|
+
this.end = getElementCenter(target);
|
|
2818
|
+
this.toTarget = target;
|
|
2819
|
+
} else {
|
|
2820
|
+
this.end = world;
|
|
2821
|
+
this.toTarget = null;
|
|
2822
|
+
}
|
|
2398
2823
|
ctx.requestRender();
|
|
2399
2824
|
}
|
|
2400
2825
|
onPointerUp(_state, ctx) {
|
|
@@ -2404,16 +2829,40 @@ var ArrowTool = class {
|
|
|
2404
2829
|
const arrow = createArrow({
|
|
2405
2830
|
from: this.start,
|
|
2406
2831
|
to: this.end,
|
|
2832
|
+
position: this.start,
|
|
2407
2833
|
color: this.color,
|
|
2408
|
-
width: this.width
|
|
2834
|
+
width: this.width,
|
|
2835
|
+
fromBinding: this.fromBinding,
|
|
2836
|
+
toBinding: this.toTarget ? { elementId: this.toTarget.id } : void 0
|
|
2409
2837
|
});
|
|
2410
2838
|
ctx.store.add(arrow);
|
|
2839
|
+
this.fromTarget = null;
|
|
2840
|
+
this.toTarget = null;
|
|
2411
2841
|
ctx.requestRender();
|
|
2842
|
+
ctx.switchTool?.("select");
|
|
2412
2843
|
}
|
|
2413
2844
|
renderOverlay(ctx) {
|
|
2414
2845
|
if (!this.drawing) return;
|
|
2415
2846
|
if (this.start.x === this.end.x && this.start.y === this.end.y) return;
|
|
2416
2847
|
ctx.save();
|
|
2848
|
+
if (this.fromTarget) {
|
|
2849
|
+
const bounds = getElementBounds(this.fromTarget);
|
|
2850
|
+
if (bounds) {
|
|
2851
|
+
ctx.strokeStyle = "#2196F3";
|
|
2852
|
+
ctx.lineWidth = 2;
|
|
2853
|
+
ctx.setLineDash([]);
|
|
2854
|
+
ctx.strokeRect(bounds.x, bounds.y, bounds.w, bounds.h);
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
if (this.toTarget) {
|
|
2858
|
+
const bounds = getElementBounds(this.toTarget);
|
|
2859
|
+
if (bounds) {
|
|
2860
|
+
ctx.strokeStyle = "#2196F3";
|
|
2861
|
+
ctx.lineWidth = 2;
|
|
2862
|
+
ctx.setLineDash([]);
|
|
2863
|
+
ctx.strokeRect(bounds.x, bounds.y, bounds.w, bounds.h);
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2417
2866
|
ctx.strokeStyle = this.color;
|
|
2418
2867
|
ctx.lineWidth = this.width;
|
|
2419
2868
|
ctx.lineCap = "round";
|
|
@@ -2544,8 +2993,123 @@ var ImageTool = class {
|
|
|
2544
2993
|
}
|
|
2545
2994
|
};
|
|
2546
2995
|
|
|
2996
|
+
// src/tools/shape-tool.ts
|
|
2997
|
+
var ShapeTool = class {
|
|
2998
|
+
name = "shape";
|
|
2999
|
+
drawing = false;
|
|
3000
|
+
start = { x: 0, y: 0 };
|
|
3001
|
+
end = { x: 0, y: 0 };
|
|
3002
|
+
shiftHeld = false;
|
|
3003
|
+
shape;
|
|
3004
|
+
strokeColor;
|
|
3005
|
+
strokeWidth;
|
|
3006
|
+
fillColor;
|
|
3007
|
+
constructor(options = {}) {
|
|
3008
|
+
this.shape = options.shape ?? "rectangle";
|
|
3009
|
+
this.strokeColor = options.strokeColor ?? "#000000";
|
|
3010
|
+
this.strokeWidth = options.strokeWidth ?? 2;
|
|
3011
|
+
this.fillColor = options.fillColor ?? "none";
|
|
3012
|
+
}
|
|
3013
|
+
setOptions(options) {
|
|
3014
|
+
if (options.shape !== void 0) this.shape = options.shape;
|
|
3015
|
+
if (options.strokeColor !== void 0) this.strokeColor = options.strokeColor;
|
|
3016
|
+
if (options.strokeWidth !== void 0) this.strokeWidth = options.strokeWidth;
|
|
3017
|
+
if (options.fillColor !== void 0) this.fillColor = options.fillColor;
|
|
3018
|
+
}
|
|
3019
|
+
onActivate(_ctx) {
|
|
3020
|
+
if (typeof window !== "undefined") {
|
|
3021
|
+
window.addEventListener("keydown", this.onKeyDown);
|
|
3022
|
+
window.addEventListener("keyup", this.onKeyUp);
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
onDeactivate(_ctx) {
|
|
3026
|
+
this.shiftHeld = false;
|
|
3027
|
+
if (typeof window !== "undefined") {
|
|
3028
|
+
window.removeEventListener("keydown", this.onKeyDown);
|
|
3029
|
+
window.removeEventListener("keyup", this.onKeyUp);
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
onPointerDown(state, ctx) {
|
|
3033
|
+
this.drawing = true;
|
|
3034
|
+
this.start = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
3035
|
+
this.end = { ...this.start };
|
|
3036
|
+
}
|
|
3037
|
+
onPointerMove(state, ctx) {
|
|
3038
|
+
if (!this.drawing) return;
|
|
3039
|
+
this.end = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
3040
|
+
ctx.requestRender();
|
|
3041
|
+
}
|
|
3042
|
+
onPointerUp(_state, ctx) {
|
|
3043
|
+
if (!this.drawing) return;
|
|
3044
|
+
this.drawing = false;
|
|
3045
|
+
const { position, size } = this.computeRect();
|
|
3046
|
+
if (size.w === 0 || size.h === 0) return;
|
|
3047
|
+
const shape = createShape({
|
|
3048
|
+
position,
|
|
3049
|
+
size,
|
|
3050
|
+
shape: this.shape,
|
|
3051
|
+
strokeColor: this.strokeColor,
|
|
3052
|
+
strokeWidth: this.strokeWidth,
|
|
3053
|
+
fillColor: this.fillColor
|
|
3054
|
+
});
|
|
3055
|
+
ctx.store.add(shape);
|
|
3056
|
+
ctx.requestRender();
|
|
3057
|
+
ctx.switchTool?.("select");
|
|
3058
|
+
}
|
|
3059
|
+
renderOverlay(ctx) {
|
|
3060
|
+
if (!this.drawing) return;
|
|
3061
|
+
const { position, size } = this.computeRect();
|
|
3062
|
+
if (size.w === 0 && size.h === 0) return;
|
|
3063
|
+
ctx.save();
|
|
3064
|
+
ctx.globalAlpha = 0.5;
|
|
3065
|
+
ctx.strokeStyle = this.strokeColor;
|
|
3066
|
+
ctx.lineWidth = this.strokeWidth;
|
|
3067
|
+
if (this.fillColor !== "none") {
|
|
3068
|
+
ctx.fillStyle = this.fillColor;
|
|
3069
|
+
}
|
|
3070
|
+
switch (this.shape) {
|
|
3071
|
+
case "rectangle":
|
|
3072
|
+
if (this.fillColor !== "none") {
|
|
3073
|
+
ctx.fillRect(position.x, position.y, size.w, size.h);
|
|
3074
|
+
}
|
|
3075
|
+
ctx.strokeRect(position.x, position.y, size.w, size.h);
|
|
3076
|
+
break;
|
|
3077
|
+
case "ellipse": {
|
|
3078
|
+
const cx = position.x + size.w / 2;
|
|
3079
|
+
const cy = position.y + size.h / 2;
|
|
3080
|
+
ctx.beginPath();
|
|
3081
|
+
ctx.ellipse(cx, cy, size.w / 2, size.h / 2, 0, 0, Math.PI * 2);
|
|
3082
|
+
if (this.fillColor !== "none") ctx.fill();
|
|
3083
|
+
ctx.stroke();
|
|
3084
|
+
break;
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
ctx.restore();
|
|
3088
|
+
}
|
|
3089
|
+
computeRect() {
|
|
3090
|
+
let x = Math.min(this.start.x, this.end.x);
|
|
3091
|
+
let y = Math.min(this.start.y, this.end.y);
|
|
3092
|
+
let w = Math.abs(this.end.x - this.start.x);
|
|
3093
|
+
let h = Math.abs(this.end.y - this.start.y);
|
|
3094
|
+
if (this.shiftHeld) {
|
|
3095
|
+
const side = Math.max(w, h);
|
|
3096
|
+
w = side;
|
|
3097
|
+
h = side;
|
|
3098
|
+
x = this.end.x >= this.start.x ? this.start.x : this.start.x - side;
|
|
3099
|
+
y = this.end.y >= this.start.y ? this.start.y : this.start.y - side;
|
|
3100
|
+
}
|
|
3101
|
+
return { position: { x, y }, size: { w, h } };
|
|
3102
|
+
}
|
|
3103
|
+
onKeyDown = (e) => {
|
|
3104
|
+
if (e.key === "Shift") this.shiftHeld = true;
|
|
3105
|
+
};
|
|
3106
|
+
onKeyUp = (e) => {
|
|
3107
|
+
if (e.key === "Shift") this.shiftHeld = false;
|
|
3108
|
+
};
|
|
3109
|
+
};
|
|
3110
|
+
|
|
2547
3111
|
// src/index.ts
|
|
2548
|
-
var VERSION = "0.
|
|
3112
|
+
var VERSION = "0.4.0";
|
|
2549
3113
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2550
3114
|
0 && (module.exports = {
|
|
2551
3115
|
AddElementCommand,
|
|
@@ -2568,25 +3132,36 @@ var VERSION = "0.3.0";
|
|
|
2568
3132
|
PencilTool,
|
|
2569
3133
|
RemoveElementCommand,
|
|
2570
3134
|
SelectTool,
|
|
3135
|
+
ShapeTool,
|
|
2571
3136
|
TextTool,
|
|
2572
3137
|
ToolManager,
|
|
2573
3138
|
UpdateElementCommand,
|
|
2574
3139
|
VERSION,
|
|
2575
3140
|
Viewport,
|
|
3141
|
+
clearStaleBindings,
|
|
2576
3142
|
createArrow,
|
|
2577
3143
|
createHtmlElement,
|
|
2578
3144
|
createId,
|
|
2579
3145
|
createImage,
|
|
2580
3146
|
createNote,
|
|
3147
|
+
createShape,
|
|
2581
3148
|
createStroke,
|
|
2582
3149
|
createText,
|
|
2583
3150
|
exportState,
|
|
3151
|
+
findBindTarget,
|
|
3152
|
+
findBoundArrows,
|
|
2584
3153
|
getArrowBounds,
|
|
2585
3154
|
getArrowControlPoint,
|
|
2586
3155
|
getArrowMidpoint,
|
|
2587
3156
|
getArrowTangentAngle,
|
|
2588
3157
|
getBendFromPoint,
|
|
3158
|
+
getEdgeIntersection,
|
|
3159
|
+
getElementBounds,
|
|
3160
|
+
getElementCenter,
|
|
3161
|
+
isBindable,
|
|
2589
3162
|
isNearBezier,
|
|
2590
|
-
parseState
|
|
3163
|
+
parseState,
|
|
3164
|
+
unbindArrow,
|
|
3165
|
+
updateBoundArrow
|
|
2591
3166
|
});
|
|
2592
3167
|
//# sourceMappingURL=index.cjs.map
|