@vanduo-oss/vd3-cbun 1.3.2 → 1.4.1
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/CHANGELOG.md +83 -0
- package/README.md +24 -9
- package/SKILL.md +42 -15
- package/dist/charts/core.d.ts +4 -0
- package/dist/charts/index.cjs +197 -10
- package/dist/charts/index.cjs.map +3 -3
- package/dist/charts/index.js +197 -10
- package/dist/charts/index.js.map +3 -3
- package/dist/charts/vd3-charts.css +82 -0
- package/dist/charts/vue.d.ts +4 -0
- package/dist/code-editor/core.d.ts +19 -2
- package/dist/code-editor/highlight.cjs +1242 -0
- package/dist/code-editor/highlight.cjs.map +7 -0
- package/dist/code-editor/highlight.d.ts +6 -0
- package/dist/code-editor/highlight.js +1219 -0
- package/dist/code-editor/highlight.js.map +7 -0
- package/dist/code-editor/index.cjs +694 -70
- package/dist/code-editor/index.cjs.map +4 -4
- package/dist/code-editor/index.d.ts +2 -0
- package/dist/code-editor/index.js +694 -70
- package/dist/code-editor/index.js.map +4 -4
- package/dist/code-editor/vd3-code-editor.css +5 -0
- package/dist/draw/core.d.ts +7 -1
- package/dist/draw/index.cjs +470 -52
- package/dist/draw/index.cjs.map +2 -2
- package/dist/draw/index.js +470 -52
- package/dist/draw/index.js.map +2 -2
- package/dist/draw/vd3-draw.css +45 -0
- package/dist/draw/vue.d.ts +4 -0
- package/dist/hex-grid/core.d.ts +41 -0
- package/dist/hex-grid/index.cjs +308 -13
- package/dist/hex-grid/index.cjs.map +3 -3
- package/dist/hex-grid/index.d.ts +1 -0
- package/dist/hex-grid/index.js +308 -13
- package/dist/hex-grid/index.js.map +3 -3
- package/dist/hex-grid/vue.d.ts +4 -0
- package/dist/index.js +2 -2
- package/dist/index.js.map +2 -2
- package/dist/meta.json +177 -66
- package/package.json +15 -10
package/dist/draw/index.cjs
CHANGED
|
@@ -288,6 +288,37 @@ function simplifyPoints(points, min = 1.2) {
|
|
|
288
288
|
}
|
|
289
289
|
return out;
|
|
290
290
|
}
|
|
291
|
+
function smoothPoint(prev, next, factor = 0.5) {
|
|
292
|
+
const f = clamp(factor, 0.1, 1);
|
|
293
|
+
const x = round(prev[0] + (next[0] - prev[0]) * f);
|
|
294
|
+
const y = round(prev[1] + (next[1] - prev[1]) * f);
|
|
295
|
+
const hasPressure = prev.length >= 3 && prev[2] != null || next.length >= 3 && next[2] != null;
|
|
296
|
+
if (hasPressure) {
|
|
297
|
+
const pp = prev.length >= 3 && prev[2] != null ? prev[2] : 0.5;
|
|
298
|
+
const np = next.length >= 3 && next[2] != null ? next[2] : 0.5;
|
|
299
|
+
return [x, y, clamp(pp + (np - pp) * f, 0, 1)];
|
|
300
|
+
}
|
|
301
|
+
return [x, y];
|
|
302
|
+
}
|
|
303
|
+
function appendAndSimplify(points, point, minDist = 1.5, maxLen = 500) {
|
|
304
|
+
if (!points.length) {
|
|
305
|
+
points.push(roundPoint(point));
|
|
306
|
+
return points;
|
|
307
|
+
}
|
|
308
|
+
const last = points[points.length - 1];
|
|
309
|
+
if (Math.hypot(point[0] - last[0], point[1] - last[1]) < minDist) return points;
|
|
310
|
+
points.push(roundPoint(point));
|
|
311
|
+
if (points.length > maxLen) {
|
|
312
|
+
const first = points[0];
|
|
313
|
+
const end = points[points.length - 1];
|
|
314
|
+
const kept = [first];
|
|
315
|
+
for (let i = 2; i < points.length - 1; i += 2) kept.push(points[i]);
|
|
316
|
+
kept.push(end);
|
|
317
|
+
points.length = 0;
|
|
318
|
+
for (const p of kept) points.push(p);
|
|
319
|
+
}
|
|
320
|
+
return points;
|
|
321
|
+
}
|
|
291
322
|
function streamlinePoints(points, streamline) {
|
|
292
323
|
const first = points[0];
|
|
293
324
|
const out = [[first[0], first[1], first[2] == null ? 0.5 : clamp(first[2], 0, 1)]];
|
|
@@ -502,6 +533,55 @@ function normalizeDocument(data) {
|
|
|
502
533
|
shapes
|
|
503
534
|
};
|
|
504
535
|
}
|
|
536
|
+
function wrapText(text, maxWidth, charWidth = 8.8) {
|
|
537
|
+
if (typeof text !== "string" || text.length === 0) return [];
|
|
538
|
+
const rawLines = text.split(/\r?\n/);
|
|
539
|
+
if (!maxWidth || maxWidth <= 0 || !Number.isFinite(maxWidth)) {
|
|
540
|
+
return rawLines;
|
|
541
|
+
}
|
|
542
|
+
const maxChars = Math.max(1, Math.floor(maxWidth / charWidth));
|
|
543
|
+
const result = [];
|
|
544
|
+
for (const rawLine of rawLines) {
|
|
545
|
+
if (rawLine.length <= maxChars) {
|
|
546
|
+
result.push(rawLine);
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
const words = rawLine.split(" ");
|
|
550
|
+
let currentLine = "";
|
|
551
|
+
for (const word of words) {
|
|
552
|
+
if (!currentLine) {
|
|
553
|
+
if (word.length <= maxChars) {
|
|
554
|
+
currentLine = word;
|
|
555
|
+
} else {
|
|
556
|
+
let remaining = word;
|
|
557
|
+
while (remaining.length > maxChars) {
|
|
558
|
+
result.push(remaining.slice(0, maxChars));
|
|
559
|
+
remaining = remaining.slice(maxChars);
|
|
560
|
+
}
|
|
561
|
+
currentLine = remaining;
|
|
562
|
+
}
|
|
563
|
+
} else {
|
|
564
|
+
if (currentLine.length + 1 + word.length <= maxChars) {
|
|
565
|
+
currentLine += " " + word;
|
|
566
|
+
} else {
|
|
567
|
+
result.push(currentLine);
|
|
568
|
+
if (word.length <= maxChars) {
|
|
569
|
+
currentLine = word;
|
|
570
|
+
} else {
|
|
571
|
+
let remaining = word;
|
|
572
|
+
while (remaining.length > maxChars) {
|
|
573
|
+
result.push(remaining.slice(0, maxChars));
|
|
574
|
+
remaining = remaining.slice(maxChars);
|
|
575
|
+
}
|
|
576
|
+
currentLine = remaining;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
if (currentLine) result.push(currentLine);
|
|
582
|
+
}
|
|
583
|
+
return result;
|
|
584
|
+
}
|
|
505
585
|
|
|
506
586
|
// src/draw/core.js
|
|
507
587
|
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
@@ -627,6 +707,15 @@ var VdDraw = class {
|
|
|
627
707
|
this.lastReason = null;
|
|
628
708
|
this.lastTargetKey = null;
|
|
629
709
|
this.listeners = /* @__PURE__ */ new Map();
|
|
710
|
+
this.options = { ...opts };
|
|
711
|
+
this._shapesById = /* @__PURE__ */ new Map();
|
|
712
|
+
this._rebuildShapeIndex();
|
|
713
|
+
this._activePointers = /* @__PURE__ */ new Map();
|
|
714
|
+
this._shapeElements = /* @__PURE__ */ new Map();
|
|
715
|
+
this._dirtyShapes = /* @__PURE__ */ new Set();
|
|
716
|
+
this._allDirty = true;
|
|
717
|
+
this._rafId = null;
|
|
718
|
+
this._pendingFlags = null;
|
|
630
719
|
this._buildShell();
|
|
631
720
|
if (typeof opts.color !== "string") this.style.color = this._resolveInk();
|
|
632
721
|
this._bindEvents();
|
|
@@ -635,6 +724,12 @@ var VdDraw = class {
|
|
|
635
724
|
this._syncStylePanel();
|
|
636
725
|
this._scheduleReady();
|
|
637
726
|
}
|
|
727
|
+
_rebuildShapeIndex() {
|
|
728
|
+
this._shapesById.clear();
|
|
729
|
+
for (const s of this.documentData.shapes) {
|
|
730
|
+
this._shapesById.set(s.id, s);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
638
733
|
_resolveElement(target) {
|
|
639
734
|
if (!target) return null;
|
|
640
735
|
if (typeof target === "string") return hasWindow() ? document.querySelector(target) : null;
|
|
@@ -668,11 +763,13 @@ var VdDraw = class {
|
|
|
668
763
|
}
|
|
669
764
|
this.canvasEl = document.createElement("div");
|
|
670
765
|
this.canvasEl.className = "vd-draw-canvas";
|
|
766
|
+
this.canvasEl.setAttribute("data-tool", this.tool);
|
|
671
767
|
this.canvasEl.tabIndex = 0;
|
|
672
768
|
this.svg = createSvgEl("svg", { class: "vd-draw-svg" });
|
|
673
769
|
this.svg.setAttribute("width", "100%");
|
|
674
770
|
this.svg.setAttribute("height", "100%");
|
|
675
771
|
const defs = createSvgEl("defs");
|
|
772
|
+
this.defsEl = defs;
|
|
676
773
|
const gs = this.gridSize;
|
|
677
774
|
this.gridPattern = createSvgEl("pattern", {
|
|
678
775
|
id: this._svgId("grid"),
|
|
@@ -688,19 +785,7 @@ var VdDraw = class {
|
|
|
688
785
|
});
|
|
689
786
|
this.gridPattern.appendChild(this.gridPatternPath);
|
|
690
787
|
defs.appendChild(this.gridPattern);
|
|
691
|
-
|
|
692
|
-
id: this._svgId("arrow"),
|
|
693
|
-
viewBox: "0 0 10 10",
|
|
694
|
-
refX: 8,
|
|
695
|
-
refY: 5,
|
|
696
|
-
markerWidth: 7,
|
|
697
|
-
markerHeight: 7,
|
|
698
|
-
orient: "auto-start-reverse"
|
|
699
|
-
});
|
|
700
|
-
marker.appendChild(
|
|
701
|
-
createSvgEl("path", { d: "M 0 0 L 10 5 L 0 10 z", fill: "var(--vd-draw-shape-stroke)" })
|
|
702
|
-
);
|
|
703
|
-
defs.appendChild(marker);
|
|
788
|
+
this._getArrowMarkerId("", defs);
|
|
704
789
|
this.svg.appendChild(defs);
|
|
705
790
|
this.world = createSvgEl("g", { class: "vd-draw-world" });
|
|
706
791
|
this.gridLayer = createSvgEl("rect", {
|
|
@@ -991,11 +1076,11 @@ var VdDraw = class {
|
|
|
991
1076
|
this._emitViewportChange("viewport:reset");
|
|
992
1077
|
return this;
|
|
993
1078
|
}
|
|
994
|
-
fitView() {
|
|
1079
|
+
fitView(padding = 40) {
|
|
995
1080
|
const bounds = boundsOfShapes(this.documentData.shapes);
|
|
996
1081
|
const rect = this.canvasEl.getBoundingClientRect();
|
|
997
1082
|
if (!bounds || bounds.w === 0 || bounds.h === 0 || !rect.width || !rect.height) return this;
|
|
998
|
-
const pad = 40;
|
|
1083
|
+
const pad = Number.isFinite(padding) ? Math.max(0, padding) : 40;
|
|
999
1084
|
const scale = clamp(
|
|
1000
1085
|
Math.min((rect.width - pad * 2) / bounds.w, (rect.height - pad * 2) / bounds.h),
|
|
1001
1086
|
MIN_SCALE,
|
|
@@ -1037,10 +1122,63 @@ var VdDraw = class {
|
|
|
1037
1122
|
toggleGrid() {
|
|
1038
1123
|
return this.setGridVisible(!this.showGrid);
|
|
1039
1124
|
}
|
|
1125
|
+
// ── Runtime options ──────────────────────────────────────────────────────
|
|
1126
|
+
setReadonly(readonly) {
|
|
1127
|
+
const next = Boolean(readonly);
|
|
1128
|
+
if (this.readonly === next) return this;
|
|
1129
|
+
this.readonly = next;
|
|
1130
|
+
this.options.readonly = next;
|
|
1131
|
+
if (this.element) {
|
|
1132
|
+
if (next) this.element.setAttribute("data-readonly", "true");
|
|
1133
|
+
else this.element.removeAttribute("data-readonly");
|
|
1134
|
+
}
|
|
1135
|
+
if (next) {
|
|
1136
|
+
this.stopTextEdit({ commit: true });
|
|
1137
|
+
this.deselect();
|
|
1138
|
+
if (this.toolbarEl) this.toolbarEl.style.display = "none";
|
|
1139
|
+
if (this.panelEl) this.panelEl.style.display = "none";
|
|
1140
|
+
} else {
|
|
1141
|
+
if (this.toolbarEl && !this.toolbarEl.children.length) {
|
|
1142
|
+
this._buildToolbar();
|
|
1143
|
+
}
|
|
1144
|
+
if (this.panelEl && !this.panelEl.children.length) {
|
|
1145
|
+
this._buildStylePanel();
|
|
1146
|
+
}
|
|
1147
|
+
if (this.toolbarEl) this.toolbarEl.style.display = "";
|
|
1148
|
+
if (this.panelEl) this.panelEl.style.display = "";
|
|
1149
|
+
}
|
|
1150
|
+
this.render({ scene: false, overlay: true });
|
|
1151
|
+
return this;
|
|
1152
|
+
}
|
|
1153
|
+
setSnap(snap) {
|
|
1154
|
+
this.snap = Boolean(snap);
|
|
1155
|
+
this.options.snap = this.snap;
|
|
1156
|
+
return this;
|
|
1157
|
+
}
|
|
1158
|
+
setHistoryEnabled(enabled) {
|
|
1159
|
+
this.historyEnabled = Boolean(enabled);
|
|
1160
|
+
this.options.history = this.historyEnabled;
|
|
1161
|
+
if (!this.historyEnabled) {
|
|
1162
|
+
this.clearHistory();
|
|
1163
|
+
}
|
|
1164
|
+
return this;
|
|
1165
|
+
}
|
|
1166
|
+
setHistoryLimit(limit) {
|
|
1167
|
+
const num = Number(limit);
|
|
1168
|
+
this.historyLimit = Number.isFinite(num) && num > 0 ? num : 100;
|
|
1169
|
+
this.options.historyLimit = this.historyLimit;
|
|
1170
|
+
if (this.history.length > this.historyLimit + 1) {
|
|
1171
|
+
const drop = this.history.length - (this.historyLimit + 1);
|
|
1172
|
+
this.history.splice(0, drop);
|
|
1173
|
+
this.historyIndex = Math.max(0, this.historyIndex - drop);
|
|
1174
|
+
}
|
|
1175
|
+
return this;
|
|
1176
|
+
}
|
|
1040
1177
|
// ── Tool + current style ─────────────────────────────────────────────────
|
|
1041
1178
|
setTool(tool) {
|
|
1042
1179
|
if (!DRAW_TOOLS.includes(tool)) return this;
|
|
1043
1180
|
this.tool = tool;
|
|
1181
|
+
if (this.canvasEl) this.canvasEl.setAttribute("data-tool", tool);
|
|
1044
1182
|
this._syncToolbar();
|
|
1045
1183
|
return this;
|
|
1046
1184
|
}
|
|
@@ -1074,7 +1212,7 @@ var VdDraw = class {
|
|
|
1074
1212
|
}
|
|
1075
1213
|
// ── Shape CRUD ─────────────────────────────────────────────────────────
|
|
1076
1214
|
getShape(id) {
|
|
1077
|
-
return this.
|
|
1215
|
+
return this._shapesById.get(id) || null;
|
|
1078
1216
|
}
|
|
1079
1217
|
getShapes() {
|
|
1080
1218
|
return this.documentData.shapes.map((s) => deepClone(s));
|
|
@@ -1126,6 +1264,8 @@ var VdDraw = class {
|
|
|
1126
1264
|
shape.text = typeof partial.text === "string" ? partial.text : "";
|
|
1127
1265
|
}
|
|
1128
1266
|
this.documentData.shapes.push(shape);
|
|
1267
|
+
this._shapesById.set(shape.id, shape);
|
|
1268
|
+
this._markDirty(shape.id);
|
|
1129
1269
|
this.render();
|
|
1130
1270
|
this._emitChange("shape:add", { shape: deepClone(shape), shapeId: shape.id });
|
|
1131
1271
|
return shape;
|
|
@@ -1139,6 +1279,8 @@ var VdDraw = class {
|
|
|
1139
1279
|
const shape = this.getShape(id);
|
|
1140
1280
|
if (!shape || !patch) return null;
|
|
1141
1281
|
Object.assign(shape, patch);
|
|
1282
|
+
this._shapesById.set(id, shape);
|
|
1283
|
+
this._markDirty(id);
|
|
1142
1284
|
this.render();
|
|
1143
1285
|
this._emitChange(options.reason || "shape:update", { shapeId: id }, options.reason ? id : null);
|
|
1144
1286
|
return shape;
|
|
@@ -1147,7 +1289,9 @@ var VdDraw = class {
|
|
|
1147
1289
|
const idx = this.documentData.shapes.findIndex((s) => s.id === id);
|
|
1148
1290
|
if (idx === -1) return false;
|
|
1149
1291
|
this.documentData.shapes.splice(idx, 1);
|
|
1292
|
+
this._shapesById.delete(id);
|
|
1150
1293
|
this.selectedIds.delete(id);
|
|
1294
|
+
this._markDirty(id);
|
|
1151
1295
|
this.render();
|
|
1152
1296
|
this._emitChange("shape:delete", { shapeId: id });
|
|
1153
1297
|
return true;
|
|
@@ -1214,11 +1358,13 @@ var VdDraw = class {
|
|
|
1214
1358
|
_replaceShape(next) {
|
|
1215
1359
|
const idx = this.documentData.shapes.findIndex((s) => s.id === next.id);
|
|
1216
1360
|
if (idx !== -1) this.documentData.shapes[idx] = next;
|
|
1361
|
+
this._shapesById.set(next.id, next);
|
|
1217
1362
|
}
|
|
1218
1363
|
nudge(dx, dy) {
|
|
1219
1364
|
const selected = this.getSelectedShapes();
|
|
1220
1365
|
if (!selected.length) return this;
|
|
1221
1366
|
for (const shape of selected) this._replaceShape(translateShape(shape, dx, dy));
|
|
1367
|
+
this._markAllDirty();
|
|
1222
1368
|
this.render();
|
|
1223
1369
|
this._emitChange(
|
|
1224
1370
|
"shape:nudge",
|
|
@@ -1237,6 +1383,7 @@ var VdDraw = class {
|
|
|
1237
1383
|
const scaled = scaleShape(shape, cur.x, cur.y, sx, sy);
|
|
1238
1384
|
this._replaceShape(translateShape(scaled, target.x - cur.x, target.y - cur.y));
|
|
1239
1385
|
}
|
|
1386
|
+
this._markAllDirty();
|
|
1240
1387
|
this.render();
|
|
1241
1388
|
this._emitChange("shape:resize", { shapeIds: [...this.selectedIds] });
|
|
1242
1389
|
return this;
|
|
@@ -1244,8 +1391,10 @@ var VdDraw = class {
|
|
|
1244
1391
|
deleteSelection() {
|
|
1245
1392
|
if (this.selectedIds.size === 0) return false;
|
|
1246
1393
|
const ids = [...this.selectedIds];
|
|
1394
|
+
for (const id of ids) this._shapesById.delete(id);
|
|
1247
1395
|
this.documentData.shapes = this.documentData.shapes.filter((s) => !this.selectedIds.has(s.id));
|
|
1248
1396
|
this.selectedIds.clear();
|
|
1397
|
+
this._markAllDirty();
|
|
1249
1398
|
this.render();
|
|
1250
1399
|
this._emitChange("shape:delete", { shapeIds: ids });
|
|
1251
1400
|
return true;
|
|
@@ -1257,6 +1406,7 @@ var VdDraw = class {
|
|
|
1257
1406
|
for (const shape of selected) {
|
|
1258
1407
|
for (const key of allowed) if (key in patch) shape[key] = patch[key];
|
|
1259
1408
|
}
|
|
1409
|
+
this._markAllDirty();
|
|
1260
1410
|
this.render();
|
|
1261
1411
|
this._emitChange(
|
|
1262
1412
|
"shape:style",
|
|
@@ -1269,6 +1419,7 @@ var VdDraw = class {
|
|
|
1269
1419
|
_reorder(mutator) {
|
|
1270
1420
|
if (this.selectedIds.size === 0) return this;
|
|
1271
1421
|
mutator();
|
|
1422
|
+
this._markAllDirty();
|
|
1272
1423
|
this.render();
|
|
1273
1424
|
this._emitChange("shape:reorder", { shapeIds: [...this.selectedIds] });
|
|
1274
1425
|
return this;
|
|
@@ -1311,6 +1462,7 @@ var VdDraw = class {
|
|
|
1311
1462
|
if (selected.length < 2) return this;
|
|
1312
1463
|
const groupId = createId("grp");
|
|
1313
1464
|
for (const shape of selected) shape.groupId = groupId;
|
|
1465
|
+
this._markAllDirty();
|
|
1314
1466
|
this.render();
|
|
1315
1467
|
this._emitChange("shape:group", { groupId, shapeIds: [...this.selectedIds] });
|
|
1316
1468
|
return this;
|
|
@@ -1325,6 +1477,7 @@ var VdDraw = class {
|
|
|
1325
1477
|
}
|
|
1326
1478
|
}
|
|
1327
1479
|
if (!changed) return this;
|
|
1480
|
+
this._markAllDirty();
|
|
1328
1481
|
this.render();
|
|
1329
1482
|
this._emitChange("shape:ungroup", { shapeIds: [...this.selectedIds] });
|
|
1330
1483
|
return this;
|
|
@@ -1351,9 +1504,11 @@ var VdDraw = class {
|
|
|
1351
1504
|
copy.groupId = groupRemap.get(copy.groupId);
|
|
1352
1505
|
}
|
|
1353
1506
|
this.documentData.shapes.push(copy);
|
|
1507
|
+
this._shapesById.set(copy.id, copy);
|
|
1354
1508
|
newIds.push(copy.id);
|
|
1355
1509
|
}
|
|
1356
1510
|
this.selectedIds = new Set(newIds);
|
|
1511
|
+
this._markAllDirty();
|
|
1357
1512
|
this.render();
|
|
1358
1513
|
this._emitChange("shape:paste", { shapeIds: newIds });
|
|
1359
1514
|
this._emitSelect();
|
|
@@ -1415,6 +1570,30 @@ var VdDraw = class {
|
|
|
1415
1570
|
sticky: read("--vd-draw-sticky-fill", "#fdf3c4")
|
|
1416
1571
|
};
|
|
1417
1572
|
}
|
|
1573
|
+
_getArrowMarkerId(color, defsTarget = this.defsEl) {
|
|
1574
|
+
const isDefault = !color;
|
|
1575
|
+
const safeKey = isDefault ? "arrow" : "arrow-" + color.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
|
|
1576
|
+
const id = this._svgId(safeKey);
|
|
1577
|
+
if (defsTarget && !defsTarget.querySelector(`#${id}`)) {
|
|
1578
|
+
const marker = createSvgEl("marker", {
|
|
1579
|
+
id,
|
|
1580
|
+
viewBox: "0 0 10 10",
|
|
1581
|
+
refX: 8,
|
|
1582
|
+
refY: 5,
|
|
1583
|
+
markerWidth: 7,
|
|
1584
|
+
markerHeight: 7,
|
|
1585
|
+
orient: "auto-start-reverse"
|
|
1586
|
+
});
|
|
1587
|
+
marker.appendChild(
|
|
1588
|
+
createSvgEl("path", {
|
|
1589
|
+
d: "M 0 0 L 10 5 L 0 10 z",
|
|
1590
|
+
fill: color || "var(--vd-draw-shape-stroke)"
|
|
1591
|
+
})
|
|
1592
|
+
);
|
|
1593
|
+
defsTarget.appendChild(marker);
|
|
1594
|
+
}
|
|
1595
|
+
return id;
|
|
1596
|
+
}
|
|
1418
1597
|
toSVG() {
|
|
1419
1598
|
const shapes = this.documentData.shapes;
|
|
1420
1599
|
const bounds = boundsOfShapes(shapes) || { x: 0, y: 0, w: 100, h: 100 };
|
|
@@ -1432,10 +1611,14 @@ var VdDraw = class {
|
|
|
1432
1611
|
height: round(vb.h),
|
|
1433
1612
|
viewBox: `${round(vb.x)} ${round(vb.y)} ${round(vb.w)} ${round(vb.h)}`
|
|
1434
1613
|
});
|
|
1614
|
+
const defs = createSvgEl("defs");
|
|
1435
1615
|
for (const shape of shapes) {
|
|
1436
|
-
const el = this._renderShapeEl(shape, { standalone: true, colors });
|
|
1616
|
+
const el = this._renderShapeEl(shape, { standalone: true, colors, defsTarget: defs });
|
|
1437
1617
|
if (el) svg.appendChild(el);
|
|
1438
1618
|
}
|
|
1619
|
+
if (defs.childNodes.length > 0) {
|
|
1620
|
+
svg.insertBefore(defs, svg.firstChild);
|
|
1621
|
+
}
|
|
1439
1622
|
return new XMLSerializer().serializeToString(svg);
|
|
1440
1623
|
}
|
|
1441
1624
|
toPNG({ scale = 2 } = {}) {
|
|
@@ -1529,7 +1712,9 @@ var VdDraw = class {
|
|
|
1529
1712
|
const viewport = { ...this.documentData.viewport };
|
|
1530
1713
|
this.documentData = deepClone(snapshot);
|
|
1531
1714
|
this.documentData.viewport = viewport;
|
|
1715
|
+
this._rebuildShapeIndex();
|
|
1532
1716
|
this._syncSelectionValidity();
|
|
1717
|
+
this._markAllDirty();
|
|
1533
1718
|
this.render();
|
|
1534
1719
|
this.emit("change", { reason, document: this.toJSON() });
|
|
1535
1720
|
this.emit("history", { reason, canUndo: this.canUndo(), canRedo: this.canRedo() });
|
|
@@ -1545,8 +1730,10 @@ var VdDraw = class {
|
|
|
1545
1730
|
}
|
|
1546
1731
|
load(data) {
|
|
1547
1732
|
this.documentData = normalizeDocument(data);
|
|
1733
|
+
this._rebuildShapeIndex();
|
|
1548
1734
|
this.selectedIds.clear();
|
|
1549
1735
|
this._resetHistory();
|
|
1736
|
+
this._markAllDirty();
|
|
1550
1737
|
this.render();
|
|
1551
1738
|
this._emitChange("load");
|
|
1552
1739
|
this._emitSelect();
|
|
@@ -1555,7 +1742,9 @@ var VdDraw = class {
|
|
|
1555
1742
|
clear() {
|
|
1556
1743
|
if (!this.documentData.shapes.length) return this;
|
|
1557
1744
|
this.documentData.shapes = [];
|
|
1745
|
+
this._shapesById.clear();
|
|
1558
1746
|
this.selectedIds.clear();
|
|
1747
|
+
this._markAllDirty();
|
|
1559
1748
|
this.render();
|
|
1560
1749
|
this._emitChange("clear");
|
|
1561
1750
|
return this;
|
|
@@ -1580,10 +1769,16 @@ var VdDraw = class {
|
|
|
1580
1769
|
}
|
|
1581
1770
|
_positionTextEditor(shape, editor) {
|
|
1582
1771
|
const vp = this.documentData.viewport;
|
|
1583
|
-
|
|
1584
|
-
editor.style.
|
|
1585
|
-
editor.style.
|
|
1586
|
-
editor.style.
|
|
1772
|
+
const scale = vp.scale || 1;
|
|
1773
|
+
editor.style.left = `${vp.x + shape.x * scale}px`;
|
|
1774
|
+
editor.style.top = `${vp.y + shape.y * scale}px`;
|
|
1775
|
+
editor.style.width = `${shape.w * scale}px`;
|
|
1776
|
+
editor.style.height = `${shape.h * scale}px`;
|
|
1777
|
+
editor.style.fontSize = `${16 * scale}px`;
|
|
1778
|
+
editor.style.lineHeight = `${20 * scale}px`;
|
|
1779
|
+
if (shape.type === "sticky") {
|
|
1780
|
+
editor.style.background = shape.fill || "var(--vd-draw-sticky-fill)";
|
|
1781
|
+
}
|
|
1587
1782
|
}
|
|
1588
1783
|
stopTextEdit({ commit = true } = {}) {
|
|
1589
1784
|
if (!this.textEditor) return;
|
|
@@ -1595,6 +1790,7 @@ var VdDraw = class {
|
|
|
1595
1790
|
const shape = this.getShape(id);
|
|
1596
1791
|
if (shape && shape.text !== value) {
|
|
1597
1792
|
shape.text = value;
|
|
1793
|
+
this._markDirty(id);
|
|
1598
1794
|
this.render();
|
|
1599
1795
|
this._emitChange("shape:text", { shapeId: id }, `text:${id}`);
|
|
1600
1796
|
}
|
|
@@ -1652,11 +1848,56 @@ var VdDraw = class {
|
|
|
1652
1848
|
if (this.destroyed || event.button != null && event.button !== 0) return;
|
|
1653
1849
|
this.canvasEl.focus();
|
|
1654
1850
|
this.stopTextEdit({ commit: true });
|
|
1851
|
+
this._activePointers.set(event.pointerId, { clientX: event.clientX, clientY: event.clientY });
|
|
1852
|
+
if (this._activePointers.size === 2) {
|
|
1853
|
+
if (this.interaction) {
|
|
1854
|
+
const it = this.interaction;
|
|
1855
|
+
if (it.kind === "freehand" || it.kind === "create-line" || it.kind === "create-box") {
|
|
1856
|
+
this.documentData.shapes = this.documentData.shapes.filter((s) => s.id !== it.shapeId);
|
|
1857
|
+
this._shapesById.delete(it.shapeId);
|
|
1858
|
+
const el = this._shapeElements.get(it.shapeId);
|
|
1859
|
+
if (el) {
|
|
1860
|
+
el.remove();
|
|
1861
|
+
this._shapeElements.delete(it.shapeId);
|
|
1862
|
+
}
|
|
1863
|
+
this._scheduleRender({ scene: true, overlay: false });
|
|
1864
|
+
} else if (it.kind === "move" || it.kind === "resize") {
|
|
1865
|
+
for (const orig of it.originals) this._replaceShape(orig);
|
|
1866
|
+
this._scheduleRender({ scene: true });
|
|
1867
|
+
} else if (it.kind === "marquee") {
|
|
1868
|
+
clearChildren(this.marqueeLayer);
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
const pts = [...this._activePointers.values()];
|
|
1872
|
+
const p1 = pts[0];
|
|
1873
|
+
const p2 = pts[1];
|
|
1874
|
+
const initialDistance = Math.hypot(p2.clientX - p1.clientX, p2.clientY - p1.clientY) || 1;
|
|
1875
|
+
const initialClientMid = {
|
|
1876
|
+
x: (p1.clientX + p2.clientX) / 2,
|
|
1877
|
+
y: (p1.clientY + p2.clientY) / 2
|
|
1878
|
+
};
|
|
1879
|
+
const initialLocalMid = this._clientToLocal(initialClientMid.x, initialClientMid.y);
|
|
1880
|
+
const initialVp = { ...this.documentData.viewport };
|
|
1881
|
+
const currentScale = initialVp.scale || 1;
|
|
1882
|
+
const worldMid = {
|
|
1883
|
+
x: (initialLocalMid.x - initialVp.x) / currentScale,
|
|
1884
|
+
y: (initialLocalMid.y - initialVp.y) / currentScale
|
|
1885
|
+
};
|
|
1886
|
+
this.interaction = {
|
|
1887
|
+
kind: "pinch",
|
|
1888
|
+
initialDistance,
|
|
1889
|
+
worldMid,
|
|
1890
|
+
initialScale: currentScale
|
|
1891
|
+
};
|
|
1892
|
+
if (typeof event.preventDefault === "function") event.preventDefault();
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1655
1895
|
const world = this._clientToWorld(event.clientX, event.clientY);
|
|
1656
1896
|
const shapeTarget = event.target.closest("[data-shape-id]");
|
|
1657
1897
|
const handleTarget = event.target.closest("[data-handle]");
|
|
1658
1898
|
if (this.tool === "hand") {
|
|
1659
1899
|
this.interaction = this._beginPan(event);
|
|
1900
|
+
if (this.canvasEl) this.canvasEl.classList.add("vd-draw-panning");
|
|
1660
1901
|
this._capture(event.pointerId);
|
|
1661
1902
|
return;
|
|
1662
1903
|
}
|
|
@@ -1667,6 +1908,15 @@ var VdDraw = class {
|
|
|
1667
1908
|
return;
|
|
1668
1909
|
}
|
|
1669
1910
|
if (this.tool === "select") {
|
|
1911
|
+
if (event.detail === 2 && shapeTarget && !this.readonly) {
|
|
1912
|
+
const id = shapeTarget.getAttribute("data-shape-id");
|
|
1913
|
+
const shape = this.getShape(id);
|
|
1914
|
+
if (shape && (shape.type === "text" || shape.type === "sticky")) {
|
|
1915
|
+
this.select(id);
|
|
1916
|
+
this.startTextEdit(id);
|
|
1917
|
+
return;
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1670
1920
|
if (handleTarget && this.selectedIds.size) {
|
|
1671
1921
|
this.interaction = {
|
|
1672
1922
|
kind: "resize",
|
|
@@ -1730,6 +1980,7 @@ var VdDraw = class {
|
|
|
1730
1980
|
points: [[round(world.x), round(world.y), pressure]]
|
|
1731
1981
|
};
|
|
1732
1982
|
this.documentData.shapes.push(shape2);
|
|
1983
|
+
this._shapesById.set(shape2.id, shape2);
|
|
1733
1984
|
return { kind: "freehand", pointerId: event.pointerId, shapeId: shape2.id };
|
|
1734
1985
|
}
|
|
1735
1986
|
if (tool === "line") {
|
|
@@ -1747,6 +1998,7 @@ var VdDraw = class {
|
|
|
1747
1998
|
opacity: this.style.opacity
|
|
1748
1999
|
};
|
|
1749
2000
|
this.documentData.shapes.push(shape2);
|
|
2001
|
+
this._shapesById.set(shape2.id, shape2);
|
|
1750
2002
|
return { kind: "create-line", pointerId: event.pointerId, shapeId: shape2.id };
|
|
1751
2003
|
}
|
|
1752
2004
|
const type = tool;
|
|
@@ -1764,6 +2016,7 @@ var VdDraw = class {
|
|
|
1764
2016
|
};
|
|
1765
2017
|
if (type === "text" || type === "sticky") shape.text = "";
|
|
1766
2018
|
this.documentData.shapes.push(shape);
|
|
2019
|
+
this._shapesById.set(shape.id, shape);
|
|
1767
2020
|
return { kind: "create-box", pointerId: event.pointerId, shapeId: shape.id, start: world };
|
|
1768
2021
|
}
|
|
1769
2022
|
_applyErase(world) {
|
|
@@ -1778,16 +2031,44 @@ var VdDraw = class {
|
|
|
1778
2031
|
changed = true;
|
|
1779
2032
|
}
|
|
1780
2033
|
}
|
|
1781
|
-
if (changed)
|
|
2034
|
+
if (changed) {
|
|
2035
|
+
for (const id of it.erased) this._markDirty(id);
|
|
2036
|
+
this._scheduleRender({ scene: true, overlay: false });
|
|
2037
|
+
}
|
|
1782
2038
|
}
|
|
1783
2039
|
_handlePointerMove(event) {
|
|
2040
|
+
if (this._activePointers.has(event.pointerId)) {
|
|
2041
|
+
this._activePointers.set(event.pointerId, { clientX: event.clientX, clientY: event.clientY });
|
|
2042
|
+
}
|
|
1784
2043
|
const it = this.interaction;
|
|
1785
2044
|
if (!it) return;
|
|
2045
|
+
if (it.kind === "pinch") {
|
|
2046
|
+
if (this._activePointers.size >= 2) {
|
|
2047
|
+
if (typeof event.preventDefault === "function") event.preventDefault();
|
|
2048
|
+
const pts = [...this._activePointers.values()];
|
|
2049
|
+
const p1 = pts[0];
|
|
2050
|
+
const p2 = pts[1];
|
|
2051
|
+
const curDistance = Math.hypot(p2.clientX - p1.clientX, p2.clientY - p1.clientY) || 1;
|
|
2052
|
+
const curClientMid = {
|
|
2053
|
+
x: (p1.clientX + p2.clientX) / 2,
|
|
2054
|
+
y: (p1.clientY + p2.clientY) / 2
|
|
2055
|
+
};
|
|
2056
|
+
const curLocalMid = this._clientToLocal(curClientMid.x, curClientMid.y);
|
|
2057
|
+
const distRatio = curDistance / it.initialDistance;
|
|
2058
|
+
const newScale = clamp(it.initialScale * distRatio, MIN_SCALE, MAX_SCALE);
|
|
2059
|
+
const vp = this.documentData.viewport;
|
|
2060
|
+
vp.scale = newScale;
|
|
2061
|
+
vp.x = curLocalMid.x - it.worldMid.x * newScale;
|
|
2062
|
+
vp.y = curLocalMid.y - it.worldMid.y * newScale;
|
|
2063
|
+
this._scheduleRender({ scene: false });
|
|
2064
|
+
}
|
|
2065
|
+
return;
|
|
2066
|
+
}
|
|
1786
2067
|
if (it.kind === "pan") {
|
|
1787
2068
|
const vp = this.documentData.viewport;
|
|
1788
2069
|
vp.x = it.startX + (event.clientX - it.startClientX);
|
|
1789
2070
|
vp.y = it.startY + (event.clientY - it.startClientY);
|
|
1790
|
-
this.
|
|
2071
|
+
this._scheduleRender({ scene: false });
|
|
1791
2072
|
return;
|
|
1792
2073
|
}
|
|
1793
2074
|
const world = this._clientToWorld(event.clientX, event.clientY);
|
|
@@ -1805,8 +2086,9 @@ var VdDraw = class {
|
|
|
1805
2086
|
if (snap.dx || snap.dy)
|
|
1806
2087
|
for (const orig of it.originals)
|
|
1807
2088
|
this._replaceShape(translateShape(orig, dx + snap.dx, dy + snap.dy));
|
|
2089
|
+
for (const orig of it.originals) this._markDirty(orig.id);
|
|
1808
2090
|
this._renderGuides(snap.guides);
|
|
1809
|
-
this.
|
|
2091
|
+
this._scheduleRender({ scene: true, guides: false });
|
|
1810
2092
|
return;
|
|
1811
2093
|
}
|
|
1812
2094
|
if (it.kind === "resize") {
|
|
@@ -1821,9 +2103,10 @@ var VdDraw = class {
|
|
|
1821
2103
|
for (const orig of it.originals) {
|
|
1822
2104
|
const scaled = scaleShape(orig, it.startBounds.x, it.startBounds.y, sx, sy);
|
|
1823
2105
|
this._replaceShape(translateShape(scaled, t.x - it.startBounds.x, t.y - it.startBounds.y));
|
|
2106
|
+
this._markDirty(orig.id);
|
|
1824
2107
|
}
|
|
1825
2108
|
it.moved = true;
|
|
1826
|
-
this.
|
|
2109
|
+
this._scheduleRender();
|
|
1827
2110
|
return;
|
|
1828
2111
|
}
|
|
1829
2112
|
if (it.kind === "marquee") {
|
|
@@ -1834,8 +2117,14 @@ var VdDraw = class {
|
|
|
1834
2117
|
if (it.kind === "freehand") {
|
|
1835
2118
|
const shape = this.getShape(it.shapeId);
|
|
1836
2119
|
if (shape) {
|
|
1837
|
-
|
|
1838
|
-
|
|
2120
|
+
const raw = [round(world.x), round(world.y), event.pressure || 0.5];
|
|
2121
|
+
const preset = BRUSH_PRESETS[shape.brush] || BRUSH_PRESETS[DEFAULT_BRUSH];
|
|
2122
|
+
const smoothFactor = 1 - (preset.smoothing || 0.5) * 0.6;
|
|
2123
|
+
const prev = shape.points[shape.points.length - 1];
|
|
2124
|
+
const pt = prev ? smoothPoint(prev, raw, smoothFactor) : raw;
|
|
2125
|
+
appendAndSimplify(shape.points, pt);
|
|
2126
|
+
this._markDirty(it.shapeId);
|
|
2127
|
+
this._scheduleRender({ scene: true });
|
|
1839
2128
|
}
|
|
1840
2129
|
return;
|
|
1841
2130
|
}
|
|
@@ -1843,7 +2132,8 @@ var VdDraw = class {
|
|
|
1843
2132
|
const shape = this.getShape(it.shapeId);
|
|
1844
2133
|
if (shape) {
|
|
1845
2134
|
shape.points[1] = [round(world.x), round(world.y)];
|
|
1846
|
-
this.
|
|
2135
|
+
this._markDirty(it.shapeId);
|
|
2136
|
+
this._scheduleRender({ scene: true });
|
|
1847
2137
|
}
|
|
1848
2138
|
return;
|
|
1849
2139
|
}
|
|
@@ -1854,7 +2144,8 @@ var VdDraw = class {
|
|
|
1854
2144
|
shape.y = round(Math.min(it.start.y, world.y));
|
|
1855
2145
|
shape.w = round(Math.max(1, Math.abs(world.x - it.start.x)));
|
|
1856
2146
|
shape.h = round(Math.max(1, Math.abs(world.y - it.start.y)));
|
|
1857
|
-
this.
|
|
2147
|
+
this._markDirty(it.shapeId);
|
|
2148
|
+
this._scheduleRender({ scene: true });
|
|
1858
2149
|
}
|
|
1859
2150
|
}
|
|
1860
2151
|
}
|
|
@@ -1873,9 +2164,18 @@ var VdDraw = class {
|
|
|
1873
2164
|
return { x, y, w, h: h2 };
|
|
1874
2165
|
}
|
|
1875
2166
|
_handlePointerUp(event) {
|
|
2167
|
+
this._activePointers.delete(event.pointerId);
|
|
1876
2168
|
const it = this.interaction;
|
|
1877
2169
|
if (!it) return;
|
|
2170
|
+
if (it.kind === "pinch") {
|
|
2171
|
+
if (this._activePointers.size < 2) {
|
|
2172
|
+
this.interaction = null;
|
|
2173
|
+
this._emitViewportChange("viewport:pinch");
|
|
2174
|
+
}
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
1878
2177
|
this.interaction = null;
|
|
2178
|
+
if (this.canvasEl) this.canvasEl.classList.remove("vd-draw-panning");
|
|
1879
2179
|
if (typeof this.canvasEl.releasePointerCapture === "function" && this.canvasEl.hasPointerCapture?.(event.pointerId)) {
|
|
1880
2180
|
try {
|
|
1881
2181
|
this.canvasEl.releasePointerCapture(event.pointerId);
|
|
@@ -1891,18 +2191,22 @@ var VdDraw = class {
|
|
|
1891
2191
|
if (it.kind === "erase") {
|
|
1892
2192
|
if (it.erased.size) {
|
|
1893
2193
|
const ids = [...it.erased];
|
|
2194
|
+
for (const id of ids) this._shapesById.delete(id);
|
|
1894
2195
|
this.documentData.shapes = this.documentData.shapes.filter((s) => !it.erased.has(s.id));
|
|
2196
|
+
this._markAllDirty();
|
|
1895
2197
|
this.render();
|
|
1896
2198
|
this._emitChange("shape:erase", { shapeIds: ids });
|
|
1897
2199
|
}
|
|
1898
2200
|
return;
|
|
1899
2201
|
}
|
|
1900
2202
|
if (it.kind === "move" && it.moved) {
|
|
2203
|
+
this._markAllDirty();
|
|
1901
2204
|
this.render();
|
|
1902
2205
|
this._emitChange("shape:move", { shapeIds: [...this.selectedIds] });
|
|
1903
2206
|
return;
|
|
1904
2207
|
}
|
|
1905
2208
|
if (it.kind === "resize" && it.moved) {
|
|
2209
|
+
this._markAllDirty();
|
|
1906
2210
|
this.render();
|
|
1907
2211
|
this._emitChange("shape:resize", { shapeIds: [...this.selectedIds] });
|
|
1908
2212
|
return;
|
|
@@ -1919,6 +2223,8 @@ var VdDraw = class {
|
|
|
1919
2223
|
const b = shapeBounds(shape);
|
|
1920
2224
|
if (it.kind !== "freehand" && b.w < 2 && b.h < 2 && shape.type !== "text" && shape.type !== "sticky") {
|
|
1921
2225
|
this.documentData.shapes = this.documentData.shapes.filter((s) => s.id !== it.shapeId);
|
|
2226
|
+
this._shapesById.delete(it.shapeId);
|
|
2227
|
+
this._markAllDirty();
|
|
1922
2228
|
this.render();
|
|
1923
2229
|
return;
|
|
1924
2230
|
}
|
|
@@ -1932,6 +2238,7 @@ var VdDraw = class {
|
|
|
1932
2238
|
this.select(shape.id);
|
|
1933
2239
|
this.setTool("select");
|
|
1934
2240
|
}
|
|
2241
|
+
this._markDirty(shape.id);
|
|
1935
2242
|
this.render();
|
|
1936
2243
|
this._emitChange("shape:add", { shape: deepClone(shape), shapeId: shape.id });
|
|
1937
2244
|
if (shape.type === "text" || shape.type === "sticky") this.startTextEdit(shape.id);
|
|
@@ -2008,26 +2315,91 @@ var VdDraw = class {
|
|
|
2008
2315
|
if (this.destroyed) return;
|
|
2009
2316
|
this.render({ scene: false });
|
|
2010
2317
|
}
|
|
2318
|
+
// ── Dirty tracking ──────────────────────────────────────────────────────
|
|
2319
|
+
_markDirty(shapeId) {
|
|
2320
|
+
this._dirtyShapes.add(shapeId);
|
|
2321
|
+
}
|
|
2322
|
+
_markAllDirty() {
|
|
2323
|
+
this._allDirty = true;
|
|
2324
|
+
}
|
|
2325
|
+
// ── rAF-batched render ─────────────────────────────────────────────────
|
|
2326
|
+
_scheduleRender(flags = {}) {
|
|
2327
|
+
this._pendingFlags = {
|
|
2328
|
+
scene: (this._pendingFlags?.scene ?? false) || flags.scene !== false,
|
|
2329
|
+
overlay: (this._pendingFlags?.overlay ?? false) || flags.overlay !== false
|
|
2330
|
+
};
|
|
2331
|
+
if (this._rafId != null) return;
|
|
2332
|
+
this._rafId = (typeof requestAnimationFrame === "function" ? requestAnimationFrame : setTimeout)(() => {
|
|
2333
|
+
this._rafId = null;
|
|
2334
|
+
const f = this._pendingFlags || {};
|
|
2335
|
+
this._pendingFlags = null;
|
|
2336
|
+
this.render(f);
|
|
2337
|
+
});
|
|
2338
|
+
}
|
|
2011
2339
|
// ── Rendering ────────────────────────────────────────────────────────────
|
|
2012
2340
|
render(flags = {}) {
|
|
2013
2341
|
if (this.destroyed) return;
|
|
2014
2342
|
const { scene = true, overlay = true } = flags;
|
|
2015
2343
|
const vp = this.documentData.viewport;
|
|
2016
2344
|
this.world.setAttribute("transform", `matrix(${vp.scale} 0 0 ${vp.scale} ${vp.x} ${vp.y})`);
|
|
2345
|
+
if (this.textEditor) {
|
|
2346
|
+
const activeShape = this.getShape(this.textEditor.id);
|
|
2347
|
+
if (activeShape) this._positionTextEditor(activeShape, this.textEditor.el);
|
|
2348
|
+
}
|
|
2017
2349
|
if (scene) {
|
|
2018
|
-
|
|
2019
|
-
const erasing = this.interaction && this.interaction.kind === "erase" ? this.interaction.erased : null;
|
|
2020
|
-
for (const shape of this.documentData.shapes) {
|
|
2021
|
-
if (erasing && erasing.has(shape.id)) continue;
|
|
2022
|
-
const el = this._renderShapeEl(shape, {});
|
|
2023
|
-
if (el) this.shapesLayer.appendChild(el);
|
|
2024
|
-
}
|
|
2350
|
+
this._renderSceneIncremental();
|
|
2025
2351
|
}
|
|
2026
2352
|
if (overlay) this._renderOverlay();
|
|
2027
2353
|
}
|
|
2354
|
+
_renderSceneIncremental() {
|
|
2355
|
+
const shapes = this.documentData.shapes;
|
|
2356
|
+
const erasing = this.interaction && this.interaction.kind === "erase" ? this.interaction.erased : null;
|
|
2357
|
+
const currentIds = /* @__PURE__ */ new Set();
|
|
2358
|
+
for (const shape of shapes) {
|
|
2359
|
+
if (erasing && erasing.has(shape.id)) continue;
|
|
2360
|
+
currentIds.add(shape.id);
|
|
2361
|
+
}
|
|
2362
|
+
for (const [id, el] of this._shapeElements) {
|
|
2363
|
+
if (!currentIds.has(id)) {
|
|
2364
|
+
el.remove();
|
|
2365
|
+
this._shapeElements.delete(id);
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
const fullRebuild = this._allDirty;
|
|
2369
|
+
let prevEl = null;
|
|
2370
|
+
for (const shape of shapes) {
|
|
2371
|
+
if (!currentIds.has(shape.id)) continue;
|
|
2372
|
+
const needsUpdate = fullRebuild || this._dirtyShapes.has(shape.id);
|
|
2373
|
+
let el = this._shapeElements.get(shape.id);
|
|
2374
|
+
if (needsUpdate || !el) {
|
|
2375
|
+
const newEl = this._renderShapeEl(shape, {});
|
|
2376
|
+
if (!newEl) {
|
|
2377
|
+
if (el) {
|
|
2378
|
+
el.remove();
|
|
2379
|
+
this._shapeElements.delete(shape.id);
|
|
2380
|
+
}
|
|
2381
|
+
continue;
|
|
2382
|
+
}
|
|
2383
|
+
if (el) {
|
|
2384
|
+
el.replaceWith(newEl);
|
|
2385
|
+
} else {
|
|
2386
|
+
if (prevEl && prevEl.parentNode === this.shapesLayer) {
|
|
2387
|
+
prevEl.after(newEl);
|
|
2388
|
+
} else {
|
|
2389
|
+
this.shapesLayer.prepend(newEl);
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
el = newEl;
|
|
2393
|
+
this._shapeElements.set(shape.id, el);
|
|
2394
|
+
}
|
|
2395
|
+
prevEl = el;
|
|
2396
|
+
}
|
|
2397
|
+
this._dirtyShapes.clear();
|
|
2398
|
+
this._allDirty = false;
|
|
2399
|
+
}
|
|
2028
2400
|
// Colors are applied via inline `style` (which wins over the CSS class rules
|
|
2029
2401
|
// and serializes self-contained), so a picked color always renders.
|
|
2030
|
-
_renderShapeEl(shape, { standalone = false, colors = null }) {
|
|
2402
|
+
_renderShapeEl(shape, { standalone = false, colors = null, defsTarget = null } = {}) {
|
|
2031
2403
|
let el = null;
|
|
2032
2404
|
const setOpacity = (node) => {
|
|
2033
2405
|
if (shape.opacity != null && shape.opacity !== 1)
|
|
@@ -2065,8 +2437,11 @@ var VdDraw = class {
|
|
|
2065
2437
|
el.classList.add("vd-draw-shape");
|
|
2066
2438
|
this._applyShapeStroke(el, shape, standalone, colors);
|
|
2067
2439
|
setOpacity(el);
|
|
2068
|
-
|
|
2069
|
-
|
|
2440
|
+
const strokeColor = shape.color || (standalone && colors ? colors.shapeStroke : "");
|
|
2441
|
+
const targetDefs = defsTarget || this.defsEl;
|
|
2442
|
+
const markerId = this._getArrowMarkerId(strokeColor, targetDefs);
|
|
2443
|
+
if (shape.arrowEnd) el.setAttribute("marker-end", `url(#${markerId})`);
|
|
2444
|
+
if (shape.arrowStart) el.setAttribute("marker-start", `url(#${markerId})`);
|
|
2070
2445
|
} else if (shape.type === "text" || shape.type === "sticky") {
|
|
2071
2446
|
el = createSvgEl("g");
|
|
2072
2447
|
setOpacity(el);
|
|
@@ -2083,11 +2458,31 @@ var VdDraw = class {
|
|
|
2083
2458
|
else if (standalone && colors) bg.style.fill = colors.sticky;
|
|
2084
2459
|
el.appendChild(bg);
|
|
2085
2460
|
}
|
|
2086
|
-
const
|
|
2461
|
+
const startX = (shape.x || 0) + (shape.type === "sticky" ? 10 : 6);
|
|
2462
|
+
const startY = (shape.y || 0) + (shape.type === "sticky" ? 14 : 18);
|
|
2463
|
+
const availWidth = shape.type === "sticky" ? Math.max(20, (shape.w || 160) - 20) : shape.w && shape.w > 20 ? shape.w - 12 : 0;
|
|
2464
|
+
const lines = wrapText(shape.text || "", availWidth);
|
|
2465
|
+
const text = createSvgEl("text", {
|
|
2466
|
+
x: startX,
|
|
2467
|
+
y: startY,
|
|
2468
|
+
"xml:space": "preserve"
|
|
2469
|
+
});
|
|
2087
2470
|
text.classList.add("vd-draw-text");
|
|
2088
2471
|
const fill = shape.color || (standalone && colors ? colors.text : "");
|
|
2089
2472
|
if (fill) text.style.fill = fill;
|
|
2090
|
-
|
|
2473
|
+
if (!lines.length) {
|
|
2474
|
+
text.textContent = "";
|
|
2475
|
+
} else {
|
|
2476
|
+
const lineHeight = 20;
|
|
2477
|
+
lines.forEach((lineText, idx) => {
|
|
2478
|
+
const tspan = createSvgEl("tspan", {
|
|
2479
|
+
x: startX,
|
|
2480
|
+
y: startY + idx * lineHeight
|
|
2481
|
+
});
|
|
2482
|
+
tspan.textContent = lineText;
|
|
2483
|
+
text.appendChild(tspan);
|
|
2484
|
+
});
|
|
2485
|
+
}
|
|
2091
2486
|
el.appendChild(text);
|
|
2092
2487
|
}
|
|
2093
2488
|
if (el && !standalone) el.setAttribute("data-shape-id", shape.id);
|
|
@@ -2171,9 +2566,18 @@ var VdDraw = class {
|
|
|
2171
2566
|
destroy() {
|
|
2172
2567
|
if (this.destroyed) return;
|
|
2173
2568
|
this.destroyed = true;
|
|
2569
|
+
if (this._rafId != null) {
|
|
2570
|
+
(typeof cancelAnimationFrame === "function" ? cancelAnimationFrame : clearTimeout)(
|
|
2571
|
+
this._rafId
|
|
2572
|
+
);
|
|
2573
|
+
this._rafId = null;
|
|
2574
|
+
}
|
|
2174
2575
|
this.stopTextEdit({ commit: false });
|
|
2175
2576
|
this._unbindEvents();
|
|
2176
2577
|
this.listeners.clear();
|
|
2578
|
+
this._activePointers.clear();
|
|
2579
|
+
this._shapesById.clear();
|
|
2580
|
+
this._shapeElements.clear();
|
|
2177
2581
|
if (this.element) this.element.replaceChildren();
|
|
2178
2582
|
}
|
|
2179
2583
|
};
|
|
@@ -2206,10 +2610,10 @@ var VdDraw2 = (0, import_vue.defineComponent)({
|
|
|
2206
2610
|
setup(props, { emit, expose }) {
|
|
2207
2611
|
const el = (0, import_vue.ref)(null);
|
|
2208
2612
|
let instance = null;
|
|
2209
|
-
const create = () => {
|
|
2613
|
+
const create = (savedDoc) => {
|
|
2210
2614
|
instance = new VdDraw({
|
|
2211
2615
|
element: el.value,
|
|
2212
|
-
data: props.data,
|
|
2616
|
+
data: savedDoc || props.data,
|
|
2213
2617
|
readonly: props.readonly,
|
|
2214
2618
|
tool: props.tool,
|
|
2215
2619
|
gridSize: props.gridSize,
|
|
@@ -2243,18 +2647,28 @@ var VdDraw2 = (0, import_vue.defineComponent)({
|
|
|
2243
2647
|
(next) => instance?.setGridVisible(next)
|
|
2244
2648
|
);
|
|
2245
2649
|
(0, import_vue.watch)(
|
|
2246
|
-
() =>
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2650
|
+
() => props.readonly,
|
|
2651
|
+
(next) => instance?.setReadonly(next)
|
|
2652
|
+
);
|
|
2653
|
+
(0, import_vue.watch)(
|
|
2654
|
+
() => props.snap,
|
|
2655
|
+
(next) => instance?.setSnap(next)
|
|
2656
|
+
);
|
|
2657
|
+
(0, import_vue.watch)(
|
|
2658
|
+
() => props.history,
|
|
2659
|
+
(next) => instance?.setHistoryEnabled(next)
|
|
2660
|
+
);
|
|
2661
|
+
(0, import_vue.watch)(
|
|
2662
|
+
() => props.historyLimit,
|
|
2663
|
+
(next) => instance?.setHistoryLimit(next)
|
|
2664
|
+
);
|
|
2665
|
+
(0, import_vue.watch)(
|
|
2666
|
+
() => [props.gridSize, props.autoFit],
|
|
2254
2667
|
() => {
|
|
2255
2668
|
if (!instance) return;
|
|
2669
|
+
const currentDoc = typeof instance.toJSON === "function" ? instance.toJSON() : void 0;
|
|
2256
2670
|
instance.destroy();
|
|
2257
|
-
create();
|
|
2671
|
+
create(currentDoc);
|
|
2258
2672
|
}
|
|
2259
2673
|
);
|
|
2260
2674
|
(0, import_vue.onBeforeUnmount)(() => {
|
|
@@ -2266,6 +2680,10 @@ var VdDraw2 = (0, import_vue.defineComponent)({
|
|
|
2266
2680
|
expose({
|
|
2267
2681
|
getInstance: () => instance,
|
|
2268
2682
|
setTool: (tool) => instance?.setTool(tool),
|
|
2683
|
+
setReadonly: (readonly) => instance?.setReadonly(readonly),
|
|
2684
|
+
setSnap: (snap) => instance?.setSnap(snap),
|
|
2685
|
+
setHistoryEnabled: (enabled) => instance?.setHistoryEnabled(enabled),
|
|
2686
|
+
setHistoryLimit: (limit) => instance?.setHistoryLimit(limit),
|
|
2269
2687
|
undo: () => instance?.undo(),
|
|
2270
2688
|
redo: () => instance?.redo(),
|
|
2271
2689
|
canUndo: () => Boolean(instance?.canUndo()),
|