@vanduo-oss/vd3-cbun 1.4.0 → 1.4.2
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 +121 -0
- package/README.md +18 -6
- package/SKILL.md +21 -5
- package/dist/charts/core.d.ts +6 -0
- package/dist/charts/index.cjs +235 -14
- package/dist/charts/index.cjs.map +3 -3
- package/dist/charts/index.js +235 -14
- package/dist/charts/index.js.map +3 -3
- package/dist/charts/vd3-charts.css +82 -0
- package/dist/charts/vue.d.ts +6 -0
- package/dist/code-editor/vd3-code-editor.css +5 -0
- package/dist/draw/core.d.ts +7 -1
- package/dist/draw/index.cjs +484 -52
- package/dist/draw/index.cjs.map +2 -2
- package/dist/draw/index.js +484 -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 +311 -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 +311 -13
- package/dist/hex-grid/index.js.map +3 -3
- package/dist/hex-grid/vue.d.ts +4 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/meta.json +33 -33
- package/package.json +10 -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,65 @@ 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
|
+
this._syncToolbar();
|
|
1150
|
+
this._syncStylePanel();
|
|
1151
|
+
}
|
|
1152
|
+
this.render({ scene: false, overlay: true });
|
|
1153
|
+
return this;
|
|
1154
|
+
}
|
|
1155
|
+
setSnap(snap) {
|
|
1156
|
+
this.snap = Boolean(snap);
|
|
1157
|
+
this.options.snap = this.snap;
|
|
1158
|
+
return this;
|
|
1159
|
+
}
|
|
1160
|
+
setHistoryEnabled(enabled) {
|
|
1161
|
+
this.historyEnabled = Boolean(enabled);
|
|
1162
|
+
this.options.history = this.historyEnabled;
|
|
1163
|
+
if (!this.historyEnabled) {
|
|
1164
|
+
this.clearHistory();
|
|
1165
|
+
}
|
|
1166
|
+
return this;
|
|
1167
|
+
}
|
|
1168
|
+
setHistoryLimit(limit) {
|
|
1169
|
+
const num = Number(limit);
|
|
1170
|
+
this.historyLimit = Number.isFinite(num) && num > 0 ? num : 100;
|
|
1171
|
+
this.options.historyLimit = this.historyLimit;
|
|
1172
|
+
if (this.history.length > this.historyLimit + 1) {
|
|
1173
|
+
const drop = this.history.length - (this.historyLimit + 1);
|
|
1174
|
+
this.history.splice(0, drop);
|
|
1175
|
+
this.historyIndex = Math.max(0, this.historyIndex - drop);
|
|
1176
|
+
}
|
|
1177
|
+
return this;
|
|
1178
|
+
}
|
|
1040
1179
|
// ── Tool + current style ─────────────────────────────────────────────────
|
|
1041
1180
|
setTool(tool) {
|
|
1042
1181
|
if (!DRAW_TOOLS.includes(tool)) return this;
|
|
1043
1182
|
this.tool = tool;
|
|
1183
|
+
if (this.canvasEl) this.canvasEl.setAttribute("data-tool", tool);
|
|
1044
1184
|
this._syncToolbar();
|
|
1045
1185
|
return this;
|
|
1046
1186
|
}
|
|
@@ -1074,7 +1214,7 @@ var VdDraw = class {
|
|
|
1074
1214
|
}
|
|
1075
1215
|
// ── Shape CRUD ─────────────────────────────────────────────────────────
|
|
1076
1216
|
getShape(id) {
|
|
1077
|
-
return this.
|
|
1217
|
+
return this._shapesById.get(id) || null;
|
|
1078
1218
|
}
|
|
1079
1219
|
getShapes() {
|
|
1080
1220
|
return this.documentData.shapes.map((s) => deepClone(s));
|
|
@@ -1126,6 +1266,8 @@ var VdDraw = class {
|
|
|
1126
1266
|
shape.text = typeof partial.text === "string" ? partial.text : "";
|
|
1127
1267
|
}
|
|
1128
1268
|
this.documentData.shapes.push(shape);
|
|
1269
|
+
this._shapesById.set(shape.id, shape);
|
|
1270
|
+
this._markDirty(shape.id);
|
|
1129
1271
|
this.render();
|
|
1130
1272
|
this._emitChange("shape:add", { shape: deepClone(shape), shapeId: shape.id });
|
|
1131
1273
|
return shape;
|
|
@@ -1139,6 +1281,8 @@ var VdDraw = class {
|
|
|
1139
1281
|
const shape = this.getShape(id);
|
|
1140
1282
|
if (!shape || !patch) return null;
|
|
1141
1283
|
Object.assign(shape, patch);
|
|
1284
|
+
this._shapesById.set(id, shape);
|
|
1285
|
+
this._markDirty(id);
|
|
1142
1286
|
this.render();
|
|
1143
1287
|
this._emitChange(options.reason || "shape:update", { shapeId: id }, options.reason ? id : null);
|
|
1144
1288
|
return shape;
|
|
@@ -1147,7 +1291,9 @@ var VdDraw = class {
|
|
|
1147
1291
|
const idx = this.documentData.shapes.findIndex((s) => s.id === id);
|
|
1148
1292
|
if (idx === -1) return false;
|
|
1149
1293
|
this.documentData.shapes.splice(idx, 1);
|
|
1294
|
+
this._shapesById.delete(id);
|
|
1150
1295
|
this.selectedIds.delete(id);
|
|
1296
|
+
this._markDirty(id);
|
|
1151
1297
|
this.render();
|
|
1152
1298
|
this._emitChange("shape:delete", { shapeId: id });
|
|
1153
1299
|
return true;
|
|
@@ -1214,11 +1360,13 @@ var VdDraw = class {
|
|
|
1214
1360
|
_replaceShape(next) {
|
|
1215
1361
|
const idx = this.documentData.shapes.findIndex((s) => s.id === next.id);
|
|
1216
1362
|
if (idx !== -1) this.documentData.shapes[idx] = next;
|
|
1363
|
+
this._shapesById.set(next.id, next);
|
|
1217
1364
|
}
|
|
1218
1365
|
nudge(dx, dy) {
|
|
1219
1366
|
const selected = this.getSelectedShapes();
|
|
1220
1367
|
if (!selected.length) return this;
|
|
1221
1368
|
for (const shape of selected) this._replaceShape(translateShape(shape, dx, dy));
|
|
1369
|
+
this._markAllDirty();
|
|
1222
1370
|
this.render();
|
|
1223
1371
|
this._emitChange(
|
|
1224
1372
|
"shape:nudge",
|
|
@@ -1237,6 +1385,7 @@ var VdDraw = class {
|
|
|
1237
1385
|
const scaled = scaleShape(shape, cur.x, cur.y, sx, sy);
|
|
1238
1386
|
this._replaceShape(translateShape(scaled, target.x - cur.x, target.y - cur.y));
|
|
1239
1387
|
}
|
|
1388
|
+
this._markAllDirty();
|
|
1240
1389
|
this.render();
|
|
1241
1390
|
this._emitChange("shape:resize", { shapeIds: [...this.selectedIds] });
|
|
1242
1391
|
return this;
|
|
@@ -1244,8 +1393,10 @@ var VdDraw = class {
|
|
|
1244
1393
|
deleteSelection() {
|
|
1245
1394
|
if (this.selectedIds.size === 0) return false;
|
|
1246
1395
|
const ids = [...this.selectedIds];
|
|
1396
|
+
for (const id of ids) this._shapesById.delete(id);
|
|
1247
1397
|
this.documentData.shapes = this.documentData.shapes.filter((s) => !this.selectedIds.has(s.id));
|
|
1248
1398
|
this.selectedIds.clear();
|
|
1399
|
+
this._markAllDirty();
|
|
1249
1400
|
this.render();
|
|
1250
1401
|
this._emitChange("shape:delete", { shapeIds: ids });
|
|
1251
1402
|
return true;
|
|
@@ -1257,6 +1408,7 @@ var VdDraw = class {
|
|
|
1257
1408
|
for (const shape of selected) {
|
|
1258
1409
|
for (const key of allowed) if (key in patch) shape[key] = patch[key];
|
|
1259
1410
|
}
|
|
1411
|
+
this._markAllDirty();
|
|
1260
1412
|
this.render();
|
|
1261
1413
|
this._emitChange(
|
|
1262
1414
|
"shape:style",
|
|
@@ -1269,6 +1421,7 @@ var VdDraw = class {
|
|
|
1269
1421
|
_reorder(mutator) {
|
|
1270
1422
|
if (this.selectedIds.size === 0) return this;
|
|
1271
1423
|
mutator();
|
|
1424
|
+
this._markAllDirty();
|
|
1272
1425
|
this.render();
|
|
1273
1426
|
this._emitChange("shape:reorder", { shapeIds: [...this.selectedIds] });
|
|
1274
1427
|
return this;
|
|
@@ -1311,6 +1464,7 @@ var VdDraw = class {
|
|
|
1311
1464
|
if (selected.length < 2) return this;
|
|
1312
1465
|
const groupId = createId("grp");
|
|
1313
1466
|
for (const shape of selected) shape.groupId = groupId;
|
|
1467
|
+
this._markAllDirty();
|
|
1314
1468
|
this.render();
|
|
1315
1469
|
this._emitChange("shape:group", { groupId, shapeIds: [...this.selectedIds] });
|
|
1316
1470
|
return this;
|
|
@@ -1325,6 +1479,7 @@ var VdDraw = class {
|
|
|
1325
1479
|
}
|
|
1326
1480
|
}
|
|
1327
1481
|
if (!changed) return this;
|
|
1482
|
+
this._markAllDirty();
|
|
1328
1483
|
this.render();
|
|
1329
1484
|
this._emitChange("shape:ungroup", { shapeIds: [...this.selectedIds] });
|
|
1330
1485
|
return this;
|
|
@@ -1351,9 +1506,11 @@ var VdDraw = class {
|
|
|
1351
1506
|
copy.groupId = groupRemap.get(copy.groupId);
|
|
1352
1507
|
}
|
|
1353
1508
|
this.documentData.shapes.push(copy);
|
|
1509
|
+
this._shapesById.set(copy.id, copy);
|
|
1354
1510
|
newIds.push(copy.id);
|
|
1355
1511
|
}
|
|
1356
1512
|
this.selectedIds = new Set(newIds);
|
|
1513
|
+
this._markAllDirty();
|
|
1357
1514
|
this.render();
|
|
1358
1515
|
this._emitChange("shape:paste", { shapeIds: newIds });
|
|
1359
1516
|
this._emitSelect();
|
|
@@ -1415,6 +1572,30 @@ var VdDraw = class {
|
|
|
1415
1572
|
sticky: read("--vd-draw-sticky-fill", "#fdf3c4")
|
|
1416
1573
|
};
|
|
1417
1574
|
}
|
|
1575
|
+
_getArrowMarkerId(color, defsTarget = this.defsEl) {
|
|
1576
|
+
const isDefault = !color;
|
|
1577
|
+
const safeKey = isDefault ? "arrow" : "arrow-" + color.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
|
|
1578
|
+
const id = this._svgId(safeKey);
|
|
1579
|
+
if (defsTarget && !defsTarget.querySelector(`#${id}`)) {
|
|
1580
|
+
const marker = createSvgEl("marker", {
|
|
1581
|
+
id,
|
|
1582
|
+
viewBox: "0 0 10 10",
|
|
1583
|
+
refX: 8,
|
|
1584
|
+
refY: 5,
|
|
1585
|
+
markerWidth: 7,
|
|
1586
|
+
markerHeight: 7,
|
|
1587
|
+
orient: "auto-start-reverse"
|
|
1588
|
+
});
|
|
1589
|
+
marker.appendChild(
|
|
1590
|
+
createSvgEl("path", {
|
|
1591
|
+
d: "M 0 0 L 10 5 L 0 10 z",
|
|
1592
|
+
fill: color || "var(--vd-draw-shape-stroke)"
|
|
1593
|
+
})
|
|
1594
|
+
);
|
|
1595
|
+
defsTarget.appendChild(marker);
|
|
1596
|
+
}
|
|
1597
|
+
return id;
|
|
1598
|
+
}
|
|
1418
1599
|
toSVG() {
|
|
1419
1600
|
const shapes = this.documentData.shapes;
|
|
1420
1601
|
const bounds = boundsOfShapes(shapes) || { x: 0, y: 0, w: 100, h: 100 };
|
|
@@ -1432,10 +1613,14 @@ var VdDraw = class {
|
|
|
1432
1613
|
height: round(vb.h),
|
|
1433
1614
|
viewBox: `${round(vb.x)} ${round(vb.y)} ${round(vb.w)} ${round(vb.h)}`
|
|
1434
1615
|
});
|
|
1616
|
+
const defs = createSvgEl("defs");
|
|
1435
1617
|
for (const shape of shapes) {
|
|
1436
|
-
const el = this._renderShapeEl(shape, { standalone: true, colors });
|
|
1618
|
+
const el = this._renderShapeEl(shape, { standalone: true, colors, defsTarget: defs });
|
|
1437
1619
|
if (el) svg.appendChild(el);
|
|
1438
1620
|
}
|
|
1621
|
+
if (defs.childNodes.length > 0) {
|
|
1622
|
+
svg.insertBefore(defs, svg.firstChild);
|
|
1623
|
+
}
|
|
1439
1624
|
return new XMLSerializer().serializeToString(svg);
|
|
1440
1625
|
}
|
|
1441
1626
|
toPNG({ scale = 2 } = {}) {
|
|
@@ -1529,7 +1714,9 @@ var VdDraw = class {
|
|
|
1529
1714
|
const viewport = { ...this.documentData.viewport };
|
|
1530
1715
|
this.documentData = deepClone(snapshot);
|
|
1531
1716
|
this.documentData.viewport = viewport;
|
|
1717
|
+
this._rebuildShapeIndex();
|
|
1532
1718
|
this._syncSelectionValidity();
|
|
1719
|
+
this._markAllDirty();
|
|
1533
1720
|
this.render();
|
|
1534
1721
|
this.emit("change", { reason, document: this.toJSON() });
|
|
1535
1722
|
this.emit("history", { reason, canUndo: this.canUndo(), canRedo: this.canRedo() });
|
|
@@ -1545,8 +1732,10 @@ var VdDraw = class {
|
|
|
1545
1732
|
}
|
|
1546
1733
|
load(data) {
|
|
1547
1734
|
this.documentData = normalizeDocument(data);
|
|
1735
|
+
this._rebuildShapeIndex();
|
|
1548
1736
|
this.selectedIds.clear();
|
|
1549
1737
|
this._resetHistory();
|
|
1738
|
+
this._markAllDirty();
|
|
1550
1739
|
this.render();
|
|
1551
1740
|
this._emitChange("load");
|
|
1552
1741
|
this._emitSelect();
|
|
@@ -1555,7 +1744,9 @@ var VdDraw = class {
|
|
|
1555
1744
|
clear() {
|
|
1556
1745
|
if (!this.documentData.shapes.length) return this;
|
|
1557
1746
|
this.documentData.shapes = [];
|
|
1747
|
+
this._shapesById.clear();
|
|
1558
1748
|
this.selectedIds.clear();
|
|
1749
|
+
this._markAllDirty();
|
|
1559
1750
|
this.render();
|
|
1560
1751
|
this._emitChange("clear");
|
|
1561
1752
|
return this;
|
|
@@ -1580,10 +1771,16 @@ var VdDraw = class {
|
|
|
1580
1771
|
}
|
|
1581
1772
|
_positionTextEditor(shape, editor) {
|
|
1582
1773
|
const vp = this.documentData.viewport;
|
|
1583
|
-
|
|
1584
|
-
editor.style.
|
|
1585
|
-
editor.style.
|
|
1586
|
-
editor.style.
|
|
1774
|
+
const scale = vp.scale || 1;
|
|
1775
|
+
editor.style.left = `${vp.x + shape.x * scale}px`;
|
|
1776
|
+
editor.style.top = `${vp.y + shape.y * scale}px`;
|
|
1777
|
+
editor.style.width = `${shape.w * scale}px`;
|
|
1778
|
+
editor.style.height = `${shape.h * scale}px`;
|
|
1779
|
+
editor.style.fontSize = `${16 * scale}px`;
|
|
1780
|
+
editor.style.lineHeight = `${20 * scale}px`;
|
|
1781
|
+
if (shape.type === "sticky") {
|
|
1782
|
+
editor.style.background = shape.fill || "var(--vd-draw-sticky-fill)";
|
|
1783
|
+
}
|
|
1587
1784
|
}
|
|
1588
1785
|
stopTextEdit({ commit = true } = {}) {
|
|
1589
1786
|
if (!this.textEditor) return;
|
|
@@ -1595,6 +1792,7 @@ var VdDraw = class {
|
|
|
1595
1792
|
const shape = this.getShape(id);
|
|
1596
1793
|
if (shape && shape.text !== value) {
|
|
1597
1794
|
shape.text = value;
|
|
1795
|
+
this._markDirty(id);
|
|
1598
1796
|
this.render();
|
|
1599
1797
|
this._emitChange("shape:text", { shapeId: id }, `text:${id}`);
|
|
1600
1798
|
}
|
|
@@ -1652,11 +1850,65 @@ var VdDraw = class {
|
|
|
1652
1850
|
if (this.destroyed || event.button != null && event.button !== 0) return;
|
|
1653
1851
|
this.canvasEl.focus();
|
|
1654
1852
|
this.stopTextEdit({ commit: true });
|
|
1853
|
+
this._activePointers.set(event.pointerId, { clientX: event.clientX, clientY: event.clientY });
|
|
1854
|
+
if (this._activePointers.size === 2) {
|
|
1855
|
+
if (this.interaction) {
|
|
1856
|
+
const it = this.interaction;
|
|
1857
|
+
if (it.kind === "freehand" || it.kind === "create-line" || it.kind === "create-box") {
|
|
1858
|
+
this.documentData.shapes = this.documentData.shapes.filter((s) => s.id !== it.shapeId);
|
|
1859
|
+
this._shapesById.delete(it.shapeId);
|
|
1860
|
+
const el = this._shapeElements.get(it.shapeId);
|
|
1861
|
+
if (el) {
|
|
1862
|
+
el.remove();
|
|
1863
|
+
this._shapeElements.delete(it.shapeId);
|
|
1864
|
+
}
|
|
1865
|
+
this._scheduleRender({ scene: true, overlay: false });
|
|
1866
|
+
} else if (it.kind === "move" || it.kind === "resize") {
|
|
1867
|
+
for (const orig of it.originals) {
|
|
1868
|
+
this._replaceShape(orig);
|
|
1869
|
+
this._markDirty(orig.id);
|
|
1870
|
+
}
|
|
1871
|
+
if (it.kind === "move") this._renderGuides([]);
|
|
1872
|
+
this._scheduleRender({ scene: true });
|
|
1873
|
+
} else if (it.kind === "erase") {
|
|
1874
|
+
for (const id of it.erased) this._markDirty(id);
|
|
1875
|
+
this._scheduleRender({ scene: true, overlay: false });
|
|
1876
|
+
} else if (it.kind === "pan") {
|
|
1877
|
+
if (this.canvasEl) this.canvasEl.classList.remove("vd-draw-panning");
|
|
1878
|
+
} else if (it.kind === "marquee") {
|
|
1879
|
+
clearChildren(this.marqueeLayer);
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
const pts = [...this._activePointers.values()];
|
|
1883
|
+
const p1 = pts[0];
|
|
1884
|
+
const p2 = pts[1];
|
|
1885
|
+
const initialDistance = Math.hypot(p2.clientX - p1.clientX, p2.clientY - p1.clientY) || 1;
|
|
1886
|
+
const initialClientMid = {
|
|
1887
|
+
x: (p1.clientX + p2.clientX) / 2,
|
|
1888
|
+
y: (p1.clientY + p2.clientY) / 2
|
|
1889
|
+
};
|
|
1890
|
+
const initialLocalMid = this._clientToLocal(initialClientMid.x, initialClientMid.y);
|
|
1891
|
+
const initialVp = { ...this.documentData.viewport };
|
|
1892
|
+
const currentScale = initialVp.scale || 1;
|
|
1893
|
+
const worldMid = {
|
|
1894
|
+
x: (initialLocalMid.x - initialVp.x) / currentScale,
|
|
1895
|
+
y: (initialLocalMid.y - initialVp.y) / currentScale
|
|
1896
|
+
};
|
|
1897
|
+
this.interaction = {
|
|
1898
|
+
kind: "pinch",
|
|
1899
|
+
initialDistance,
|
|
1900
|
+
worldMid,
|
|
1901
|
+
initialScale: currentScale
|
|
1902
|
+
};
|
|
1903
|
+
if (typeof event.preventDefault === "function") event.preventDefault();
|
|
1904
|
+
return;
|
|
1905
|
+
}
|
|
1655
1906
|
const world = this._clientToWorld(event.clientX, event.clientY);
|
|
1656
1907
|
const shapeTarget = event.target.closest("[data-shape-id]");
|
|
1657
1908
|
const handleTarget = event.target.closest("[data-handle]");
|
|
1658
1909
|
if (this.tool === "hand") {
|
|
1659
1910
|
this.interaction = this._beginPan(event);
|
|
1911
|
+
if (this.canvasEl) this.canvasEl.classList.add("vd-draw-panning");
|
|
1660
1912
|
this._capture(event.pointerId);
|
|
1661
1913
|
return;
|
|
1662
1914
|
}
|
|
@@ -1667,6 +1919,15 @@ var VdDraw = class {
|
|
|
1667
1919
|
return;
|
|
1668
1920
|
}
|
|
1669
1921
|
if (this.tool === "select") {
|
|
1922
|
+
if (event.detail === 2 && shapeTarget && !this.readonly) {
|
|
1923
|
+
const id = shapeTarget.getAttribute("data-shape-id");
|
|
1924
|
+
const shape = this.getShape(id);
|
|
1925
|
+
if (shape && (shape.type === "text" || shape.type === "sticky")) {
|
|
1926
|
+
this.select(id);
|
|
1927
|
+
this.startTextEdit(id);
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1670
1931
|
if (handleTarget && this.selectedIds.size) {
|
|
1671
1932
|
this.interaction = {
|
|
1672
1933
|
kind: "resize",
|
|
@@ -1730,6 +1991,7 @@ var VdDraw = class {
|
|
|
1730
1991
|
points: [[round(world.x), round(world.y), pressure]]
|
|
1731
1992
|
};
|
|
1732
1993
|
this.documentData.shapes.push(shape2);
|
|
1994
|
+
this._shapesById.set(shape2.id, shape2);
|
|
1733
1995
|
return { kind: "freehand", pointerId: event.pointerId, shapeId: shape2.id };
|
|
1734
1996
|
}
|
|
1735
1997
|
if (tool === "line") {
|
|
@@ -1747,6 +2009,7 @@ var VdDraw = class {
|
|
|
1747
2009
|
opacity: this.style.opacity
|
|
1748
2010
|
};
|
|
1749
2011
|
this.documentData.shapes.push(shape2);
|
|
2012
|
+
this._shapesById.set(shape2.id, shape2);
|
|
1750
2013
|
return { kind: "create-line", pointerId: event.pointerId, shapeId: shape2.id };
|
|
1751
2014
|
}
|
|
1752
2015
|
const type = tool;
|
|
@@ -1764,6 +2027,7 @@ var VdDraw = class {
|
|
|
1764
2027
|
};
|
|
1765
2028
|
if (type === "text" || type === "sticky") shape.text = "";
|
|
1766
2029
|
this.documentData.shapes.push(shape);
|
|
2030
|
+
this._shapesById.set(shape.id, shape);
|
|
1767
2031
|
return { kind: "create-box", pointerId: event.pointerId, shapeId: shape.id, start: world };
|
|
1768
2032
|
}
|
|
1769
2033
|
_applyErase(world) {
|
|
@@ -1778,16 +2042,44 @@ var VdDraw = class {
|
|
|
1778
2042
|
changed = true;
|
|
1779
2043
|
}
|
|
1780
2044
|
}
|
|
1781
|
-
if (changed)
|
|
2045
|
+
if (changed) {
|
|
2046
|
+
for (const id of it.erased) this._markDirty(id);
|
|
2047
|
+
this._scheduleRender({ scene: true, overlay: false });
|
|
2048
|
+
}
|
|
1782
2049
|
}
|
|
1783
2050
|
_handlePointerMove(event) {
|
|
2051
|
+
if (this._activePointers.has(event.pointerId)) {
|
|
2052
|
+
this._activePointers.set(event.pointerId, { clientX: event.clientX, clientY: event.clientY });
|
|
2053
|
+
}
|
|
1784
2054
|
const it = this.interaction;
|
|
1785
2055
|
if (!it) return;
|
|
2056
|
+
if (it.kind === "pinch") {
|
|
2057
|
+
if (this._activePointers.size >= 2) {
|
|
2058
|
+
if (typeof event.preventDefault === "function") event.preventDefault();
|
|
2059
|
+
const pts = [...this._activePointers.values()];
|
|
2060
|
+
const p1 = pts[0];
|
|
2061
|
+
const p2 = pts[1];
|
|
2062
|
+
const curDistance = Math.hypot(p2.clientX - p1.clientX, p2.clientY - p1.clientY) || 1;
|
|
2063
|
+
const curClientMid = {
|
|
2064
|
+
x: (p1.clientX + p2.clientX) / 2,
|
|
2065
|
+
y: (p1.clientY + p2.clientY) / 2
|
|
2066
|
+
};
|
|
2067
|
+
const curLocalMid = this._clientToLocal(curClientMid.x, curClientMid.y);
|
|
2068
|
+
const distRatio = curDistance / it.initialDistance;
|
|
2069
|
+
const newScale = clamp(it.initialScale * distRatio, MIN_SCALE, MAX_SCALE);
|
|
2070
|
+
const vp = this.documentData.viewport;
|
|
2071
|
+
vp.scale = newScale;
|
|
2072
|
+
vp.x = curLocalMid.x - it.worldMid.x * newScale;
|
|
2073
|
+
vp.y = curLocalMid.y - it.worldMid.y * newScale;
|
|
2074
|
+
this._scheduleRender({ scene: false });
|
|
2075
|
+
}
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
1786
2078
|
if (it.kind === "pan") {
|
|
1787
2079
|
const vp = this.documentData.viewport;
|
|
1788
2080
|
vp.x = it.startX + (event.clientX - it.startClientX);
|
|
1789
2081
|
vp.y = it.startY + (event.clientY - it.startClientY);
|
|
1790
|
-
this.
|
|
2082
|
+
this._scheduleRender({ scene: false });
|
|
1791
2083
|
return;
|
|
1792
2084
|
}
|
|
1793
2085
|
const world = this._clientToWorld(event.clientX, event.clientY);
|
|
@@ -1805,8 +2097,9 @@ var VdDraw = class {
|
|
|
1805
2097
|
if (snap.dx || snap.dy)
|
|
1806
2098
|
for (const orig of it.originals)
|
|
1807
2099
|
this._replaceShape(translateShape(orig, dx + snap.dx, dy + snap.dy));
|
|
2100
|
+
for (const orig of it.originals) this._markDirty(orig.id);
|
|
1808
2101
|
this._renderGuides(snap.guides);
|
|
1809
|
-
this.
|
|
2102
|
+
this._scheduleRender({ scene: true, guides: false });
|
|
1810
2103
|
return;
|
|
1811
2104
|
}
|
|
1812
2105
|
if (it.kind === "resize") {
|
|
@@ -1821,9 +2114,10 @@ var VdDraw = class {
|
|
|
1821
2114
|
for (const orig of it.originals) {
|
|
1822
2115
|
const scaled = scaleShape(orig, it.startBounds.x, it.startBounds.y, sx, sy);
|
|
1823
2116
|
this._replaceShape(translateShape(scaled, t.x - it.startBounds.x, t.y - it.startBounds.y));
|
|
2117
|
+
this._markDirty(orig.id);
|
|
1824
2118
|
}
|
|
1825
2119
|
it.moved = true;
|
|
1826
|
-
this.
|
|
2120
|
+
this._scheduleRender();
|
|
1827
2121
|
return;
|
|
1828
2122
|
}
|
|
1829
2123
|
if (it.kind === "marquee") {
|
|
@@ -1834,8 +2128,14 @@ var VdDraw = class {
|
|
|
1834
2128
|
if (it.kind === "freehand") {
|
|
1835
2129
|
const shape = this.getShape(it.shapeId);
|
|
1836
2130
|
if (shape) {
|
|
1837
|
-
|
|
1838
|
-
|
|
2131
|
+
const raw = [round(world.x), round(world.y), event.pressure || 0.5];
|
|
2132
|
+
const preset = BRUSH_PRESETS[shape.brush] || BRUSH_PRESETS[DEFAULT_BRUSH];
|
|
2133
|
+
const smoothFactor = 1 - (preset.smoothing || 0.5) * 0.6;
|
|
2134
|
+
const prev = shape.points[shape.points.length - 1];
|
|
2135
|
+
const pt = prev ? smoothPoint(prev, raw, smoothFactor) : raw;
|
|
2136
|
+
appendAndSimplify(shape.points, pt);
|
|
2137
|
+
this._markDirty(it.shapeId);
|
|
2138
|
+
this._scheduleRender({ scene: true });
|
|
1839
2139
|
}
|
|
1840
2140
|
return;
|
|
1841
2141
|
}
|
|
@@ -1843,7 +2143,8 @@ var VdDraw = class {
|
|
|
1843
2143
|
const shape = this.getShape(it.shapeId);
|
|
1844
2144
|
if (shape) {
|
|
1845
2145
|
shape.points[1] = [round(world.x), round(world.y)];
|
|
1846
|
-
this.
|
|
2146
|
+
this._markDirty(it.shapeId);
|
|
2147
|
+
this._scheduleRender({ scene: true });
|
|
1847
2148
|
}
|
|
1848
2149
|
return;
|
|
1849
2150
|
}
|
|
@@ -1854,7 +2155,8 @@ var VdDraw = class {
|
|
|
1854
2155
|
shape.y = round(Math.min(it.start.y, world.y));
|
|
1855
2156
|
shape.w = round(Math.max(1, Math.abs(world.x - it.start.x)));
|
|
1856
2157
|
shape.h = round(Math.max(1, Math.abs(world.y - it.start.y)));
|
|
1857
|
-
this.
|
|
2158
|
+
this._markDirty(it.shapeId);
|
|
2159
|
+
this._scheduleRender({ scene: true });
|
|
1858
2160
|
}
|
|
1859
2161
|
}
|
|
1860
2162
|
}
|
|
@@ -1873,9 +2175,19 @@ var VdDraw = class {
|
|
|
1873
2175
|
return { x, y, w, h: h2 };
|
|
1874
2176
|
}
|
|
1875
2177
|
_handlePointerUp(event) {
|
|
2178
|
+
this._activePointers.delete(event.pointerId);
|
|
1876
2179
|
const it = this.interaction;
|
|
1877
2180
|
if (!it) return;
|
|
2181
|
+
if (it.kind === "pinch") {
|
|
2182
|
+
if (this._activePointers.size < 2) {
|
|
2183
|
+
this.interaction = null;
|
|
2184
|
+
if (this.canvasEl) this.canvasEl.classList.remove("vd-draw-panning");
|
|
2185
|
+
this._emitViewportChange("viewport:pinch");
|
|
2186
|
+
}
|
|
2187
|
+
return;
|
|
2188
|
+
}
|
|
1878
2189
|
this.interaction = null;
|
|
2190
|
+
if (this.canvasEl) this.canvasEl.classList.remove("vd-draw-panning");
|
|
1879
2191
|
if (typeof this.canvasEl.releasePointerCapture === "function" && this.canvasEl.hasPointerCapture?.(event.pointerId)) {
|
|
1880
2192
|
try {
|
|
1881
2193
|
this.canvasEl.releasePointerCapture(event.pointerId);
|
|
@@ -1891,18 +2203,22 @@ var VdDraw = class {
|
|
|
1891
2203
|
if (it.kind === "erase") {
|
|
1892
2204
|
if (it.erased.size) {
|
|
1893
2205
|
const ids = [...it.erased];
|
|
2206
|
+
for (const id of ids) this._shapesById.delete(id);
|
|
1894
2207
|
this.documentData.shapes = this.documentData.shapes.filter((s) => !it.erased.has(s.id));
|
|
2208
|
+
this._markAllDirty();
|
|
1895
2209
|
this.render();
|
|
1896
2210
|
this._emitChange("shape:erase", { shapeIds: ids });
|
|
1897
2211
|
}
|
|
1898
2212
|
return;
|
|
1899
2213
|
}
|
|
1900
2214
|
if (it.kind === "move" && it.moved) {
|
|
2215
|
+
this._markAllDirty();
|
|
1901
2216
|
this.render();
|
|
1902
2217
|
this._emitChange("shape:move", { shapeIds: [...this.selectedIds] });
|
|
1903
2218
|
return;
|
|
1904
2219
|
}
|
|
1905
2220
|
if (it.kind === "resize" && it.moved) {
|
|
2221
|
+
this._markAllDirty();
|
|
1906
2222
|
this.render();
|
|
1907
2223
|
this._emitChange("shape:resize", { shapeIds: [...this.selectedIds] });
|
|
1908
2224
|
return;
|
|
@@ -1919,6 +2235,8 @@ var VdDraw = class {
|
|
|
1919
2235
|
const b = shapeBounds(shape);
|
|
1920
2236
|
if (it.kind !== "freehand" && b.w < 2 && b.h < 2 && shape.type !== "text" && shape.type !== "sticky") {
|
|
1921
2237
|
this.documentData.shapes = this.documentData.shapes.filter((s) => s.id !== it.shapeId);
|
|
2238
|
+
this._shapesById.delete(it.shapeId);
|
|
2239
|
+
this._markAllDirty();
|
|
1922
2240
|
this.render();
|
|
1923
2241
|
return;
|
|
1924
2242
|
}
|
|
@@ -1932,6 +2250,7 @@ var VdDraw = class {
|
|
|
1932
2250
|
this.select(shape.id);
|
|
1933
2251
|
this.setTool("select");
|
|
1934
2252
|
}
|
|
2253
|
+
this._markDirty(shape.id);
|
|
1935
2254
|
this.render();
|
|
1936
2255
|
this._emitChange("shape:add", { shape: deepClone(shape), shapeId: shape.id });
|
|
1937
2256
|
if (shape.type === "text" || shape.type === "sticky") this.startTextEdit(shape.id);
|
|
@@ -2008,26 +2327,93 @@ var VdDraw = class {
|
|
|
2008
2327
|
if (this.destroyed) return;
|
|
2009
2328
|
this.render({ scene: false });
|
|
2010
2329
|
}
|
|
2330
|
+
// ── Dirty tracking ──────────────────────────────────────────────────────
|
|
2331
|
+
_markDirty(shapeId) {
|
|
2332
|
+
this._dirtyShapes.add(shapeId);
|
|
2333
|
+
}
|
|
2334
|
+
_markAllDirty() {
|
|
2335
|
+
this._allDirty = true;
|
|
2336
|
+
}
|
|
2337
|
+
// ── rAF-batched render ─────────────────────────────────────────────────
|
|
2338
|
+
_scheduleRender(flags = {}) {
|
|
2339
|
+
this._pendingFlags = {
|
|
2340
|
+
scene: (this._pendingFlags?.scene ?? false) || flags.scene !== false,
|
|
2341
|
+
overlay: (this._pendingFlags?.overlay ?? false) || flags.overlay !== false
|
|
2342
|
+
};
|
|
2343
|
+
if (this._rafId != null) return;
|
|
2344
|
+
this._rafId = (typeof requestAnimationFrame === "function" ? requestAnimationFrame : setTimeout)(() => {
|
|
2345
|
+
this._rafId = null;
|
|
2346
|
+
const f = this._pendingFlags || {};
|
|
2347
|
+
this._pendingFlags = null;
|
|
2348
|
+
this.render(f);
|
|
2349
|
+
});
|
|
2350
|
+
}
|
|
2011
2351
|
// ── Rendering ────────────────────────────────────────────────────────────
|
|
2012
2352
|
render(flags = {}) {
|
|
2013
2353
|
if (this.destroyed) return;
|
|
2014
2354
|
const { scene = true, overlay = true } = flags;
|
|
2015
2355
|
const vp = this.documentData.viewport;
|
|
2016
2356
|
this.world.setAttribute("transform", `matrix(${vp.scale} 0 0 ${vp.scale} ${vp.x} ${vp.y})`);
|
|
2357
|
+
if (this.textEditor) {
|
|
2358
|
+
const activeShape = this.getShape(this.textEditor.id);
|
|
2359
|
+
if (activeShape) this._positionTextEditor(activeShape, this.textEditor.el);
|
|
2360
|
+
}
|
|
2017
2361
|
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
|
-
}
|
|
2362
|
+
this._renderSceneIncremental();
|
|
2025
2363
|
}
|
|
2026
2364
|
if (overlay) this._renderOverlay();
|
|
2027
2365
|
}
|
|
2366
|
+
_renderSceneIncremental() {
|
|
2367
|
+
const shapes = this.documentData.shapes;
|
|
2368
|
+
const erasing = this.interaction && this.interaction.kind === "erase" ? this.interaction.erased : null;
|
|
2369
|
+
const currentIds = /* @__PURE__ */ new Set();
|
|
2370
|
+
for (const shape of shapes) {
|
|
2371
|
+
if (erasing && erasing.has(shape.id)) continue;
|
|
2372
|
+
currentIds.add(shape.id);
|
|
2373
|
+
}
|
|
2374
|
+
for (const [id, el] of this._shapeElements) {
|
|
2375
|
+
if (!currentIds.has(id)) {
|
|
2376
|
+
el.remove();
|
|
2377
|
+
this._shapeElements.delete(id);
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
const fullRebuild = this._allDirty;
|
|
2381
|
+
let prevEl = null;
|
|
2382
|
+
for (const shape of shapes) {
|
|
2383
|
+
if (!currentIds.has(shape.id)) continue;
|
|
2384
|
+
const needsUpdate = fullRebuild || this._dirtyShapes.has(shape.id);
|
|
2385
|
+
let el = this._shapeElements.get(shape.id);
|
|
2386
|
+
if (needsUpdate || !el) {
|
|
2387
|
+
const newEl = this._renderShapeEl(shape, {});
|
|
2388
|
+
if (!newEl) {
|
|
2389
|
+
if (el) {
|
|
2390
|
+
el.remove();
|
|
2391
|
+
this._shapeElements.delete(shape.id);
|
|
2392
|
+
}
|
|
2393
|
+
continue;
|
|
2394
|
+
}
|
|
2395
|
+
if (el) {
|
|
2396
|
+
el.replaceWith(newEl);
|
|
2397
|
+
} else {
|
|
2398
|
+
if (prevEl && prevEl.parentNode === this.shapesLayer) {
|
|
2399
|
+
prevEl.after(newEl);
|
|
2400
|
+
} else {
|
|
2401
|
+
this.shapesLayer.prepend(newEl);
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
el = newEl;
|
|
2405
|
+
this._shapeElements.set(shape.id, el);
|
|
2406
|
+
}
|
|
2407
|
+
const expected = prevEl ? prevEl.nextElementSibling : this.shapesLayer.firstElementChild;
|
|
2408
|
+
if (el !== expected) this.shapesLayer.insertBefore(el, expected);
|
|
2409
|
+
prevEl = el;
|
|
2410
|
+
}
|
|
2411
|
+
this._dirtyShapes.clear();
|
|
2412
|
+
this._allDirty = false;
|
|
2413
|
+
}
|
|
2028
2414
|
// Colors are applied via inline `style` (which wins over the CSS class rules
|
|
2029
2415
|
// and serializes self-contained), so a picked color always renders.
|
|
2030
|
-
_renderShapeEl(shape, { standalone = false, colors = null }) {
|
|
2416
|
+
_renderShapeEl(shape, { standalone = false, colors = null, defsTarget = null } = {}) {
|
|
2031
2417
|
let el = null;
|
|
2032
2418
|
const setOpacity = (node) => {
|
|
2033
2419
|
if (shape.opacity != null && shape.opacity !== 1)
|
|
@@ -2065,8 +2451,11 @@ var VdDraw = class {
|
|
|
2065
2451
|
el.classList.add("vd-draw-shape");
|
|
2066
2452
|
this._applyShapeStroke(el, shape, standalone, colors);
|
|
2067
2453
|
setOpacity(el);
|
|
2068
|
-
|
|
2069
|
-
|
|
2454
|
+
const strokeColor = shape.color || (standalone && colors ? colors.shapeStroke : "");
|
|
2455
|
+
const targetDefs = defsTarget || this.defsEl;
|
|
2456
|
+
const markerId = this._getArrowMarkerId(strokeColor, targetDefs);
|
|
2457
|
+
if (shape.arrowEnd) el.setAttribute("marker-end", `url(#${markerId})`);
|
|
2458
|
+
if (shape.arrowStart) el.setAttribute("marker-start", `url(#${markerId})`);
|
|
2070
2459
|
} else if (shape.type === "text" || shape.type === "sticky") {
|
|
2071
2460
|
el = createSvgEl("g");
|
|
2072
2461
|
setOpacity(el);
|
|
@@ -2083,11 +2472,31 @@ var VdDraw = class {
|
|
|
2083
2472
|
else if (standalone && colors) bg.style.fill = colors.sticky;
|
|
2084
2473
|
el.appendChild(bg);
|
|
2085
2474
|
}
|
|
2086
|
-
const
|
|
2475
|
+
const startX = (shape.x || 0) + (shape.type === "sticky" ? 10 : 6);
|
|
2476
|
+
const startY = (shape.y || 0) + (shape.type === "sticky" ? 14 : 18);
|
|
2477
|
+
const availWidth = shape.type === "sticky" ? Math.max(20, (shape.w || 160) - 20) : shape.w && shape.w > 20 ? shape.w - 12 : 0;
|
|
2478
|
+
const lines = wrapText(shape.text || "", availWidth);
|
|
2479
|
+
const text = createSvgEl("text", {
|
|
2480
|
+
x: startX,
|
|
2481
|
+
y: startY,
|
|
2482
|
+
"xml:space": "preserve"
|
|
2483
|
+
});
|
|
2087
2484
|
text.classList.add("vd-draw-text");
|
|
2088
2485
|
const fill = shape.color || (standalone && colors ? colors.text : "");
|
|
2089
2486
|
if (fill) text.style.fill = fill;
|
|
2090
|
-
|
|
2487
|
+
if (!lines.length) {
|
|
2488
|
+
text.textContent = "";
|
|
2489
|
+
} else {
|
|
2490
|
+
const lineHeight = 20;
|
|
2491
|
+
lines.forEach((lineText, idx) => {
|
|
2492
|
+
const tspan = createSvgEl("tspan", {
|
|
2493
|
+
x: startX,
|
|
2494
|
+
y: startY + idx * lineHeight
|
|
2495
|
+
});
|
|
2496
|
+
tspan.textContent = lineText;
|
|
2497
|
+
text.appendChild(tspan);
|
|
2498
|
+
});
|
|
2499
|
+
}
|
|
2091
2500
|
el.appendChild(text);
|
|
2092
2501
|
}
|
|
2093
2502
|
if (el && !standalone) el.setAttribute("data-shape-id", shape.id);
|
|
@@ -2171,9 +2580,18 @@ var VdDraw = class {
|
|
|
2171
2580
|
destroy() {
|
|
2172
2581
|
if (this.destroyed) return;
|
|
2173
2582
|
this.destroyed = true;
|
|
2583
|
+
if (this._rafId != null) {
|
|
2584
|
+
(typeof cancelAnimationFrame === "function" ? cancelAnimationFrame : clearTimeout)(
|
|
2585
|
+
this._rafId
|
|
2586
|
+
);
|
|
2587
|
+
this._rafId = null;
|
|
2588
|
+
}
|
|
2174
2589
|
this.stopTextEdit({ commit: false });
|
|
2175
2590
|
this._unbindEvents();
|
|
2176
2591
|
this.listeners.clear();
|
|
2592
|
+
this._activePointers.clear();
|
|
2593
|
+
this._shapesById.clear();
|
|
2594
|
+
this._shapeElements.clear();
|
|
2177
2595
|
if (this.element) this.element.replaceChildren();
|
|
2178
2596
|
}
|
|
2179
2597
|
};
|
|
@@ -2206,10 +2624,10 @@ var VdDraw2 = (0, import_vue.defineComponent)({
|
|
|
2206
2624
|
setup(props, { emit, expose }) {
|
|
2207
2625
|
const el = (0, import_vue.ref)(null);
|
|
2208
2626
|
let instance = null;
|
|
2209
|
-
const create = () => {
|
|
2627
|
+
const create = (savedDoc) => {
|
|
2210
2628
|
instance = new VdDraw({
|
|
2211
2629
|
element: el.value,
|
|
2212
|
-
data: props.data,
|
|
2630
|
+
data: savedDoc || props.data,
|
|
2213
2631
|
readonly: props.readonly,
|
|
2214
2632
|
tool: props.tool,
|
|
2215
2633
|
gridSize: props.gridSize,
|
|
@@ -2243,18 +2661,28 @@ var VdDraw2 = (0, import_vue.defineComponent)({
|
|
|
2243
2661
|
(next) => instance?.setGridVisible(next)
|
|
2244
2662
|
);
|
|
2245
2663
|
(0, import_vue.watch)(
|
|
2246
|
-
() =>
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2664
|
+
() => props.readonly,
|
|
2665
|
+
(next) => instance?.setReadonly(next)
|
|
2666
|
+
);
|
|
2667
|
+
(0, import_vue.watch)(
|
|
2668
|
+
() => props.snap,
|
|
2669
|
+
(next) => instance?.setSnap(next)
|
|
2670
|
+
);
|
|
2671
|
+
(0, import_vue.watch)(
|
|
2672
|
+
() => props.history,
|
|
2673
|
+
(next) => instance?.setHistoryEnabled(next)
|
|
2674
|
+
);
|
|
2675
|
+
(0, import_vue.watch)(
|
|
2676
|
+
() => props.historyLimit,
|
|
2677
|
+
(next) => instance?.setHistoryLimit(next)
|
|
2678
|
+
);
|
|
2679
|
+
(0, import_vue.watch)(
|
|
2680
|
+
() => [props.gridSize, props.autoFit],
|
|
2254
2681
|
() => {
|
|
2255
2682
|
if (!instance) return;
|
|
2683
|
+
const currentDoc = typeof instance.toJSON === "function" ? instance.toJSON() : void 0;
|
|
2256
2684
|
instance.destroy();
|
|
2257
|
-
create();
|
|
2685
|
+
create(currentDoc);
|
|
2258
2686
|
}
|
|
2259
2687
|
);
|
|
2260
2688
|
(0, import_vue.onBeforeUnmount)(() => {
|
|
@@ -2266,6 +2694,10 @@ var VdDraw2 = (0, import_vue.defineComponent)({
|
|
|
2266
2694
|
expose({
|
|
2267
2695
|
getInstance: () => instance,
|
|
2268
2696
|
setTool: (tool) => instance?.setTool(tool),
|
|
2697
|
+
setReadonly: (readonly) => instance?.setReadonly(readonly),
|
|
2698
|
+
setSnap: (snap) => instance?.setSnap(snap),
|
|
2699
|
+
setHistoryEnabled: (enabled) => instance?.setHistoryEnabled(enabled),
|
|
2700
|
+
setHistoryLimit: (limit) => instance?.setHistoryLimit(limit),
|
|
2269
2701
|
undo: () => instance?.undo(),
|
|
2270
2702
|
redo: () => instance?.redo(),
|
|
2271
2703
|
canUndo: () => Boolean(instance?.canUndo()),
|