@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.js
CHANGED
|
@@ -257,6 +257,37 @@ function simplifyPoints(points, min = 1.2) {
|
|
|
257
257
|
}
|
|
258
258
|
return out;
|
|
259
259
|
}
|
|
260
|
+
function smoothPoint(prev, next, factor = 0.5) {
|
|
261
|
+
const f = clamp(factor, 0.1, 1);
|
|
262
|
+
const x = round(prev[0] + (next[0] - prev[0]) * f);
|
|
263
|
+
const y = round(prev[1] + (next[1] - prev[1]) * f);
|
|
264
|
+
const hasPressure = prev.length >= 3 && prev[2] != null || next.length >= 3 && next[2] != null;
|
|
265
|
+
if (hasPressure) {
|
|
266
|
+
const pp = prev.length >= 3 && prev[2] != null ? prev[2] : 0.5;
|
|
267
|
+
const np = next.length >= 3 && next[2] != null ? next[2] : 0.5;
|
|
268
|
+
return [x, y, clamp(pp + (np - pp) * f, 0, 1)];
|
|
269
|
+
}
|
|
270
|
+
return [x, y];
|
|
271
|
+
}
|
|
272
|
+
function appendAndSimplify(points, point, minDist = 1.5, maxLen = 500) {
|
|
273
|
+
if (!points.length) {
|
|
274
|
+
points.push(roundPoint(point));
|
|
275
|
+
return points;
|
|
276
|
+
}
|
|
277
|
+
const last = points[points.length - 1];
|
|
278
|
+
if (Math.hypot(point[0] - last[0], point[1] - last[1]) < minDist) return points;
|
|
279
|
+
points.push(roundPoint(point));
|
|
280
|
+
if (points.length > maxLen) {
|
|
281
|
+
const first = points[0];
|
|
282
|
+
const end = points[points.length - 1];
|
|
283
|
+
const kept = [first];
|
|
284
|
+
for (let i = 2; i < points.length - 1; i += 2) kept.push(points[i]);
|
|
285
|
+
kept.push(end);
|
|
286
|
+
points.length = 0;
|
|
287
|
+
for (const p of kept) points.push(p);
|
|
288
|
+
}
|
|
289
|
+
return points;
|
|
290
|
+
}
|
|
260
291
|
function streamlinePoints(points, streamline) {
|
|
261
292
|
const first = points[0];
|
|
262
293
|
const out = [[first[0], first[1], first[2] == null ? 0.5 : clamp(first[2], 0, 1)]];
|
|
@@ -471,6 +502,55 @@ function normalizeDocument(data) {
|
|
|
471
502
|
shapes
|
|
472
503
|
};
|
|
473
504
|
}
|
|
505
|
+
function wrapText(text, maxWidth, charWidth = 8.8) {
|
|
506
|
+
if (typeof text !== "string" || text.length === 0) return [];
|
|
507
|
+
const rawLines = text.split(/\r?\n/);
|
|
508
|
+
if (!maxWidth || maxWidth <= 0 || !Number.isFinite(maxWidth)) {
|
|
509
|
+
return rawLines;
|
|
510
|
+
}
|
|
511
|
+
const maxChars = Math.max(1, Math.floor(maxWidth / charWidth));
|
|
512
|
+
const result = [];
|
|
513
|
+
for (const rawLine of rawLines) {
|
|
514
|
+
if (rawLine.length <= maxChars) {
|
|
515
|
+
result.push(rawLine);
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
const words = rawLine.split(" ");
|
|
519
|
+
let currentLine = "";
|
|
520
|
+
for (const word of words) {
|
|
521
|
+
if (!currentLine) {
|
|
522
|
+
if (word.length <= maxChars) {
|
|
523
|
+
currentLine = word;
|
|
524
|
+
} else {
|
|
525
|
+
let remaining = word;
|
|
526
|
+
while (remaining.length > maxChars) {
|
|
527
|
+
result.push(remaining.slice(0, maxChars));
|
|
528
|
+
remaining = remaining.slice(maxChars);
|
|
529
|
+
}
|
|
530
|
+
currentLine = remaining;
|
|
531
|
+
}
|
|
532
|
+
} else {
|
|
533
|
+
if (currentLine.length + 1 + word.length <= maxChars) {
|
|
534
|
+
currentLine += " " + word;
|
|
535
|
+
} else {
|
|
536
|
+
result.push(currentLine);
|
|
537
|
+
if (word.length <= maxChars) {
|
|
538
|
+
currentLine = word;
|
|
539
|
+
} else {
|
|
540
|
+
let remaining = word;
|
|
541
|
+
while (remaining.length > maxChars) {
|
|
542
|
+
result.push(remaining.slice(0, maxChars));
|
|
543
|
+
remaining = remaining.slice(maxChars);
|
|
544
|
+
}
|
|
545
|
+
currentLine = remaining;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
if (currentLine) result.push(currentLine);
|
|
551
|
+
}
|
|
552
|
+
return result;
|
|
553
|
+
}
|
|
474
554
|
|
|
475
555
|
// src/draw/core.js
|
|
476
556
|
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
@@ -596,6 +676,15 @@ var VdDraw = class {
|
|
|
596
676
|
this.lastReason = null;
|
|
597
677
|
this.lastTargetKey = null;
|
|
598
678
|
this.listeners = /* @__PURE__ */ new Map();
|
|
679
|
+
this.options = { ...opts };
|
|
680
|
+
this._shapesById = /* @__PURE__ */ new Map();
|
|
681
|
+
this._rebuildShapeIndex();
|
|
682
|
+
this._activePointers = /* @__PURE__ */ new Map();
|
|
683
|
+
this._shapeElements = /* @__PURE__ */ new Map();
|
|
684
|
+
this._dirtyShapes = /* @__PURE__ */ new Set();
|
|
685
|
+
this._allDirty = true;
|
|
686
|
+
this._rafId = null;
|
|
687
|
+
this._pendingFlags = null;
|
|
599
688
|
this._buildShell();
|
|
600
689
|
if (typeof opts.color !== "string") this.style.color = this._resolveInk();
|
|
601
690
|
this._bindEvents();
|
|
@@ -604,6 +693,12 @@ var VdDraw = class {
|
|
|
604
693
|
this._syncStylePanel();
|
|
605
694
|
this._scheduleReady();
|
|
606
695
|
}
|
|
696
|
+
_rebuildShapeIndex() {
|
|
697
|
+
this._shapesById.clear();
|
|
698
|
+
for (const s of this.documentData.shapes) {
|
|
699
|
+
this._shapesById.set(s.id, s);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
607
702
|
_resolveElement(target) {
|
|
608
703
|
if (!target) return null;
|
|
609
704
|
if (typeof target === "string") return hasWindow() ? document.querySelector(target) : null;
|
|
@@ -637,11 +732,13 @@ var VdDraw = class {
|
|
|
637
732
|
}
|
|
638
733
|
this.canvasEl = document.createElement("div");
|
|
639
734
|
this.canvasEl.className = "vd-draw-canvas";
|
|
735
|
+
this.canvasEl.setAttribute("data-tool", this.tool);
|
|
640
736
|
this.canvasEl.tabIndex = 0;
|
|
641
737
|
this.svg = createSvgEl("svg", { class: "vd-draw-svg" });
|
|
642
738
|
this.svg.setAttribute("width", "100%");
|
|
643
739
|
this.svg.setAttribute("height", "100%");
|
|
644
740
|
const defs = createSvgEl("defs");
|
|
741
|
+
this.defsEl = defs;
|
|
645
742
|
const gs = this.gridSize;
|
|
646
743
|
this.gridPattern = createSvgEl("pattern", {
|
|
647
744
|
id: this._svgId("grid"),
|
|
@@ -657,19 +754,7 @@ var VdDraw = class {
|
|
|
657
754
|
});
|
|
658
755
|
this.gridPattern.appendChild(this.gridPatternPath);
|
|
659
756
|
defs.appendChild(this.gridPattern);
|
|
660
|
-
|
|
661
|
-
id: this._svgId("arrow"),
|
|
662
|
-
viewBox: "0 0 10 10",
|
|
663
|
-
refX: 8,
|
|
664
|
-
refY: 5,
|
|
665
|
-
markerWidth: 7,
|
|
666
|
-
markerHeight: 7,
|
|
667
|
-
orient: "auto-start-reverse"
|
|
668
|
-
});
|
|
669
|
-
marker.appendChild(
|
|
670
|
-
createSvgEl("path", { d: "M 0 0 L 10 5 L 0 10 z", fill: "var(--vd-draw-shape-stroke)" })
|
|
671
|
-
);
|
|
672
|
-
defs.appendChild(marker);
|
|
757
|
+
this._getArrowMarkerId("", defs);
|
|
673
758
|
this.svg.appendChild(defs);
|
|
674
759
|
this.world = createSvgEl("g", { class: "vd-draw-world" });
|
|
675
760
|
this.gridLayer = createSvgEl("rect", {
|
|
@@ -960,11 +1045,11 @@ var VdDraw = class {
|
|
|
960
1045
|
this._emitViewportChange("viewport:reset");
|
|
961
1046
|
return this;
|
|
962
1047
|
}
|
|
963
|
-
fitView() {
|
|
1048
|
+
fitView(padding = 40) {
|
|
964
1049
|
const bounds = boundsOfShapes(this.documentData.shapes);
|
|
965
1050
|
const rect = this.canvasEl.getBoundingClientRect();
|
|
966
1051
|
if (!bounds || bounds.w === 0 || bounds.h === 0 || !rect.width || !rect.height) return this;
|
|
967
|
-
const pad = 40;
|
|
1052
|
+
const pad = Number.isFinite(padding) ? Math.max(0, padding) : 40;
|
|
968
1053
|
const scale = clamp(
|
|
969
1054
|
Math.min((rect.width - pad * 2) / bounds.w, (rect.height - pad * 2) / bounds.h),
|
|
970
1055
|
MIN_SCALE,
|
|
@@ -1006,10 +1091,65 @@ var VdDraw = class {
|
|
|
1006
1091
|
toggleGrid() {
|
|
1007
1092
|
return this.setGridVisible(!this.showGrid);
|
|
1008
1093
|
}
|
|
1094
|
+
// ── Runtime options ──────────────────────────────────────────────────────
|
|
1095
|
+
setReadonly(readonly) {
|
|
1096
|
+
const next = Boolean(readonly);
|
|
1097
|
+
if (this.readonly === next) return this;
|
|
1098
|
+
this.readonly = next;
|
|
1099
|
+
this.options.readonly = next;
|
|
1100
|
+
if (this.element) {
|
|
1101
|
+
if (next) this.element.setAttribute("data-readonly", "true");
|
|
1102
|
+
else this.element.removeAttribute("data-readonly");
|
|
1103
|
+
}
|
|
1104
|
+
if (next) {
|
|
1105
|
+
this.stopTextEdit({ commit: true });
|
|
1106
|
+
this.deselect();
|
|
1107
|
+
if (this.toolbarEl) this.toolbarEl.style.display = "none";
|
|
1108
|
+
if (this.panelEl) this.panelEl.style.display = "none";
|
|
1109
|
+
} else {
|
|
1110
|
+
if (this.toolbarEl && !this.toolbarEl.children.length) {
|
|
1111
|
+
this._buildToolbar();
|
|
1112
|
+
}
|
|
1113
|
+
if (this.panelEl && !this.panelEl.children.length) {
|
|
1114
|
+
this._buildStylePanel();
|
|
1115
|
+
}
|
|
1116
|
+
if (this.toolbarEl) this.toolbarEl.style.display = "";
|
|
1117
|
+
if (this.panelEl) this.panelEl.style.display = "";
|
|
1118
|
+
this._syncToolbar();
|
|
1119
|
+
this._syncStylePanel();
|
|
1120
|
+
}
|
|
1121
|
+
this.render({ scene: false, overlay: true });
|
|
1122
|
+
return this;
|
|
1123
|
+
}
|
|
1124
|
+
setSnap(snap) {
|
|
1125
|
+
this.snap = Boolean(snap);
|
|
1126
|
+
this.options.snap = this.snap;
|
|
1127
|
+
return this;
|
|
1128
|
+
}
|
|
1129
|
+
setHistoryEnabled(enabled) {
|
|
1130
|
+
this.historyEnabled = Boolean(enabled);
|
|
1131
|
+
this.options.history = this.historyEnabled;
|
|
1132
|
+
if (!this.historyEnabled) {
|
|
1133
|
+
this.clearHistory();
|
|
1134
|
+
}
|
|
1135
|
+
return this;
|
|
1136
|
+
}
|
|
1137
|
+
setHistoryLimit(limit) {
|
|
1138
|
+
const num = Number(limit);
|
|
1139
|
+
this.historyLimit = Number.isFinite(num) && num > 0 ? num : 100;
|
|
1140
|
+
this.options.historyLimit = this.historyLimit;
|
|
1141
|
+
if (this.history.length > this.historyLimit + 1) {
|
|
1142
|
+
const drop = this.history.length - (this.historyLimit + 1);
|
|
1143
|
+
this.history.splice(0, drop);
|
|
1144
|
+
this.historyIndex = Math.max(0, this.historyIndex - drop);
|
|
1145
|
+
}
|
|
1146
|
+
return this;
|
|
1147
|
+
}
|
|
1009
1148
|
// ── Tool + current style ─────────────────────────────────────────────────
|
|
1010
1149
|
setTool(tool) {
|
|
1011
1150
|
if (!DRAW_TOOLS.includes(tool)) return this;
|
|
1012
1151
|
this.tool = tool;
|
|
1152
|
+
if (this.canvasEl) this.canvasEl.setAttribute("data-tool", tool);
|
|
1013
1153
|
this._syncToolbar();
|
|
1014
1154
|
return this;
|
|
1015
1155
|
}
|
|
@@ -1043,7 +1183,7 @@ var VdDraw = class {
|
|
|
1043
1183
|
}
|
|
1044
1184
|
// ── Shape CRUD ─────────────────────────────────────────────────────────
|
|
1045
1185
|
getShape(id) {
|
|
1046
|
-
return this.
|
|
1186
|
+
return this._shapesById.get(id) || null;
|
|
1047
1187
|
}
|
|
1048
1188
|
getShapes() {
|
|
1049
1189
|
return this.documentData.shapes.map((s) => deepClone(s));
|
|
@@ -1095,6 +1235,8 @@ var VdDraw = class {
|
|
|
1095
1235
|
shape.text = typeof partial.text === "string" ? partial.text : "";
|
|
1096
1236
|
}
|
|
1097
1237
|
this.documentData.shapes.push(shape);
|
|
1238
|
+
this._shapesById.set(shape.id, shape);
|
|
1239
|
+
this._markDirty(shape.id);
|
|
1098
1240
|
this.render();
|
|
1099
1241
|
this._emitChange("shape:add", { shape: deepClone(shape), shapeId: shape.id });
|
|
1100
1242
|
return shape;
|
|
@@ -1108,6 +1250,8 @@ var VdDraw = class {
|
|
|
1108
1250
|
const shape = this.getShape(id);
|
|
1109
1251
|
if (!shape || !patch) return null;
|
|
1110
1252
|
Object.assign(shape, patch);
|
|
1253
|
+
this._shapesById.set(id, shape);
|
|
1254
|
+
this._markDirty(id);
|
|
1111
1255
|
this.render();
|
|
1112
1256
|
this._emitChange(options.reason || "shape:update", { shapeId: id }, options.reason ? id : null);
|
|
1113
1257
|
return shape;
|
|
@@ -1116,7 +1260,9 @@ var VdDraw = class {
|
|
|
1116
1260
|
const idx = this.documentData.shapes.findIndex((s) => s.id === id);
|
|
1117
1261
|
if (idx === -1) return false;
|
|
1118
1262
|
this.documentData.shapes.splice(idx, 1);
|
|
1263
|
+
this._shapesById.delete(id);
|
|
1119
1264
|
this.selectedIds.delete(id);
|
|
1265
|
+
this._markDirty(id);
|
|
1120
1266
|
this.render();
|
|
1121
1267
|
this._emitChange("shape:delete", { shapeId: id });
|
|
1122
1268
|
return true;
|
|
@@ -1183,11 +1329,13 @@ var VdDraw = class {
|
|
|
1183
1329
|
_replaceShape(next) {
|
|
1184
1330
|
const idx = this.documentData.shapes.findIndex((s) => s.id === next.id);
|
|
1185
1331
|
if (idx !== -1) this.documentData.shapes[idx] = next;
|
|
1332
|
+
this._shapesById.set(next.id, next);
|
|
1186
1333
|
}
|
|
1187
1334
|
nudge(dx, dy) {
|
|
1188
1335
|
const selected = this.getSelectedShapes();
|
|
1189
1336
|
if (!selected.length) return this;
|
|
1190
1337
|
for (const shape of selected) this._replaceShape(translateShape(shape, dx, dy));
|
|
1338
|
+
this._markAllDirty();
|
|
1191
1339
|
this.render();
|
|
1192
1340
|
this._emitChange(
|
|
1193
1341
|
"shape:nudge",
|
|
@@ -1206,6 +1354,7 @@ var VdDraw = class {
|
|
|
1206
1354
|
const scaled = scaleShape(shape, cur.x, cur.y, sx, sy);
|
|
1207
1355
|
this._replaceShape(translateShape(scaled, target.x - cur.x, target.y - cur.y));
|
|
1208
1356
|
}
|
|
1357
|
+
this._markAllDirty();
|
|
1209
1358
|
this.render();
|
|
1210
1359
|
this._emitChange("shape:resize", { shapeIds: [...this.selectedIds] });
|
|
1211
1360
|
return this;
|
|
@@ -1213,8 +1362,10 @@ var VdDraw = class {
|
|
|
1213
1362
|
deleteSelection() {
|
|
1214
1363
|
if (this.selectedIds.size === 0) return false;
|
|
1215
1364
|
const ids = [...this.selectedIds];
|
|
1365
|
+
for (const id of ids) this._shapesById.delete(id);
|
|
1216
1366
|
this.documentData.shapes = this.documentData.shapes.filter((s) => !this.selectedIds.has(s.id));
|
|
1217
1367
|
this.selectedIds.clear();
|
|
1368
|
+
this._markAllDirty();
|
|
1218
1369
|
this.render();
|
|
1219
1370
|
this._emitChange("shape:delete", { shapeIds: ids });
|
|
1220
1371
|
return true;
|
|
@@ -1226,6 +1377,7 @@ var VdDraw = class {
|
|
|
1226
1377
|
for (const shape of selected) {
|
|
1227
1378
|
for (const key of allowed) if (key in patch) shape[key] = patch[key];
|
|
1228
1379
|
}
|
|
1380
|
+
this._markAllDirty();
|
|
1229
1381
|
this.render();
|
|
1230
1382
|
this._emitChange(
|
|
1231
1383
|
"shape:style",
|
|
@@ -1238,6 +1390,7 @@ var VdDraw = class {
|
|
|
1238
1390
|
_reorder(mutator) {
|
|
1239
1391
|
if (this.selectedIds.size === 0) return this;
|
|
1240
1392
|
mutator();
|
|
1393
|
+
this._markAllDirty();
|
|
1241
1394
|
this.render();
|
|
1242
1395
|
this._emitChange("shape:reorder", { shapeIds: [...this.selectedIds] });
|
|
1243
1396
|
return this;
|
|
@@ -1280,6 +1433,7 @@ var VdDraw = class {
|
|
|
1280
1433
|
if (selected.length < 2) return this;
|
|
1281
1434
|
const groupId = createId("grp");
|
|
1282
1435
|
for (const shape of selected) shape.groupId = groupId;
|
|
1436
|
+
this._markAllDirty();
|
|
1283
1437
|
this.render();
|
|
1284
1438
|
this._emitChange("shape:group", { groupId, shapeIds: [...this.selectedIds] });
|
|
1285
1439
|
return this;
|
|
@@ -1294,6 +1448,7 @@ var VdDraw = class {
|
|
|
1294
1448
|
}
|
|
1295
1449
|
}
|
|
1296
1450
|
if (!changed) return this;
|
|
1451
|
+
this._markAllDirty();
|
|
1297
1452
|
this.render();
|
|
1298
1453
|
this._emitChange("shape:ungroup", { shapeIds: [...this.selectedIds] });
|
|
1299
1454
|
return this;
|
|
@@ -1320,9 +1475,11 @@ var VdDraw = class {
|
|
|
1320
1475
|
copy.groupId = groupRemap.get(copy.groupId);
|
|
1321
1476
|
}
|
|
1322
1477
|
this.documentData.shapes.push(copy);
|
|
1478
|
+
this._shapesById.set(copy.id, copy);
|
|
1323
1479
|
newIds.push(copy.id);
|
|
1324
1480
|
}
|
|
1325
1481
|
this.selectedIds = new Set(newIds);
|
|
1482
|
+
this._markAllDirty();
|
|
1326
1483
|
this.render();
|
|
1327
1484
|
this._emitChange("shape:paste", { shapeIds: newIds });
|
|
1328
1485
|
this._emitSelect();
|
|
@@ -1384,6 +1541,30 @@ var VdDraw = class {
|
|
|
1384
1541
|
sticky: read("--vd-draw-sticky-fill", "#fdf3c4")
|
|
1385
1542
|
};
|
|
1386
1543
|
}
|
|
1544
|
+
_getArrowMarkerId(color, defsTarget = this.defsEl) {
|
|
1545
|
+
const isDefault = !color;
|
|
1546
|
+
const safeKey = isDefault ? "arrow" : "arrow-" + color.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
|
|
1547
|
+
const id = this._svgId(safeKey);
|
|
1548
|
+
if (defsTarget && !defsTarget.querySelector(`#${id}`)) {
|
|
1549
|
+
const marker = createSvgEl("marker", {
|
|
1550
|
+
id,
|
|
1551
|
+
viewBox: "0 0 10 10",
|
|
1552
|
+
refX: 8,
|
|
1553
|
+
refY: 5,
|
|
1554
|
+
markerWidth: 7,
|
|
1555
|
+
markerHeight: 7,
|
|
1556
|
+
orient: "auto-start-reverse"
|
|
1557
|
+
});
|
|
1558
|
+
marker.appendChild(
|
|
1559
|
+
createSvgEl("path", {
|
|
1560
|
+
d: "M 0 0 L 10 5 L 0 10 z",
|
|
1561
|
+
fill: color || "var(--vd-draw-shape-stroke)"
|
|
1562
|
+
})
|
|
1563
|
+
);
|
|
1564
|
+
defsTarget.appendChild(marker);
|
|
1565
|
+
}
|
|
1566
|
+
return id;
|
|
1567
|
+
}
|
|
1387
1568
|
toSVG() {
|
|
1388
1569
|
const shapes = this.documentData.shapes;
|
|
1389
1570
|
const bounds = boundsOfShapes(shapes) || { x: 0, y: 0, w: 100, h: 100 };
|
|
@@ -1401,10 +1582,14 @@ var VdDraw = class {
|
|
|
1401
1582
|
height: round(vb.h),
|
|
1402
1583
|
viewBox: `${round(vb.x)} ${round(vb.y)} ${round(vb.w)} ${round(vb.h)}`
|
|
1403
1584
|
});
|
|
1585
|
+
const defs = createSvgEl("defs");
|
|
1404
1586
|
for (const shape of shapes) {
|
|
1405
|
-
const el = this._renderShapeEl(shape, { standalone: true, colors });
|
|
1587
|
+
const el = this._renderShapeEl(shape, { standalone: true, colors, defsTarget: defs });
|
|
1406
1588
|
if (el) svg.appendChild(el);
|
|
1407
1589
|
}
|
|
1590
|
+
if (defs.childNodes.length > 0) {
|
|
1591
|
+
svg.insertBefore(defs, svg.firstChild);
|
|
1592
|
+
}
|
|
1408
1593
|
return new XMLSerializer().serializeToString(svg);
|
|
1409
1594
|
}
|
|
1410
1595
|
toPNG({ scale = 2 } = {}) {
|
|
@@ -1498,7 +1683,9 @@ var VdDraw = class {
|
|
|
1498
1683
|
const viewport = { ...this.documentData.viewport };
|
|
1499
1684
|
this.documentData = deepClone(snapshot);
|
|
1500
1685
|
this.documentData.viewport = viewport;
|
|
1686
|
+
this._rebuildShapeIndex();
|
|
1501
1687
|
this._syncSelectionValidity();
|
|
1688
|
+
this._markAllDirty();
|
|
1502
1689
|
this.render();
|
|
1503
1690
|
this.emit("change", { reason, document: this.toJSON() });
|
|
1504
1691
|
this.emit("history", { reason, canUndo: this.canUndo(), canRedo: this.canRedo() });
|
|
@@ -1514,8 +1701,10 @@ var VdDraw = class {
|
|
|
1514
1701
|
}
|
|
1515
1702
|
load(data) {
|
|
1516
1703
|
this.documentData = normalizeDocument(data);
|
|
1704
|
+
this._rebuildShapeIndex();
|
|
1517
1705
|
this.selectedIds.clear();
|
|
1518
1706
|
this._resetHistory();
|
|
1707
|
+
this._markAllDirty();
|
|
1519
1708
|
this.render();
|
|
1520
1709
|
this._emitChange("load");
|
|
1521
1710
|
this._emitSelect();
|
|
@@ -1524,7 +1713,9 @@ var VdDraw = class {
|
|
|
1524
1713
|
clear() {
|
|
1525
1714
|
if (!this.documentData.shapes.length) return this;
|
|
1526
1715
|
this.documentData.shapes = [];
|
|
1716
|
+
this._shapesById.clear();
|
|
1527
1717
|
this.selectedIds.clear();
|
|
1718
|
+
this._markAllDirty();
|
|
1528
1719
|
this.render();
|
|
1529
1720
|
this._emitChange("clear");
|
|
1530
1721
|
return this;
|
|
@@ -1549,10 +1740,16 @@ var VdDraw = class {
|
|
|
1549
1740
|
}
|
|
1550
1741
|
_positionTextEditor(shape, editor) {
|
|
1551
1742
|
const vp = this.documentData.viewport;
|
|
1552
|
-
|
|
1553
|
-
editor.style.
|
|
1554
|
-
editor.style.
|
|
1555
|
-
editor.style.
|
|
1743
|
+
const scale = vp.scale || 1;
|
|
1744
|
+
editor.style.left = `${vp.x + shape.x * scale}px`;
|
|
1745
|
+
editor.style.top = `${vp.y + shape.y * scale}px`;
|
|
1746
|
+
editor.style.width = `${shape.w * scale}px`;
|
|
1747
|
+
editor.style.height = `${shape.h * scale}px`;
|
|
1748
|
+
editor.style.fontSize = `${16 * scale}px`;
|
|
1749
|
+
editor.style.lineHeight = `${20 * scale}px`;
|
|
1750
|
+
if (shape.type === "sticky") {
|
|
1751
|
+
editor.style.background = shape.fill || "var(--vd-draw-sticky-fill)";
|
|
1752
|
+
}
|
|
1556
1753
|
}
|
|
1557
1754
|
stopTextEdit({ commit = true } = {}) {
|
|
1558
1755
|
if (!this.textEditor) return;
|
|
@@ -1564,6 +1761,7 @@ var VdDraw = class {
|
|
|
1564
1761
|
const shape = this.getShape(id);
|
|
1565
1762
|
if (shape && shape.text !== value) {
|
|
1566
1763
|
shape.text = value;
|
|
1764
|
+
this._markDirty(id);
|
|
1567
1765
|
this.render();
|
|
1568
1766
|
this._emitChange("shape:text", { shapeId: id }, `text:${id}`);
|
|
1569
1767
|
}
|
|
@@ -1621,11 +1819,65 @@ var VdDraw = class {
|
|
|
1621
1819
|
if (this.destroyed || event.button != null && event.button !== 0) return;
|
|
1622
1820
|
this.canvasEl.focus();
|
|
1623
1821
|
this.stopTextEdit({ commit: true });
|
|
1822
|
+
this._activePointers.set(event.pointerId, { clientX: event.clientX, clientY: event.clientY });
|
|
1823
|
+
if (this._activePointers.size === 2) {
|
|
1824
|
+
if (this.interaction) {
|
|
1825
|
+
const it = this.interaction;
|
|
1826
|
+
if (it.kind === "freehand" || it.kind === "create-line" || it.kind === "create-box") {
|
|
1827
|
+
this.documentData.shapes = this.documentData.shapes.filter((s) => s.id !== it.shapeId);
|
|
1828
|
+
this._shapesById.delete(it.shapeId);
|
|
1829
|
+
const el = this._shapeElements.get(it.shapeId);
|
|
1830
|
+
if (el) {
|
|
1831
|
+
el.remove();
|
|
1832
|
+
this._shapeElements.delete(it.shapeId);
|
|
1833
|
+
}
|
|
1834
|
+
this._scheduleRender({ scene: true, overlay: false });
|
|
1835
|
+
} else if (it.kind === "move" || it.kind === "resize") {
|
|
1836
|
+
for (const orig of it.originals) {
|
|
1837
|
+
this._replaceShape(orig);
|
|
1838
|
+
this._markDirty(orig.id);
|
|
1839
|
+
}
|
|
1840
|
+
if (it.kind === "move") this._renderGuides([]);
|
|
1841
|
+
this._scheduleRender({ scene: true });
|
|
1842
|
+
} else if (it.kind === "erase") {
|
|
1843
|
+
for (const id of it.erased) this._markDirty(id);
|
|
1844
|
+
this._scheduleRender({ scene: true, overlay: false });
|
|
1845
|
+
} else if (it.kind === "pan") {
|
|
1846
|
+
if (this.canvasEl) this.canvasEl.classList.remove("vd-draw-panning");
|
|
1847
|
+
} else if (it.kind === "marquee") {
|
|
1848
|
+
clearChildren(this.marqueeLayer);
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
const pts = [...this._activePointers.values()];
|
|
1852
|
+
const p1 = pts[0];
|
|
1853
|
+
const p2 = pts[1];
|
|
1854
|
+
const initialDistance = Math.hypot(p2.clientX - p1.clientX, p2.clientY - p1.clientY) || 1;
|
|
1855
|
+
const initialClientMid = {
|
|
1856
|
+
x: (p1.clientX + p2.clientX) / 2,
|
|
1857
|
+
y: (p1.clientY + p2.clientY) / 2
|
|
1858
|
+
};
|
|
1859
|
+
const initialLocalMid = this._clientToLocal(initialClientMid.x, initialClientMid.y);
|
|
1860
|
+
const initialVp = { ...this.documentData.viewport };
|
|
1861
|
+
const currentScale = initialVp.scale || 1;
|
|
1862
|
+
const worldMid = {
|
|
1863
|
+
x: (initialLocalMid.x - initialVp.x) / currentScale,
|
|
1864
|
+
y: (initialLocalMid.y - initialVp.y) / currentScale
|
|
1865
|
+
};
|
|
1866
|
+
this.interaction = {
|
|
1867
|
+
kind: "pinch",
|
|
1868
|
+
initialDistance,
|
|
1869
|
+
worldMid,
|
|
1870
|
+
initialScale: currentScale
|
|
1871
|
+
};
|
|
1872
|
+
if (typeof event.preventDefault === "function") event.preventDefault();
|
|
1873
|
+
return;
|
|
1874
|
+
}
|
|
1624
1875
|
const world = this._clientToWorld(event.clientX, event.clientY);
|
|
1625
1876
|
const shapeTarget = event.target.closest("[data-shape-id]");
|
|
1626
1877
|
const handleTarget = event.target.closest("[data-handle]");
|
|
1627
1878
|
if (this.tool === "hand") {
|
|
1628
1879
|
this.interaction = this._beginPan(event);
|
|
1880
|
+
if (this.canvasEl) this.canvasEl.classList.add("vd-draw-panning");
|
|
1629
1881
|
this._capture(event.pointerId);
|
|
1630
1882
|
return;
|
|
1631
1883
|
}
|
|
@@ -1636,6 +1888,15 @@ var VdDraw = class {
|
|
|
1636
1888
|
return;
|
|
1637
1889
|
}
|
|
1638
1890
|
if (this.tool === "select") {
|
|
1891
|
+
if (event.detail === 2 && shapeTarget && !this.readonly) {
|
|
1892
|
+
const id = shapeTarget.getAttribute("data-shape-id");
|
|
1893
|
+
const shape = this.getShape(id);
|
|
1894
|
+
if (shape && (shape.type === "text" || shape.type === "sticky")) {
|
|
1895
|
+
this.select(id);
|
|
1896
|
+
this.startTextEdit(id);
|
|
1897
|
+
return;
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1639
1900
|
if (handleTarget && this.selectedIds.size) {
|
|
1640
1901
|
this.interaction = {
|
|
1641
1902
|
kind: "resize",
|
|
@@ -1699,6 +1960,7 @@ var VdDraw = class {
|
|
|
1699
1960
|
points: [[round(world.x), round(world.y), pressure]]
|
|
1700
1961
|
};
|
|
1701
1962
|
this.documentData.shapes.push(shape2);
|
|
1963
|
+
this._shapesById.set(shape2.id, shape2);
|
|
1702
1964
|
return { kind: "freehand", pointerId: event.pointerId, shapeId: shape2.id };
|
|
1703
1965
|
}
|
|
1704
1966
|
if (tool === "line") {
|
|
@@ -1716,6 +1978,7 @@ var VdDraw = class {
|
|
|
1716
1978
|
opacity: this.style.opacity
|
|
1717
1979
|
};
|
|
1718
1980
|
this.documentData.shapes.push(shape2);
|
|
1981
|
+
this._shapesById.set(shape2.id, shape2);
|
|
1719
1982
|
return { kind: "create-line", pointerId: event.pointerId, shapeId: shape2.id };
|
|
1720
1983
|
}
|
|
1721
1984
|
const type = tool;
|
|
@@ -1733,6 +1996,7 @@ var VdDraw = class {
|
|
|
1733
1996
|
};
|
|
1734
1997
|
if (type === "text" || type === "sticky") shape.text = "";
|
|
1735
1998
|
this.documentData.shapes.push(shape);
|
|
1999
|
+
this._shapesById.set(shape.id, shape);
|
|
1736
2000
|
return { kind: "create-box", pointerId: event.pointerId, shapeId: shape.id, start: world };
|
|
1737
2001
|
}
|
|
1738
2002
|
_applyErase(world) {
|
|
@@ -1747,16 +2011,44 @@ var VdDraw = class {
|
|
|
1747
2011
|
changed = true;
|
|
1748
2012
|
}
|
|
1749
2013
|
}
|
|
1750
|
-
if (changed)
|
|
2014
|
+
if (changed) {
|
|
2015
|
+
for (const id of it.erased) this._markDirty(id);
|
|
2016
|
+
this._scheduleRender({ scene: true, overlay: false });
|
|
2017
|
+
}
|
|
1751
2018
|
}
|
|
1752
2019
|
_handlePointerMove(event) {
|
|
2020
|
+
if (this._activePointers.has(event.pointerId)) {
|
|
2021
|
+
this._activePointers.set(event.pointerId, { clientX: event.clientX, clientY: event.clientY });
|
|
2022
|
+
}
|
|
1753
2023
|
const it = this.interaction;
|
|
1754
2024
|
if (!it) return;
|
|
2025
|
+
if (it.kind === "pinch") {
|
|
2026
|
+
if (this._activePointers.size >= 2) {
|
|
2027
|
+
if (typeof event.preventDefault === "function") event.preventDefault();
|
|
2028
|
+
const pts = [...this._activePointers.values()];
|
|
2029
|
+
const p1 = pts[0];
|
|
2030
|
+
const p2 = pts[1];
|
|
2031
|
+
const curDistance = Math.hypot(p2.clientX - p1.clientX, p2.clientY - p1.clientY) || 1;
|
|
2032
|
+
const curClientMid = {
|
|
2033
|
+
x: (p1.clientX + p2.clientX) / 2,
|
|
2034
|
+
y: (p1.clientY + p2.clientY) / 2
|
|
2035
|
+
};
|
|
2036
|
+
const curLocalMid = this._clientToLocal(curClientMid.x, curClientMid.y);
|
|
2037
|
+
const distRatio = curDistance / it.initialDistance;
|
|
2038
|
+
const newScale = clamp(it.initialScale * distRatio, MIN_SCALE, MAX_SCALE);
|
|
2039
|
+
const vp = this.documentData.viewport;
|
|
2040
|
+
vp.scale = newScale;
|
|
2041
|
+
vp.x = curLocalMid.x - it.worldMid.x * newScale;
|
|
2042
|
+
vp.y = curLocalMid.y - it.worldMid.y * newScale;
|
|
2043
|
+
this._scheduleRender({ scene: false });
|
|
2044
|
+
}
|
|
2045
|
+
return;
|
|
2046
|
+
}
|
|
1755
2047
|
if (it.kind === "pan") {
|
|
1756
2048
|
const vp = this.documentData.viewport;
|
|
1757
2049
|
vp.x = it.startX + (event.clientX - it.startClientX);
|
|
1758
2050
|
vp.y = it.startY + (event.clientY - it.startClientY);
|
|
1759
|
-
this.
|
|
2051
|
+
this._scheduleRender({ scene: false });
|
|
1760
2052
|
return;
|
|
1761
2053
|
}
|
|
1762
2054
|
const world = this._clientToWorld(event.clientX, event.clientY);
|
|
@@ -1774,8 +2066,9 @@ var VdDraw = class {
|
|
|
1774
2066
|
if (snap.dx || snap.dy)
|
|
1775
2067
|
for (const orig of it.originals)
|
|
1776
2068
|
this._replaceShape(translateShape(orig, dx + snap.dx, dy + snap.dy));
|
|
2069
|
+
for (const orig of it.originals) this._markDirty(orig.id);
|
|
1777
2070
|
this._renderGuides(snap.guides);
|
|
1778
|
-
this.
|
|
2071
|
+
this._scheduleRender({ scene: true, guides: false });
|
|
1779
2072
|
return;
|
|
1780
2073
|
}
|
|
1781
2074
|
if (it.kind === "resize") {
|
|
@@ -1790,9 +2083,10 @@ var VdDraw = class {
|
|
|
1790
2083
|
for (const orig of it.originals) {
|
|
1791
2084
|
const scaled = scaleShape(orig, it.startBounds.x, it.startBounds.y, sx, sy);
|
|
1792
2085
|
this._replaceShape(translateShape(scaled, t.x - it.startBounds.x, t.y - it.startBounds.y));
|
|
2086
|
+
this._markDirty(orig.id);
|
|
1793
2087
|
}
|
|
1794
2088
|
it.moved = true;
|
|
1795
|
-
this.
|
|
2089
|
+
this._scheduleRender();
|
|
1796
2090
|
return;
|
|
1797
2091
|
}
|
|
1798
2092
|
if (it.kind === "marquee") {
|
|
@@ -1803,8 +2097,14 @@ var VdDraw = class {
|
|
|
1803
2097
|
if (it.kind === "freehand") {
|
|
1804
2098
|
const shape = this.getShape(it.shapeId);
|
|
1805
2099
|
if (shape) {
|
|
1806
|
-
|
|
1807
|
-
|
|
2100
|
+
const raw = [round(world.x), round(world.y), event.pressure || 0.5];
|
|
2101
|
+
const preset = BRUSH_PRESETS[shape.brush] || BRUSH_PRESETS[DEFAULT_BRUSH];
|
|
2102
|
+
const smoothFactor = 1 - (preset.smoothing || 0.5) * 0.6;
|
|
2103
|
+
const prev = shape.points[shape.points.length - 1];
|
|
2104
|
+
const pt = prev ? smoothPoint(prev, raw, smoothFactor) : raw;
|
|
2105
|
+
appendAndSimplify(shape.points, pt);
|
|
2106
|
+
this._markDirty(it.shapeId);
|
|
2107
|
+
this._scheduleRender({ scene: true });
|
|
1808
2108
|
}
|
|
1809
2109
|
return;
|
|
1810
2110
|
}
|
|
@@ -1812,7 +2112,8 @@ var VdDraw = class {
|
|
|
1812
2112
|
const shape = this.getShape(it.shapeId);
|
|
1813
2113
|
if (shape) {
|
|
1814
2114
|
shape.points[1] = [round(world.x), round(world.y)];
|
|
1815
|
-
this.
|
|
2115
|
+
this._markDirty(it.shapeId);
|
|
2116
|
+
this._scheduleRender({ scene: true });
|
|
1816
2117
|
}
|
|
1817
2118
|
return;
|
|
1818
2119
|
}
|
|
@@ -1823,7 +2124,8 @@ var VdDraw = class {
|
|
|
1823
2124
|
shape.y = round(Math.min(it.start.y, world.y));
|
|
1824
2125
|
shape.w = round(Math.max(1, Math.abs(world.x - it.start.x)));
|
|
1825
2126
|
shape.h = round(Math.max(1, Math.abs(world.y - it.start.y)));
|
|
1826
|
-
this.
|
|
2127
|
+
this._markDirty(it.shapeId);
|
|
2128
|
+
this._scheduleRender({ scene: true });
|
|
1827
2129
|
}
|
|
1828
2130
|
}
|
|
1829
2131
|
}
|
|
@@ -1842,9 +2144,19 @@ var VdDraw = class {
|
|
|
1842
2144
|
return { x, y, w, h: h2 };
|
|
1843
2145
|
}
|
|
1844
2146
|
_handlePointerUp(event) {
|
|
2147
|
+
this._activePointers.delete(event.pointerId);
|
|
1845
2148
|
const it = this.interaction;
|
|
1846
2149
|
if (!it) return;
|
|
2150
|
+
if (it.kind === "pinch") {
|
|
2151
|
+
if (this._activePointers.size < 2) {
|
|
2152
|
+
this.interaction = null;
|
|
2153
|
+
if (this.canvasEl) this.canvasEl.classList.remove("vd-draw-panning");
|
|
2154
|
+
this._emitViewportChange("viewport:pinch");
|
|
2155
|
+
}
|
|
2156
|
+
return;
|
|
2157
|
+
}
|
|
1847
2158
|
this.interaction = null;
|
|
2159
|
+
if (this.canvasEl) this.canvasEl.classList.remove("vd-draw-panning");
|
|
1848
2160
|
if (typeof this.canvasEl.releasePointerCapture === "function" && this.canvasEl.hasPointerCapture?.(event.pointerId)) {
|
|
1849
2161
|
try {
|
|
1850
2162
|
this.canvasEl.releasePointerCapture(event.pointerId);
|
|
@@ -1860,18 +2172,22 @@ var VdDraw = class {
|
|
|
1860
2172
|
if (it.kind === "erase") {
|
|
1861
2173
|
if (it.erased.size) {
|
|
1862
2174
|
const ids = [...it.erased];
|
|
2175
|
+
for (const id of ids) this._shapesById.delete(id);
|
|
1863
2176
|
this.documentData.shapes = this.documentData.shapes.filter((s) => !it.erased.has(s.id));
|
|
2177
|
+
this._markAllDirty();
|
|
1864
2178
|
this.render();
|
|
1865
2179
|
this._emitChange("shape:erase", { shapeIds: ids });
|
|
1866
2180
|
}
|
|
1867
2181
|
return;
|
|
1868
2182
|
}
|
|
1869
2183
|
if (it.kind === "move" && it.moved) {
|
|
2184
|
+
this._markAllDirty();
|
|
1870
2185
|
this.render();
|
|
1871
2186
|
this._emitChange("shape:move", { shapeIds: [...this.selectedIds] });
|
|
1872
2187
|
return;
|
|
1873
2188
|
}
|
|
1874
2189
|
if (it.kind === "resize" && it.moved) {
|
|
2190
|
+
this._markAllDirty();
|
|
1875
2191
|
this.render();
|
|
1876
2192
|
this._emitChange("shape:resize", { shapeIds: [...this.selectedIds] });
|
|
1877
2193
|
return;
|
|
@@ -1888,6 +2204,8 @@ var VdDraw = class {
|
|
|
1888
2204
|
const b = shapeBounds(shape);
|
|
1889
2205
|
if (it.kind !== "freehand" && b.w < 2 && b.h < 2 && shape.type !== "text" && shape.type !== "sticky") {
|
|
1890
2206
|
this.documentData.shapes = this.documentData.shapes.filter((s) => s.id !== it.shapeId);
|
|
2207
|
+
this._shapesById.delete(it.shapeId);
|
|
2208
|
+
this._markAllDirty();
|
|
1891
2209
|
this.render();
|
|
1892
2210
|
return;
|
|
1893
2211
|
}
|
|
@@ -1901,6 +2219,7 @@ var VdDraw = class {
|
|
|
1901
2219
|
this.select(shape.id);
|
|
1902
2220
|
this.setTool("select");
|
|
1903
2221
|
}
|
|
2222
|
+
this._markDirty(shape.id);
|
|
1904
2223
|
this.render();
|
|
1905
2224
|
this._emitChange("shape:add", { shape: deepClone(shape), shapeId: shape.id });
|
|
1906
2225
|
if (shape.type === "text" || shape.type === "sticky") this.startTextEdit(shape.id);
|
|
@@ -1977,26 +2296,93 @@ var VdDraw = class {
|
|
|
1977
2296
|
if (this.destroyed) return;
|
|
1978
2297
|
this.render({ scene: false });
|
|
1979
2298
|
}
|
|
2299
|
+
// ── Dirty tracking ──────────────────────────────────────────────────────
|
|
2300
|
+
_markDirty(shapeId) {
|
|
2301
|
+
this._dirtyShapes.add(shapeId);
|
|
2302
|
+
}
|
|
2303
|
+
_markAllDirty() {
|
|
2304
|
+
this._allDirty = true;
|
|
2305
|
+
}
|
|
2306
|
+
// ── rAF-batched render ─────────────────────────────────────────────────
|
|
2307
|
+
_scheduleRender(flags = {}) {
|
|
2308
|
+
this._pendingFlags = {
|
|
2309
|
+
scene: (this._pendingFlags?.scene ?? false) || flags.scene !== false,
|
|
2310
|
+
overlay: (this._pendingFlags?.overlay ?? false) || flags.overlay !== false
|
|
2311
|
+
};
|
|
2312
|
+
if (this._rafId != null) return;
|
|
2313
|
+
this._rafId = (typeof requestAnimationFrame === "function" ? requestAnimationFrame : setTimeout)(() => {
|
|
2314
|
+
this._rafId = null;
|
|
2315
|
+
const f = this._pendingFlags || {};
|
|
2316
|
+
this._pendingFlags = null;
|
|
2317
|
+
this.render(f);
|
|
2318
|
+
});
|
|
2319
|
+
}
|
|
1980
2320
|
// ── Rendering ────────────────────────────────────────────────────────────
|
|
1981
2321
|
render(flags = {}) {
|
|
1982
2322
|
if (this.destroyed) return;
|
|
1983
2323
|
const { scene = true, overlay = true } = flags;
|
|
1984
2324
|
const vp = this.documentData.viewport;
|
|
1985
2325
|
this.world.setAttribute("transform", `matrix(${vp.scale} 0 0 ${vp.scale} ${vp.x} ${vp.y})`);
|
|
2326
|
+
if (this.textEditor) {
|
|
2327
|
+
const activeShape = this.getShape(this.textEditor.id);
|
|
2328
|
+
if (activeShape) this._positionTextEditor(activeShape, this.textEditor.el);
|
|
2329
|
+
}
|
|
1986
2330
|
if (scene) {
|
|
1987
|
-
|
|
1988
|
-
const erasing = this.interaction && this.interaction.kind === "erase" ? this.interaction.erased : null;
|
|
1989
|
-
for (const shape of this.documentData.shapes) {
|
|
1990
|
-
if (erasing && erasing.has(shape.id)) continue;
|
|
1991
|
-
const el = this._renderShapeEl(shape, {});
|
|
1992
|
-
if (el) this.shapesLayer.appendChild(el);
|
|
1993
|
-
}
|
|
2331
|
+
this._renderSceneIncremental();
|
|
1994
2332
|
}
|
|
1995
2333
|
if (overlay) this._renderOverlay();
|
|
1996
2334
|
}
|
|
2335
|
+
_renderSceneIncremental() {
|
|
2336
|
+
const shapes = this.documentData.shapes;
|
|
2337
|
+
const erasing = this.interaction && this.interaction.kind === "erase" ? this.interaction.erased : null;
|
|
2338
|
+
const currentIds = /* @__PURE__ */ new Set();
|
|
2339
|
+
for (const shape of shapes) {
|
|
2340
|
+
if (erasing && erasing.has(shape.id)) continue;
|
|
2341
|
+
currentIds.add(shape.id);
|
|
2342
|
+
}
|
|
2343
|
+
for (const [id, el] of this._shapeElements) {
|
|
2344
|
+
if (!currentIds.has(id)) {
|
|
2345
|
+
el.remove();
|
|
2346
|
+
this._shapeElements.delete(id);
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
const fullRebuild = this._allDirty;
|
|
2350
|
+
let prevEl = null;
|
|
2351
|
+
for (const shape of shapes) {
|
|
2352
|
+
if (!currentIds.has(shape.id)) continue;
|
|
2353
|
+
const needsUpdate = fullRebuild || this._dirtyShapes.has(shape.id);
|
|
2354
|
+
let el = this._shapeElements.get(shape.id);
|
|
2355
|
+
if (needsUpdate || !el) {
|
|
2356
|
+
const newEl = this._renderShapeEl(shape, {});
|
|
2357
|
+
if (!newEl) {
|
|
2358
|
+
if (el) {
|
|
2359
|
+
el.remove();
|
|
2360
|
+
this._shapeElements.delete(shape.id);
|
|
2361
|
+
}
|
|
2362
|
+
continue;
|
|
2363
|
+
}
|
|
2364
|
+
if (el) {
|
|
2365
|
+
el.replaceWith(newEl);
|
|
2366
|
+
} else {
|
|
2367
|
+
if (prevEl && prevEl.parentNode === this.shapesLayer) {
|
|
2368
|
+
prevEl.after(newEl);
|
|
2369
|
+
} else {
|
|
2370
|
+
this.shapesLayer.prepend(newEl);
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
el = newEl;
|
|
2374
|
+
this._shapeElements.set(shape.id, el);
|
|
2375
|
+
}
|
|
2376
|
+
const expected = prevEl ? prevEl.nextElementSibling : this.shapesLayer.firstElementChild;
|
|
2377
|
+
if (el !== expected) this.shapesLayer.insertBefore(el, expected);
|
|
2378
|
+
prevEl = el;
|
|
2379
|
+
}
|
|
2380
|
+
this._dirtyShapes.clear();
|
|
2381
|
+
this._allDirty = false;
|
|
2382
|
+
}
|
|
1997
2383
|
// Colors are applied via inline `style` (which wins over the CSS class rules
|
|
1998
2384
|
// and serializes self-contained), so a picked color always renders.
|
|
1999
|
-
_renderShapeEl(shape, { standalone = false, colors = null }) {
|
|
2385
|
+
_renderShapeEl(shape, { standalone = false, colors = null, defsTarget = null } = {}) {
|
|
2000
2386
|
let el = null;
|
|
2001
2387
|
const setOpacity = (node) => {
|
|
2002
2388
|
if (shape.opacity != null && shape.opacity !== 1)
|
|
@@ -2034,8 +2420,11 @@ var VdDraw = class {
|
|
|
2034
2420
|
el.classList.add("vd-draw-shape");
|
|
2035
2421
|
this._applyShapeStroke(el, shape, standalone, colors);
|
|
2036
2422
|
setOpacity(el);
|
|
2037
|
-
|
|
2038
|
-
|
|
2423
|
+
const strokeColor = shape.color || (standalone && colors ? colors.shapeStroke : "");
|
|
2424
|
+
const targetDefs = defsTarget || this.defsEl;
|
|
2425
|
+
const markerId = this._getArrowMarkerId(strokeColor, targetDefs);
|
|
2426
|
+
if (shape.arrowEnd) el.setAttribute("marker-end", `url(#${markerId})`);
|
|
2427
|
+
if (shape.arrowStart) el.setAttribute("marker-start", `url(#${markerId})`);
|
|
2039
2428
|
} else if (shape.type === "text" || shape.type === "sticky") {
|
|
2040
2429
|
el = createSvgEl("g");
|
|
2041
2430
|
setOpacity(el);
|
|
@@ -2052,11 +2441,31 @@ var VdDraw = class {
|
|
|
2052
2441
|
else if (standalone && colors) bg.style.fill = colors.sticky;
|
|
2053
2442
|
el.appendChild(bg);
|
|
2054
2443
|
}
|
|
2055
|
-
const
|
|
2444
|
+
const startX = (shape.x || 0) + (shape.type === "sticky" ? 10 : 6);
|
|
2445
|
+
const startY = (shape.y || 0) + (shape.type === "sticky" ? 14 : 18);
|
|
2446
|
+
const availWidth = shape.type === "sticky" ? Math.max(20, (shape.w || 160) - 20) : shape.w && shape.w > 20 ? shape.w - 12 : 0;
|
|
2447
|
+
const lines = wrapText(shape.text || "", availWidth);
|
|
2448
|
+
const text = createSvgEl("text", {
|
|
2449
|
+
x: startX,
|
|
2450
|
+
y: startY,
|
|
2451
|
+
"xml:space": "preserve"
|
|
2452
|
+
});
|
|
2056
2453
|
text.classList.add("vd-draw-text");
|
|
2057
2454
|
const fill = shape.color || (standalone && colors ? colors.text : "");
|
|
2058
2455
|
if (fill) text.style.fill = fill;
|
|
2059
|
-
|
|
2456
|
+
if (!lines.length) {
|
|
2457
|
+
text.textContent = "";
|
|
2458
|
+
} else {
|
|
2459
|
+
const lineHeight = 20;
|
|
2460
|
+
lines.forEach((lineText, idx) => {
|
|
2461
|
+
const tspan = createSvgEl("tspan", {
|
|
2462
|
+
x: startX,
|
|
2463
|
+
y: startY + idx * lineHeight
|
|
2464
|
+
});
|
|
2465
|
+
tspan.textContent = lineText;
|
|
2466
|
+
text.appendChild(tspan);
|
|
2467
|
+
});
|
|
2468
|
+
}
|
|
2060
2469
|
el.appendChild(text);
|
|
2061
2470
|
}
|
|
2062
2471
|
if (el && !standalone) el.setAttribute("data-shape-id", shape.id);
|
|
@@ -2140,9 +2549,18 @@ var VdDraw = class {
|
|
|
2140
2549
|
destroy() {
|
|
2141
2550
|
if (this.destroyed) return;
|
|
2142
2551
|
this.destroyed = true;
|
|
2552
|
+
if (this._rafId != null) {
|
|
2553
|
+
(typeof cancelAnimationFrame === "function" ? cancelAnimationFrame : clearTimeout)(
|
|
2554
|
+
this._rafId
|
|
2555
|
+
);
|
|
2556
|
+
this._rafId = null;
|
|
2557
|
+
}
|
|
2143
2558
|
this.stopTextEdit({ commit: false });
|
|
2144
2559
|
this._unbindEvents();
|
|
2145
2560
|
this.listeners.clear();
|
|
2561
|
+
this._activePointers.clear();
|
|
2562
|
+
this._shapesById.clear();
|
|
2563
|
+
this._shapeElements.clear();
|
|
2146
2564
|
if (this.element) this.element.replaceChildren();
|
|
2147
2565
|
}
|
|
2148
2566
|
};
|
|
@@ -2175,10 +2593,10 @@ var VdDraw2 = defineComponent({
|
|
|
2175
2593
|
setup(props, { emit, expose }) {
|
|
2176
2594
|
const el = ref(null);
|
|
2177
2595
|
let instance = null;
|
|
2178
|
-
const create = () => {
|
|
2596
|
+
const create = (savedDoc) => {
|
|
2179
2597
|
instance = new VdDraw({
|
|
2180
2598
|
element: el.value,
|
|
2181
|
-
data: props.data,
|
|
2599
|
+
data: savedDoc || props.data,
|
|
2182
2600
|
readonly: props.readonly,
|
|
2183
2601
|
tool: props.tool,
|
|
2184
2602
|
gridSize: props.gridSize,
|
|
@@ -2212,18 +2630,28 @@ var VdDraw2 = defineComponent({
|
|
|
2212
2630
|
(next) => instance?.setGridVisible(next)
|
|
2213
2631
|
);
|
|
2214
2632
|
watch(
|
|
2215
|
-
() =>
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2633
|
+
() => props.readonly,
|
|
2634
|
+
(next) => instance?.setReadonly(next)
|
|
2635
|
+
);
|
|
2636
|
+
watch(
|
|
2637
|
+
() => props.snap,
|
|
2638
|
+
(next) => instance?.setSnap(next)
|
|
2639
|
+
);
|
|
2640
|
+
watch(
|
|
2641
|
+
() => props.history,
|
|
2642
|
+
(next) => instance?.setHistoryEnabled(next)
|
|
2643
|
+
);
|
|
2644
|
+
watch(
|
|
2645
|
+
() => props.historyLimit,
|
|
2646
|
+
(next) => instance?.setHistoryLimit(next)
|
|
2647
|
+
);
|
|
2648
|
+
watch(
|
|
2649
|
+
() => [props.gridSize, props.autoFit],
|
|
2223
2650
|
() => {
|
|
2224
2651
|
if (!instance) return;
|
|
2652
|
+
const currentDoc = typeof instance.toJSON === "function" ? instance.toJSON() : void 0;
|
|
2225
2653
|
instance.destroy();
|
|
2226
|
-
create();
|
|
2654
|
+
create(currentDoc);
|
|
2227
2655
|
}
|
|
2228
2656
|
);
|
|
2229
2657
|
onBeforeUnmount(() => {
|
|
@@ -2235,6 +2663,10 @@ var VdDraw2 = defineComponent({
|
|
|
2235
2663
|
expose({
|
|
2236
2664
|
getInstance: () => instance,
|
|
2237
2665
|
setTool: (tool) => instance?.setTool(tool),
|
|
2666
|
+
setReadonly: (readonly) => instance?.setReadonly(readonly),
|
|
2667
|
+
setSnap: (snap) => instance?.setSnap(snap),
|
|
2668
|
+
setHistoryEnabled: (enabled) => instance?.setHistoryEnabled(enabled),
|
|
2669
|
+
setHistoryLimit: (limit) => instance?.setHistoryLimit(limit),
|
|
2238
2670
|
undo: () => instance?.undo(),
|
|
2239
2671
|
redo: () => instance?.redo(),
|
|
2240
2672
|
canUndo: () => Boolean(instance?.canUndo()),
|