@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.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,63 @@ 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
|
+
}
|
|
1119
|
+
this.render({ scene: false, overlay: true });
|
|
1120
|
+
return this;
|
|
1121
|
+
}
|
|
1122
|
+
setSnap(snap) {
|
|
1123
|
+
this.snap = Boolean(snap);
|
|
1124
|
+
this.options.snap = this.snap;
|
|
1125
|
+
return this;
|
|
1126
|
+
}
|
|
1127
|
+
setHistoryEnabled(enabled) {
|
|
1128
|
+
this.historyEnabled = Boolean(enabled);
|
|
1129
|
+
this.options.history = this.historyEnabled;
|
|
1130
|
+
if (!this.historyEnabled) {
|
|
1131
|
+
this.clearHistory();
|
|
1132
|
+
}
|
|
1133
|
+
return this;
|
|
1134
|
+
}
|
|
1135
|
+
setHistoryLimit(limit) {
|
|
1136
|
+
const num = Number(limit);
|
|
1137
|
+
this.historyLimit = Number.isFinite(num) && num > 0 ? num : 100;
|
|
1138
|
+
this.options.historyLimit = this.historyLimit;
|
|
1139
|
+
if (this.history.length > this.historyLimit + 1) {
|
|
1140
|
+
const drop = this.history.length - (this.historyLimit + 1);
|
|
1141
|
+
this.history.splice(0, drop);
|
|
1142
|
+
this.historyIndex = Math.max(0, this.historyIndex - drop);
|
|
1143
|
+
}
|
|
1144
|
+
return this;
|
|
1145
|
+
}
|
|
1009
1146
|
// ── Tool + current style ─────────────────────────────────────────────────
|
|
1010
1147
|
setTool(tool) {
|
|
1011
1148
|
if (!DRAW_TOOLS.includes(tool)) return this;
|
|
1012
1149
|
this.tool = tool;
|
|
1150
|
+
if (this.canvasEl) this.canvasEl.setAttribute("data-tool", tool);
|
|
1013
1151
|
this._syncToolbar();
|
|
1014
1152
|
return this;
|
|
1015
1153
|
}
|
|
@@ -1043,7 +1181,7 @@ var VdDraw = class {
|
|
|
1043
1181
|
}
|
|
1044
1182
|
// ── Shape CRUD ─────────────────────────────────────────────────────────
|
|
1045
1183
|
getShape(id) {
|
|
1046
|
-
return this.
|
|
1184
|
+
return this._shapesById.get(id) || null;
|
|
1047
1185
|
}
|
|
1048
1186
|
getShapes() {
|
|
1049
1187
|
return this.documentData.shapes.map((s) => deepClone(s));
|
|
@@ -1095,6 +1233,8 @@ var VdDraw = class {
|
|
|
1095
1233
|
shape.text = typeof partial.text === "string" ? partial.text : "";
|
|
1096
1234
|
}
|
|
1097
1235
|
this.documentData.shapes.push(shape);
|
|
1236
|
+
this._shapesById.set(shape.id, shape);
|
|
1237
|
+
this._markDirty(shape.id);
|
|
1098
1238
|
this.render();
|
|
1099
1239
|
this._emitChange("shape:add", { shape: deepClone(shape), shapeId: shape.id });
|
|
1100
1240
|
return shape;
|
|
@@ -1108,6 +1248,8 @@ var VdDraw = class {
|
|
|
1108
1248
|
const shape = this.getShape(id);
|
|
1109
1249
|
if (!shape || !patch) return null;
|
|
1110
1250
|
Object.assign(shape, patch);
|
|
1251
|
+
this._shapesById.set(id, shape);
|
|
1252
|
+
this._markDirty(id);
|
|
1111
1253
|
this.render();
|
|
1112
1254
|
this._emitChange(options.reason || "shape:update", { shapeId: id }, options.reason ? id : null);
|
|
1113
1255
|
return shape;
|
|
@@ -1116,7 +1258,9 @@ var VdDraw = class {
|
|
|
1116
1258
|
const idx = this.documentData.shapes.findIndex((s) => s.id === id);
|
|
1117
1259
|
if (idx === -1) return false;
|
|
1118
1260
|
this.documentData.shapes.splice(idx, 1);
|
|
1261
|
+
this._shapesById.delete(id);
|
|
1119
1262
|
this.selectedIds.delete(id);
|
|
1263
|
+
this._markDirty(id);
|
|
1120
1264
|
this.render();
|
|
1121
1265
|
this._emitChange("shape:delete", { shapeId: id });
|
|
1122
1266
|
return true;
|
|
@@ -1183,11 +1327,13 @@ var VdDraw = class {
|
|
|
1183
1327
|
_replaceShape(next) {
|
|
1184
1328
|
const idx = this.documentData.shapes.findIndex((s) => s.id === next.id);
|
|
1185
1329
|
if (idx !== -1) this.documentData.shapes[idx] = next;
|
|
1330
|
+
this._shapesById.set(next.id, next);
|
|
1186
1331
|
}
|
|
1187
1332
|
nudge(dx, dy) {
|
|
1188
1333
|
const selected = this.getSelectedShapes();
|
|
1189
1334
|
if (!selected.length) return this;
|
|
1190
1335
|
for (const shape of selected) this._replaceShape(translateShape(shape, dx, dy));
|
|
1336
|
+
this._markAllDirty();
|
|
1191
1337
|
this.render();
|
|
1192
1338
|
this._emitChange(
|
|
1193
1339
|
"shape:nudge",
|
|
@@ -1206,6 +1352,7 @@ var VdDraw = class {
|
|
|
1206
1352
|
const scaled = scaleShape(shape, cur.x, cur.y, sx, sy);
|
|
1207
1353
|
this._replaceShape(translateShape(scaled, target.x - cur.x, target.y - cur.y));
|
|
1208
1354
|
}
|
|
1355
|
+
this._markAllDirty();
|
|
1209
1356
|
this.render();
|
|
1210
1357
|
this._emitChange("shape:resize", { shapeIds: [...this.selectedIds] });
|
|
1211
1358
|
return this;
|
|
@@ -1213,8 +1360,10 @@ var VdDraw = class {
|
|
|
1213
1360
|
deleteSelection() {
|
|
1214
1361
|
if (this.selectedIds.size === 0) return false;
|
|
1215
1362
|
const ids = [...this.selectedIds];
|
|
1363
|
+
for (const id of ids) this._shapesById.delete(id);
|
|
1216
1364
|
this.documentData.shapes = this.documentData.shapes.filter((s) => !this.selectedIds.has(s.id));
|
|
1217
1365
|
this.selectedIds.clear();
|
|
1366
|
+
this._markAllDirty();
|
|
1218
1367
|
this.render();
|
|
1219
1368
|
this._emitChange("shape:delete", { shapeIds: ids });
|
|
1220
1369
|
return true;
|
|
@@ -1226,6 +1375,7 @@ var VdDraw = class {
|
|
|
1226
1375
|
for (const shape of selected) {
|
|
1227
1376
|
for (const key of allowed) if (key in patch) shape[key] = patch[key];
|
|
1228
1377
|
}
|
|
1378
|
+
this._markAllDirty();
|
|
1229
1379
|
this.render();
|
|
1230
1380
|
this._emitChange(
|
|
1231
1381
|
"shape:style",
|
|
@@ -1238,6 +1388,7 @@ var VdDraw = class {
|
|
|
1238
1388
|
_reorder(mutator) {
|
|
1239
1389
|
if (this.selectedIds.size === 0) return this;
|
|
1240
1390
|
mutator();
|
|
1391
|
+
this._markAllDirty();
|
|
1241
1392
|
this.render();
|
|
1242
1393
|
this._emitChange("shape:reorder", { shapeIds: [...this.selectedIds] });
|
|
1243
1394
|
return this;
|
|
@@ -1280,6 +1431,7 @@ var VdDraw = class {
|
|
|
1280
1431
|
if (selected.length < 2) return this;
|
|
1281
1432
|
const groupId = createId("grp");
|
|
1282
1433
|
for (const shape of selected) shape.groupId = groupId;
|
|
1434
|
+
this._markAllDirty();
|
|
1283
1435
|
this.render();
|
|
1284
1436
|
this._emitChange("shape:group", { groupId, shapeIds: [...this.selectedIds] });
|
|
1285
1437
|
return this;
|
|
@@ -1294,6 +1446,7 @@ var VdDraw = class {
|
|
|
1294
1446
|
}
|
|
1295
1447
|
}
|
|
1296
1448
|
if (!changed) return this;
|
|
1449
|
+
this._markAllDirty();
|
|
1297
1450
|
this.render();
|
|
1298
1451
|
this._emitChange("shape:ungroup", { shapeIds: [...this.selectedIds] });
|
|
1299
1452
|
return this;
|
|
@@ -1320,9 +1473,11 @@ var VdDraw = class {
|
|
|
1320
1473
|
copy.groupId = groupRemap.get(copy.groupId);
|
|
1321
1474
|
}
|
|
1322
1475
|
this.documentData.shapes.push(copy);
|
|
1476
|
+
this._shapesById.set(copy.id, copy);
|
|
1323
1477
|
newIds.push(copy.id);
|
|
1324
1478
|
}
|
|
1325
1479
|
this.selectedIds = new Set(newIds);
|
|
1480
|
+
this._markAllDirty();
|
|
1326
1481
|
this.render();
|
|
1327
1482
|
this._emitChange("shape:paste", { shapeIds: newIds });
|
|
1328
1483
|
this._emitSelect();
|
|
@@ -1384,6 +1539,30 @@ var VdDraw = class {
|
|
|
1384
1539
|
sticky: read("--vd-draw-sticky-fill", "#fdf3c4")
|
|
1385
1540
|
};
|
|
1386
1541
|
}
|
|
1542
|
+
_getArrowMarkerId(color, defsTarget = this.defsEl) {
|
|
1543
|
+
const isDefault = !color;
|
|
1544
|
+
const safeKey = isDefault ? "arrow" : "arrow-" + color.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
|
|
1545
|
+
const id = this._svgId(safeKey);
|
|
1546
|
+
if (defsTarget && !defsTarget.querySelector(`#${id}`)) {
|
|
1547
|
+
const marker = createSvgEl("marker", {
|
|
1548
|
+
id,
|
|
1549
|
+
viewBox: "0 0 10 10",
|
|
1550
|
+
refX: 8,
|
|
1551
|
+
refY: 5,
|
|
1552
|
+
markerWidth: 7,
|
|
1553
|
+
markerHeight: 7,
|
|
1554
|
+
orient: "auto-start-reverse"
|
|
1555
|
+
});
|
|
1556
|
+
marker.appendChild(
|
|
1557
|
+
createSvgEl("path", {
|
|
1558
|
+
d: "M 0 0 L 10 5 L 0 10 z",
|
|
1559
|
+
fill: color || "var(--vd-draw-shape-stroke)"
|
|
1560
|
+
})
|
|
1561
|
+
);
|
|
1562
|
+
defsTarget.appendChild(marker);
|
|
1563
|
+
}
|
|
1564
|
+
return id;
|
|
1565
|
+
}
|
|
1387
1566
|
toSVG() {
|
|
1388
1567
|
const shapes = this.documentData.shapes;
|
|
1389
1568
|
const bounds = boundsOfShapes(shapes) || { x: 0, y: 0, w: 100, h: 100 };
|
|
@@ -1401,10 +1580,14 @@ var VdDraw = class {
|
|
|
1401
1580
|
height: round(vb.h),
|
|
1402
1581
|
viewBox: `${round(vb.x)} ${round(vb.y)} ${round(vb.w)} ${round(vb.h)}`
|
|
1403
1582
|
});
|
|
1583
|
+
const defs = createSvgEl("defs");
|
|
1404
1584
|
for (const shape of shapes) {
|
|
1405
|
-
const el = this._renderShapeEl(shape, { standalone: true, colors });
|
|
1585
|
+
const el = this._renderShapeEl(shape, { standalone: true, colors, defsTarget: defs });
|
|
1406
1586
|
if (el) svg.appendChild(el);
|
|
1407
1587
|
}
|
|
1588
|
+
if (defs.childNodes.length > 0) {
|
|
1589
|
+
svg.insertBefore(defs, svg.firstChild);
|
|
1590
|
+
}
|
|
1408
1591
|
return new XMLSerializer().serializeToString(svg);
|
|
1409
1592
|
}
|
|
1410
1593
|
toPNG({ scale = 2 } = {}) {
|
|
@@ -1498,7 +1681,9 @@ var VdDraw = class {
|
|
|
1498
1681
|
const viewport = { ...this.documentData.viewport };
|
|
1499
1682
|
this.documentData = deepClone(snapshot);
|
|
1500
1683
|
this.documentData.viewport = viewport;
|
|
1684
|
+
this._rebuildShapeIndex();
|
|
1501
1685
|
this._syncSelectionValidity();
|
|
1686
|
+
this._markAllDirty();
|
|
1502
1687
|
this.render();
|
|
1503
1688
|
this.emit("change", { reason, document: this.toJSON() });
|
|
1504
1689
|
this.emit("history", { reason, canUndo: this.canUndo(), canRedo: this.canRedo() });
|
|
@@ -1514,8 +1699,10 @@ var VdDraw = class {
|
|
|
1514
1699
|
}
|
|
1515
1700
|
load(data) {
|
|
1516
1701
|
this.documentData = normalizeDocument(data);
|
|
1702
|
+
this._rebuildShapeIndex();
|
|
1517
1703
|
this.selectedIds.clear();
|
|
1518
1704
|
this._resetHistory();
|
|
1705
|
+
this._markAllDirty();
|
|
1519
1706
|
this.render();
|
|
1520
1707
|
this._emitChange("load");
|
|
1521
1708
|
this._emitSelect();
|
|
@@ -1524,7 +1711,9 @@ var VdDraw = class {
|
|
|
1524
1711
|
clear() {
|
|
1525
1712
|
if (!this.documentData.shapes.length) return this;
|
|
1526
1713
|
this.documentData.shapes = [];
|
|
1714
|
+
this._shapesById.clear();
|
|
1527
1715
|
this.selectedIds.clear();
|
|
1716
|
+
this._markAllDirty();
|
|
1528
1717
|
this.render();
|
|
1529
1718
|
this._emitChange("clear");
|
|
1530
1719
|
return this;
|
|
@@ -1549,10 +1738,16 @@ var VdDraw = class {
|
|
|
1549
1738
|
}
|
|
1550
1739
|
_positionTextEditor(shape, editor) {
|
|
1551
1740
|
const vp = this.documentData.viewport;
|
|
1552
|
-
|
|
1553
|
-
editor.style.
|
|
1554
|
-
editor.style.
|
|
1555
|
-
editor.style.
|
|
1741
|
+
const scale = vp.scale || 1;
|
|
1742
|
+
editor.style.left = `${vp.x + shape.x * scale}px`;
|
|
1743
|
+
editor.style.top = `${vp.y + shape.y * scale}px`;
|
|
1744
|
+
editor.style.width = `${shape.w * scale}px`;
|
|
1745
|
+
editor.style.height = `${shape.h * scale}px`;
|
|
1746
|
+
editor.style.fontSize = `${16 * scale}px`;
|
|
1747
|
+
editor.style.lineHeight = `${20 * scale}px`;
|
|
1748
|
+
if (shape.type === "sticky") {
|
|
1749
|
+
editor.style.background = shape.fill || "var(--vd-draw-sticky-fill)";
|
|
1750
|
+
}
|
|
1556
1751
|
}
|
|
1557
1752
|
stopTextEdit({ commit = true } = {}) {
|
|
1558
1753
|
if (!this.textEditor) return;
|
|
@@ -1564,6 +1759,7 @@ var VdDraw = class {
|
|
|
1564
1759
|
const shape = this.getShape(id);
|
|
1565
1760
|
if (shape && shape.text !== value) {
|
|
1566
1761
|
shape.text = value;
|
|
1762
|
+
this._markDirty(id);
|
|
1567
1763
|
this.render();
|
|
1568
1764
|
this._emitChange("shape:text", { shapeId: id }, `text:${id}`);
|
|
1569
1765
|
}
|
|
@@ -1621,11 +1817,56 @@ var VdDraw = class {
|
|
|
1621
1817
|
if (this.destroyed || event.button != null && event.button !== 0) return;
|
|
1622
1818
|
this.canvasEl.focus();
|
|
1623
1819
|
this.stopTextEdit({ commit: true });
|
|
1820
|
+
this._activePointers.set(event.pointerId, { clientX: event.clientX, clientY: event.clientY });
|
|
1821
|
+
if (this._activePointers.size === 2) {
|
|
1822
|
+
if (this.interaction) {
|
|
1823
|
+
const it = this.interaction;
|
|
1824
|
+
if (it.kind === "freehand" || it.kind === "create-line" || it.kind === "create-box") {
|
|
1825
|
+
this.documentData.shapes = this.documentData.shapes.filter((s) => s.id !== it.shapeId);
|
|
1826
|
+
this._shapesById.delete(it.shapeId);
|
|
1827
|
+
const el = this._shapeElements.get(it.shapeId);
|
|
1828
|
+
if (el) {
|
|
1829
|
+
el.remove();
|
|
1830
|
+
this._shapeElements.delete(it.shapeId);
|
|
1831
|
+
}
|
|
1832
|
+
this._scheduleRender({ scene: true, overlay: false });
|
|
1833
|
+
} else if (it.kind === "move" || it.kind === "resize") {
|
|
1834
|
+
for (const orig of it.originals) this._replaceShape(orig);
|
|
1835
|
+
this._scheduleRender({ scene: true });
|
|
1836
|
+
} else if (it.kind === "marquee") {
|
|
1837
|
+
clearChildren(this.marqueeLayer);
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
const pts = [...this._activePointers.values()];
|
|
1841
|
+
const p1 = pts[0];
|
|
1842
|
+
const p2 = pts[1];
|
|
1843
|
+
const initialDistance = Math.hypot(p2.clientX - p1.clientX, p2.clientY - p1.clientY) || 1;
|
|
1844
|
+
const initialClientMid = {
|
|
1845
|
+
x: (p1.clientX + p2.clientX) / 2,
|
|
1846
|
+
y: (p1.clientY + p2.clientY) / 2
|
|
1847
|
+
};
|
|
1848
|
+
const initialLocalMid = this._clientToLocal(initialClientMid.x, initialClientMid.y);
|
|
1849
|
+
const initialVp = { ...this.documentData.viewport };
|
|
1850
|
+
const currentScale = initialVp.scale || 1;
|
|
1851
|
+
const worldMid = {
|
|
1852
|
+
x: (initialLocalMid.x - initialVp.x) / currentScale,
|
|
1853
|
+
y: (initialLocalMid.y - initialVp.y) / currentScale
|
|
1854
|
+
};
|
|
1855
|
+
this.interaction = {
|
|
1856
|
+
kind: "pinch",
|
|
1857
|
+
initialDistance,
|
|
1858
|
+
worldMid,
|
|
1859
|
+
initialScale: currentScale
|
|
1860
|
+
};
|
|
1861
|
+
if (typeof event.preventDefault === "function") event.preventDefault();
|
|
1862
|
+
return;
|
|
1863
|
+
}
|
|
1624
1864
|
const world = this._clientToWorld(event.clientX, event.clientY);
|
|
1625
1865
|
const shapeTarget = event.target.closest("[data-shape-id]");
|
|
1626
1866
|
const handleTarget = event.target.closest("[data-handle]");
|
|
1627
1867
|
if (this.tool === "hand") {
|
|
1628
1868
|
this.interaction = this._beginPan(event);
|
|
1869
|
+
if (this.canvasEl) this.canvasEl.classList.add("vd-draw-panning");
|
|
1629
1870
|
this._capture(event.pointerId);
|
|
1630
1871
|
return;
|
|
1631
1872
|
}
|
|
@@ -1636,6 +1877,15 @@ var VdDraw = class {
|
|
|
1636
1877
|
return;
|
|
1637
1878
|
}
|
|
1638
1879
|
if (this.tool === "select") {
|
|
1880
|
+
if (event.detail === 2 && shapeTarget && !this.readonly) {
|
|
1881
|
+
const id = shapeTarget.getAttribute("data-shape-id");
|
|
1882
|
+
const shape = this.getShape(id);
|
|
1883
|
+
if (shape && (shape.type === "text" || shape.type === "sticky")) {
|
|
1884
|
+
this.select(id);
|
|
1885
|
+
this.startTextEdit(id);
|
|
1886
|
+
return;
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1639
1889
|
if (handleTarget && this.selectedIds.size) {
|
|
1640
1890
|
this.interaction = {
|
|
1641
1891
|
kind: "resize",
|
|
@@ -1699,6 +1949,7 @@ var VdDraw = class {
|
|
|
1699
1949
|
points: [[round(world.x), round(world.y), pressure]]
|
|
1700
1950
|
};
|
|
1701
1951
|
this.documentData.shapes.push(shape2);
|
|
1952
|
+
this._shapesById.set(shape2.id, shape2);
|
|
1702
1953
|
return { kind: "freehand", pointerId: event.pointerId, shapeId: shape2.id };
|
|
1703
1954
|
}
|
|
1704
1955
|
if (tool === "line") {
|
|
@@ -1716,6 +1967,7 @@ var VdDraw = class {
|
|
|
1716
1967
|
opacity: this.style.opacity
|
|
1717
1968
|
};
|
|
1718
1969
|
this.documentData.shapes.push(shape2);
|
|
1970
|
+
this._shapesById.set(shape2.id, shape2);
|
|
1719
1971
|
return { kind: "create-line", pointerId: event.pointerId, shapeId: shape2.id };
|
|
1720
1972
|
}
|
|
1721
1973
|
const type = tool;
|
|
@@ -1733,6 +1985,7 @@ var VdDraw = class {
|
|
|
1733
1985
|
};
|
|
1734
1986
|
if (type === "text" || type === "sticky") shape.text = "";
|
|
1735
1987
|
this.documentData.shapes.push(shape);
|
|
1988
|
+
this._shapesById.set(shape.id, shape);
|
|
1736
1989
|
return { kind: "create-box", pointerId: event.pointerId, shapeId: shape.id, start: world };
|
|
1737
1990
|
}
|
|
1738
1991
|
_applyErase(world) {
|
|
@@ -1747,16 +2000,44 @@ var VdDraw = class {
|
|
|
1747
2000
|
changed = true;
|
|
1748
2001
|
}
|
|
1749
2002
|
}
|
|
1750
|
-
if (changed)
|
|
2003
|
+
if (changed) {
|
|
2004
|
+
for (const id of it.erased) this._markDirty(id);
|
|
2005
|
+
this._scheduleRender({ scene: true, overlay: false });
|
|
2006
|
+
}
|
|
1751
2007
|
}
|
|
1752
2008
|
_handlePointerMove(event) {
|
|
2009
|
+
if (this._activePointers.has(event.pointerId)) {
|
|
2010
|
+
this._activePointers.set(event.pointerId, { clientX: event.clientX, clientY: event.clientY });
|
|
2011
|
+
}
|
|
1753
2012
|
const it = this.interaction;
|
|
1754
2013
|
if (!it) return;
|
|
2014
|
+
if (it.kind === "pinch") {
|
|
2015
|
+
if (this._activePointers.size >= 2) {
|
|
2016
|
+
if (typeof event.preventDefault === "function") event.preventDefault();
|
|
2017
|
+
const pts = [...this._activePointers.values()];
|
|
2018
|
+
const p1 = pts[0];
|
|
2019
|
+
const p2 = pts[1];
|
|
2020
|
+
const curDistance = Math.hypot(p2.clientX - p1.clientX, p2.clientY - p1.clientY) || 1;
|
|
2021
|
+
const curClientMid = {
|
|
2022
|
+
x: (p1.clientX + p2.clientX) / 2,
|
|
2023
|
+
y: (p1.clientY + p2.clientY) / 2
|
|
2024
|
+
};
|
|
2025
|
+
const curLocalMid = this._clientToLocal(curClientMid.x, curClientMid.y);
|
|
2026
|
+
const distRatio = curDistance / it.initialDistance;
|
|
2027
|
+
const newScale = clamp(it.initialScale * distRatio, MIN_SCALE, MAX_SCALE);
|
|
2028
|
+
const vp = this.documentData.viewport;
|
|
2029
|
+
vp.scale = newScale;
|
|
2030
|
+
vp.x = curLocalMid.x - it.worldMid.x * newScale;
|
|
2031
|
+
vp.y = curLocalMid.y - it.worldMid.y * newScale;
|
|
2032
|
+
this._scheduleRender({ scene: false });
|
|
2033
|
+
}
|
|
2034
|
+
return;
|
|
2035
|
+
}
|
|
1755
2036
|
if (it.kind === "pan") {
|
|
1756
2037
|
const vp = this.documentData.viewport;
|
|
1757
2038
|
vp.x = it.startX + (event.clientX - it.startClientX);
|
|
1758
2039
|
vp.y = it.startY + (event.clientY - it.startClientY);
|
|
1759
|
-
this.
|
|
2040
|
+
this._scheduleRender({ scene: false });
|
|
1760
2041
|
return;
|
|
1761
2042
|
}
|
|
1762
2043
|
const world = this._clientToWorld(event.clientX, event.clientY);
|
|
@@ -1774,8 +2055,9 @@ var VdDraw = class {
|
|
|
1774
2055
|
if (snap.dx || snap.dy)
|
|
1775
2056
|
for (const orig of it.originals)
|
|
1776
2057
|
this._replaceShape(translateShape(orig, dx + snap.dx, dy + snap.dy));
|
|
2058
|
+
for (const orig of it.originals) this._markDirty(orig.id);
|
|
1777
2059
|
this._renderGuides(snap.guides);
|
|
1778
|
-
this.
|
|
2060
|
+
this._scheduleRender({ scene: true, guides: false });
|
|
1779
2061
|
return;
|
|
1780
2062
|
}
|
|
1781
2063
|
if (it.kind === "resize") {
|
|
@@ -1790,9 +2072,10 @@ var VdDraw = class {
|
|
|
1790
2072
|
for (const orig of it.originals) {
|
|
1791
2073
|
const scaled = scaleShape(orig, it.startBounds.x, it.startBounds.y, sx, sy);
|
|
1792
2074
|
this._replaceShape(translateShape(scaled, t.x - it.startBounds.x, t.y - it.startBounds.y));
|
|
2075
|
+
this._markDirty(orig.id);
|
|
1793
2076
|
}
|
|
1794
2077
|
it.moved = true;
|
|
1795
|
-
this.
|
|
2078
|
+
this._scheduleRender();
|
|
1796
2079
|
return;
|
|
1797
2080
|
}
|
|
1798
2081
|
if (it.kind === "marquee") {
|
|
@@ -1803,8 +2086,14 @@ var VdDraw = class {
|
|
|
1803
2086
|
if (it.kind === "freehand") {
|
|
1804
2087
|
const shape = this.getShape(it.shapeId);
|
|
1805
2088
|
if (shape) {
|
|
1806
|
-
|
|
1807
|
-
|
|
2089
|
+
const raw = [round(world.x), round(world.y), event.pressure || 0.5];
|
|
2090
|
+
const preset = BRUSH_PRESETS[shape.brush] || BRUSH_PRESETS[DEFAULT_BRUSH];
|
|
2091
|
+
const smoothFactor = 1 - (preset.smoothing || 0.5) * 0.6;
|
|
2092
|
+
const prev = shape.points[shape.points.length - 1];
|
|
2093
|
+
const pt = prev ? smoothPoint(prev, raw, smoothFactor) : raw;
|
|
2094
|
+
appendAndSimplify(shape.points, pt);
|
|
2095
|
+
this._markDirty(it.shapeId);
|
|
2096
|
+
this._scheduleRender({ scene: true });
|
|
1808
2097
|
}
|
|
1809
2098
|
return;
|
|
1810
2099
|
}
|
|
@@ -1812,7 +2101,8 @@ var VdDraw = class {
|
|
|
1812
2101
|
const shape = this.getShape(it.shapeId);
|
|
1813
2102
|
if (shape) {
|
|
1814
2103
|
shape.points[1] = [round(world.x), round(world.y)];
|
|
1815
|
-
this.
|
|
2104
|
+
this._markDirty(it.shapeId);
|
|
2105
|
+
this._scheduleRender({ scene: true });
|
|
1816
2106
|
}
|
|
1817
2107
|
return;
|
|
1818
2108
|
}
|
|
@@ -1823,7 +2113,8 @@ var VdDraw = class {
|
|
|
1823
2113
|
shape.y = round(Math.min(it.start.y, world.y));
|
|
1824
2114
|
shape.w = round(Math.max(1, Math.abs(world.x - it.start.x)));
|
|
1825
2115
|
shape.h = round(Math.max(1, Math.abs(world.y - it.start.y)));
|
|
1826
|
-
this.
|
|
2116
|
+
this._markDirty(it.shapeId);
|
|
2117
|
+
this._scheduleRender({ scene: true });
|
|
1827
2118
|
}
|
|
1828
2119
|
}
|
|
1829
2120
|
}
|
|
@@ -1842,9 +2133,18 @@ var VdDraw = class {
|
|
|
1842
2133
|
return { x, y, w, h: h2 };
|
|
1843
2134
|
}
|
|
1844
2135
|
_handlePointerUp(event) {
|
|
2136
|
+
this._activePointers.delete(event.pointerId);
|
|
1845
2137
|
const it = this.interaction;
|
|
1846
2138
|
if (!it) return;
|
|
2139
|
+
if (it.kind === "pinch") {
|
|
2140
|
+
if (this._activePointers.size < 2) {
|
|
2141
|
+
this.interaction = null;
|
|
2142
|
+
this._emitViewportChange("viewport:pinch");
|
|
2143
|
+
}
|
|
2144
|
+
return;
|
|
2145
|
+
}
|
|
1847
2146
|
this.interaction = null;
|
|
2147
|
+
if (this.canvasEl) this.canvasEl.classList.remove("vd-draw-panning");
|
|
1848
2148
|
if (typeof this.canvasEl.releasePointerCapture === "function" && this.canvasEl.hasPointerCapture?.(event.pointerId)) {
|
|
1849
2149
|
try {
|
|
1850
2150
|
this.canvasEl.releasePointerCapture(event.pointerId);
|
|
@@ -1860,18 +2160,22 @@ var VdDraw = class {
|
|
|
1860
2160
|
if (it.kind === "erase") {
|
|
1861
2161
|
if (it.erased.size) {
|
|
1862
2162
|
const ids = [...it.erased];
|
|
2163
|
+
for (const id of ids) this._shapesById.delete(id);
|
|
1863
2164
|
this.documentData.shapes = this.documentData.shapes.filter((s) => !it.erased.has(s.id));
|
|
2165
|
+
this._markAllDirty();
|
|
1864
2166
|
this.render();
|
|
1865
2167
|
this._emitChange("shape:erase", { shapeIds: ids });
|
|
1866
2168
|
}
|
|
1867
2169
|
return;
|
|
1868
2170
|
}
|
|
1869
2171
|
if (it.kind === "move" && it.moved) {
|
|
2172
|
+
this._markAllDirty();
|
|
1870
2173
|
this.render();
|
|
1871
2174
|
this._emitChange("shape:move", { shapeIds: [...this.selectedIds] });
|
|
1872
2175
|
return;
|
|
1873
2176
|
}
|
|
1874
2177
|
if (it.kind === "resize" && it.moved) {
|
|
2178
|
+
this._markAllDirty();
|
|
1875
2179
|
this.render();
|
|
1876
2180
|
this._emitChange("shape:resize", { shapeIds: [...this.selectedIds] });
|
|
1877
2181
|
return;
|
|
@@ -1888,6 +2192,8 @@ var VdDraw = class {
|
|
|
1888
2192
|
const b = shapeBounds(shape);
|
|
1889
2193
|
if (it.kind !== "freehand" && b.w < 2 && b.h < 2 && shape.type !== "text" && shape.type !== "sticky") {
|
|
1890
2194
|
this.documentData.shapes = this.documentData.shapes.filter((s) => s.id !== it.shapeId);
|
|
2195
|
+
this._shapesById.delete(it.shapeId);
|
|
2196
|
+
this._markAllDirty();
|
|
1891
2197
|
this.render();
|
|
1892
2198
|
return;
|
|
1893
2199
|
}
|
|
@@ -1901,6 +2207,7 @@ var VdDraw = class {
|
|
|
1901
2207
|
this.select(shape.id);
|
|
1902
2208
|
this.setTool("select");
|
|
1903
2209
|
}
|
|
2210
|
+
this._markDirty(shape.id);
|
|
1904
2211
|
this.render();
|
|
1905
2212
|
this._emitChange("shape:add", { shape: deepClone(shape), shapeId: shape.id });
|
|
1906
2213
|
if (shape.type === "text" || shape.type === "sticky") this.startTextEdit(shape.id);
|
|
@@ -1977,26 +2284,91 @@ var VdDraw = class {
|
|
|
1977
2284
|
if (this.destroyed) return;
|
|
1978
2285
|
this.render({ scene: false });
|
|
1979
2286
|
}
|
|
2287
|
+
// ── Dirty tracking ──────────────────────────────────────────────────────
|
|
2288
|
+
_markDirty(shapeId) {
|
|
2289
|
+
this._dirtyShapes.add(shapeId);
|
|
2290
|
+
}
|
|
2291
|
+
_markAllDirty() {
|
|
2292
|
+
this._allDirty = true;
|
|
2293
|
+
}
|
|
2294
|
+
// ── rAF-batched render ─────────────────────────────────────────────────
|
|
2295
|
+
_scheduleRender(flags = {}) {
|
|
2296
|
+
this._pendingFlags = {
|
|
2297
|
+
scene: (this._pendingFlags?.scene ?? false) || flags.scene !== false,
|
|
2298
|
+
overlay: (this._pendingFlags?.overlay ?? false) || flags.overlay !== false
|
|
2299
|
+
};
|
|
2300
|
+
if (this._rafId != null) return;
|
|
2301
|
+
this._rafId = (typeof requestAnimationFrame === "function" ? requestAnimationFrame : setTimeout)(() => {
|
|
2302
|
+
this._rafId = null;
|
|
2303
|
+
const f = this._pendingFlags || {};
|
|
2304
|
+
this._pendingFlags = null;
|
|
2305
|
+
this.render(f);
|
|
2306
|
+
});
|
|
2307
|
+
}
|
|
1980
2308
|
// ── Rendering ────────────────────────────────────────────────────────────
|
|
1981
2309
|
render(flags = {}) {
|
|
1982
2310
|
if (this.destroyed) return;
|
|
1983
2311
|
const { scene = true, overlay = true } = flags;
|
|
1984
2312
|
const vp = this.documentData.viewport;
|
|
1985
2313
|
this.world.setAttribute("transform", `matrix(${vp.scale} 0 0 ${vp.scale} ${vp.x} ${vp.y})`);
|
|
2314
|
+
if (this.textEditor) {
|
|
2315
|
+
const activeShape = this.getShape(this.textEditor.id);
|
|
2316
|
+
if (activeShape) this._positionTextEditor(activeShape, this.textEditor.el);
|
|
2317
|
+
}
|
|
1986
2318
|
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
|
-
}
|
|
2319
|
+
this._renderSceneIncremental();
|
|
1994
2320
|
}
|
|
1995
2321
|
if (overlay) this._renderOverlay();
|
|
1996
2322
|
}
|
|
2323
|
+
_renderSceneIncremental() {
|
|
2324
|
+
const shapes = this.documentData.shapes;
|
|
2325
|
+
const erasing = this.interaction && this.interaction.kind === "erase" ? this.interaction.erased : null;
|
|
2326
|
+
const currentIds = /* @__PURE__ */ new Set();
|
|
2327
|
+
for (const shape of shapes) {
|
|
2328
|
+
if (erasing && erasing.has(shape.id)) continue;
|
|
2329
|
+
currentIds.add(shape.id);
|
|
2330
|
+
}
|
|
2331
|
+
for (const [id, el] of this._shapeElements) {
|
|
2332
|
+
if (!currentIds.has(id)) {
|
|
2333
|
+
el.remove();
|
|
2334
|
+
this._shapeElements.delete(id);
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
const fullRebuild = this._allDirty;
|
|
2338
|
+
let prevEl = null;
|
|
2339
|
+
for (const shape of shapes) {
|
|
2340
|
+
if (!currentIds.has(shape.id)) continue;
|
|
2341
|
+
const needsUpdate = fullRebuild || this._dirtyShapes.has(shape.id);
|
|
2342
|
+
let el = this._shapeElements.get(shape.id);
|
|
2343
|
+
if (needsUpdate || !el) {
|
|
2344
|
+
const newEl = this._renderShapeEl(shape, {});
|
|
2345
|
+
if (!newEl) {
|
|
2346
|
+
if (el) {
|
|
2347
|
+
el.remove();
|
|
2348
|
+
this._shapeElements.delete(shape.id);
|
|
2349
|
+
}
|
|
2350
|
+
continue;
|
|
2351
|
+
}
|
|
2352
|
+
if (el) {
|
|
2353
|
+
el.replaceWith(newEl);
|
|
2354
|
+
} else {
|
|
2355
|
+
if (prevEl && prevEl.parentNode === this.shapesLayer) {
|
|
2356
|
+
prevEl.after(newEl);
|
|
2357
|
+
} else {
|
|
2358
|
+
this.shapesLayer.prepend(newEl);
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
el = newEl;
|
|
2362
|
+
this._shapeElements.set(shape.id, el);
|
|
2363
|
+
}
|
|
2364
|
+
prevEl = el;
|
|
2365
|
+
}
|
|
2366
|
+
this._dirtyShapes.clear();
|
|
2367
|
+
this._allDirty = false;
|
|
2368
|
+
}
|
|
1997
2369
|
// Colors are applied via inline `style` (which wins over the CSS class rules
|
|
1998
2370
|
// and serializes self-contained), so a picked color always renders.
|
|
1999
|
-
_renderShapeEl(shape, { standalone = false, colors = null }) {
|
|
2371
|
+
_renderShapeEl(shape, { standalone = false, colors = null, defsTarget = null } = {}) {
|
|
2000
2372
|
let el = null;
|
|
2001
2373
|
const setOpacity = (node) => {
|
|
2002
2374
|
if (shape.opacity != null && shape.opacity !== 1)
|
|
@@ -2034,8 +2406,11 @@ var VdDraw = class {
|
|
|
2034
2406
|
el.classList.add("vd-draw-shape");
|
|
2035
2407
|
this._applyShapeStroke(el, shape, standalone, colors);
|
|
2036
2408
|
setOpacity(el);
|
|
2037
|
-
|
|
2038
|
-
|
|
2409
|
+
const strokeColor = shape.color || (standalone && colors ? colors.shapeStroke : "");
|
|
2410
|
+
const targetDefs = defsTarget || this.defsEl;
|
|
2411
|
+
const markerId = this._getArrowMarkerId(strokeColor, targetDefs);
|
|
2412
|
+
if (shape.arrowEnd) el.setAttribute("marker-end", `url(#${markerId})`);
|
|
2413
|
+
if (shape.arrowStart) el.setAttribute("marker-start", `url(#${markerId})`);
|
|
2039
2414
|
} else if (shape.type === "text" || shape.type === "sticky") {
|
|
2040
2415
|
el = createSvgEl("g");
|
|
2041
2416
|
setOpacity(el);
|
|
@@ -2052,11 +2427,31 @@ var VdDraw = class {
|
|
|
2052
2427
|
else if (standalone && colors) bg.style.fill = colors.sticky;
|
|
2053
2428
|
el.appendChild(bg);
|
|
2054
2429
|
}
|
|
2055
|
-
const
|
|
2430
|
+
const startX = (shape.x || 0) + (shape.type === "sticky" ? 10 : 6);
|
|
2431
|
+
const startY = (shape.y || 0) + (shape.type === "sticky" ? 14 : 18);
|
|
2432
|
+
const availWidth = shape.type === "sticky" ? Math.max(20, (shape.w || 160) - 20) : shape.w && shape.w > 20 ? shape.w - 12 : 0;
|
|
2433
|
+
const lines = wrapText(shape.text || "", availWidth);
|
|
2434
|
+
const text = createSvgEl("text", {
|
|
2435
|
+
x: startX,
|
|
2436
|
+
y: startY,
|
|
2437
|
+
"xml:space": "preserve"
|
|
2438
|
+
});
|
|
2056
2439
|
text.classList.add("vd-draw-text");
|
|
2057
2440
|
const fill = shape.color || (standalone && colors ? colors.text : "");
|
|
2058
2441
|
if (fill) text.style.fill = fill;
|
|
2059
|
-
|
|
2442
|
+
if (!lines.length) {
|
|
2443
|
+
text.textContent = "";
|
|
2444
|
+
} else {
|
|
2445
|
+
const lineHeight = 20;
|
|
2446
|
+
lines.forEach((lineText, idx) => {
|
|
2447
|
+
const tspan = createSvgEl("tspan", {
|
|
2448
|
+
x: startX,
|
|
2449
|
+
y: startY + idx * lineHeight
|
|
2450
|
+
});
|
|
2451
|
+
tspan.textContent = lineText;
|
|
2452
|
+
text.appendChild(tspan);
|
|
2453
|
+
});
|
|
2454
|
+
}
|
|
2060
2455
|
el.appendChild(text);
|
|
2061
2456
|
}
|
|
2062
2457
|
if (el && !standalone) el.setAttribute("data-shape-id", shape.id);
|
|
@@ -2140,9 +2535,18 @@ var VdDraw = class {
|
|
|
2140
2535
|
destroy() {
|
|
2141
2536
|
if (this.destroyed) return;
|
|
2142
2537
|
this.destroyed = true;
|
|
2538
|
+
if (this._rafId != null) {
|
|
2539
|
+
(typeof cancelAnimationFrame === "function" ? cancelAnimationFrame : clearTimeout)(
|
|
2540
|
+
this._rafId
|
|
2541
|
+
);
|
|
2542
|
+
this._rafId = null;
|
|
2543
|
+
}
|
|
2143
2544
|
this.stopTextEdit({ commit: false });
|
|
2144
2545
|
this._unbindEvents();
|
|
2145
2546
|
this.listeners.clear();
|
|
2547
|
+
this._activePointers.clear();
|
|
2548
|
+
this._shapesById.clear();
|
|
2549
|
+
this._shapeElements.clear();
|
|
2146
2550
|
if (this.element) this.element.replaceChildren();
|
|
2147
2551
|
}
|
|
2148
2552
|
};
|
|
@@ -2175,10 +2579,10 @@ var VdDraw2 = defineComponent({
|
|
|
2175
2579
|
setup(props, { emit, expose }) {
|
|
2176
2580
|
const el = ref(null);
|
|
2177
2581
|
let instance = null;
|
|
2178
|
-
const create = () => {
|
|
2582
|
+
const create = (savedDoc) => {
|
|
2179
2583
|
instance = new VdDraw({
|
|
2180
2584
|
element: el.value,
|
|
2181
|
-
data: props.data,
|
|
2585
|
+
data: savedDoc || props.data,
|
|
2182
2586
|
readonly: props.readonly,
|
|
2183
2587
|
tool: props.tool,
|
|
2184
2588
|
gridSize: props.gridSize,
|
|
@@ -2212,18 +2616,28 @@ var VdDraw2 = defineComponent({
|
|
|
2212
2616
|
(next) => instance?.setGridVisible(next)
|
|
2213
2617
|
);
|
|
2214
2618
|
watch(
|
|
2215
|
-
() =>
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2619
|
+
() => props.readonly,
|
|
2620
|
+
(next) => instance?.setReadonly(next)
|
|
2621
|
+
);
|
|
2622
|
+
watch(
|
|
2623
|
+
() => props.snap,
|
|
2624
|
+
(next) => instance?.setSnap(next)
|
|
2625
|
+
);
|
|
2626
|
+
watch(
|
|
2627
|
+
() => props.history,
|
|
2628
|
+
(next) => instance?.setHistoryEnabled(next)
|
|
2629
|
+
);
|
|
2630
|
+
watch(
|
|
2631
|
+
() => props.historyLimit,
|
|
2632
|
+
(next) => instance?.setHistoryLimit(next)
|
|
2633
|
+
);
|
|
2634
|
+
watch(
|
|
2635
|
+
() => [props.gridSize, props.autoFit],
|
|
2223
2636
|
() => {
|
|
2224
2637
|
if (!instance) return;
|
|
2638
|
+
const currentDoc = typeof instance.toJSON === "function" ? instance.toJSON() : void 0;
|
|
2225
2639
|
instance.destroy();
|
|
2226
|
-
create();
|
|
2640
|
+
create(currentDoc);
|
|
2227
2641
|
}
|
|
2228
2642
|
);
|
|
2229
2643
|
onBeforeUnmount(() => {
|
|
@@ -2235,6 +2649,10 @@ var VdDraw2 = defineComponent({
|
|
|
2235
2649
|
expose({
|
|
2236
2650
|
getInstance: () => instance,
|
|
2237
2651
|
setTool: (tool) => instance?.setTool(tool),
|
|
2652
|
+
setReadonly: (readonly) => instance?.setReadonly(readonly),
|
|
2653
|
+
setSnap: (snap) => instance?.setSnap(snap),
|
|
2654
|
+
setHistoryEnabled: (enabled) => instance?.setHistoryEnabled(enabled),
|
|
2655
|
+
setHistoryLimit: (limit) => instance?.setHistoryLimit(limit),
|
|
2238
2656
|
undo: () => instance?.undo(),
|
|
2239
2657
|
redo: () => instance?.redo(),
|
|
2240
2658
|
canUndo: () => Boolean(instance?.canUndo()),
|