@stocksharp/diagram 1.2.0 → 1.2.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/dist/esm/canvas-renderer.js +113 -38
- package/dist/esm/canvas-renderer.js.map +1 -1
- package/dist/esm/color.js +109 -0
- package/dist/esm/color.js.map +1 -0
- package/dist/esm/diagram/context-menu.js +1 -1
- package/dist/esm/diagram/context-menu.js.map +1 -1
- package/dist/esm/diagram/stocksharp-diagram.js +10 -0
- package/dist/esm/diagram/stocksharp-diagram.js.map +1 -1
- package/dist/esm/embed.js +1 -51
- package/dist/esm/embed.js.map +1 -1
- package/dist/ssdiagram-headless.js +145 -29
- package/dist/ssdiagram-headless.js.map +4 -4
- package/dist/ssdiagram.js +163 -67
- package/dist/ssdiagram.js.map +4 -4
- package/dist/types/canvas-renderer.d.ts +5 -0
- package/dist/types/canvas-renderer.d.ts.map +1 -1
- package/dist/types/color.d.ts +12 -0
- package/dist/types/color.d.ts.map +1 -0
- package/dist/types/diagram/stocksharp-diagram.d.ts +8 -0
- package/dist/types/diagram/stocksharp-diagram.d.ts.map +1 -1
- package/dist/types/embed.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/canvas-renderer.ts +111 -35
- package/src/color.ts +125 -0
- package/src/diagram/context-menu.ts +1 -1
- package/src/diagram/stocksharp-diagram.ts +11 -0
- package/src/embed.ts +1 -56
|
@@ -348,6 +348,69 @@ var SSDiagramHeadless = (() => {
|
|
|
348
348
|
}
|
|
349
349
|
};
|
|
350
350
|
|
|
351
|
+
// src/color.ts
|
|
352
|
+
var CSS_COLOR_KEYWORDS = {
|
|
353
|
+
white: [255, 255, 255],
|
|
354
|
+
black: [0, 0, 0],
|
|
355
|
+
transparent: [255, 255, 255]
|
|
356
|
+
};
|
|
357
|
+
function parseRgb(color) {
|
|
358
|
+
const value = color.trim().toLowerCase();
|
|
359
|
+
if (value === "")
|
|
360
|
+
return null;
|
|
361
|
+
const keyword = CSS_COLOR_KEYWORDS[value];
|
|
362
|
+
if (keyword !== void 0)
|
|
363
|
+
return [...keyword];
|
|
364
|
+
const long = value.match(/^#?([0-9a-f]{6})$/);
|
|
365
|
+
if (long !== null) {
|
|
366
|
+
const n2 = parseInt(long[1], 16);
|
|
367
|
+
return [n2 >> 16 & 255, n2 >> 8 & 255, n2 & 255];
|
|
368
|
+
}
|
|
369
|
+
const short = value.match(/^#?([0-9a-f]{3})([0-9a-f])?$/);
|
|
370
|
+
if (short !== null) {
|
|
371
|
+
const [r, g, b] = [...short[1]].map((digit) => parseInt(digit + digit, 16));
|
|
372
|
+
return [r, g, b];
|
|
373
|
+
}
|
|
374
|
+
const long8 = value.match(/^#?([0-9a-f]{6})[0-9a-f]{2}$/);
|
|
375
|
+
if (long8 !== null) {
|
|
376
|
+
const n2 = parseInt(long8[1], 16);
|
|
377
|
+
return [n2 >> 16 & 255, n2 >> 8 & 255, n2 & 255];
|
|
378
|
+
}
|
|
379
|
+
const rgb = value.match(/^rgba?\(([^)]+)\)$/);
|
|
380
|
+
if (rgb !== null) {
|
|
381
|
+
const parts = rgb[1].split(/[\s,/]+/).filter((part) => part !== "");
|
|
382
|
+
if (parts.length >= 3) {
|
|
383
|
+
const [r, g, b] = parts.slice(0, 3).map((part) => part.endsWith("%") ? parseFloat(part) / 100 * 255 : parseFloat(part));
|
|
384
|
+
if ([r, g, b].every((channel) => Number.isFinite(channel)))
|
|
385
|
+
return [r, g, b];
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
var DARK_TEXT = "#1b1b1b";
|
|
391
|
+
var LIGHT_TEXT = "#f5f5f5";
|
|
392
|
+
function toLinear(channel) {
|
|
393
|
+
const v = channel / 255;
|
|
394
|
+
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
|
395
|
+
}
|
|
396
|
+
function relativeLuminance(color) {
|
|
397
|
+
const rgb = parseRgb(color);
|
|
398
|
+
if (rgb === null)
|
|
399
|
+
return -1;
|
|
400
|
+
const [r, g, b] = rgb;
|
|
401
|
+
return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
|
|
402
|
+
}
|
|
403
|
+
function contrast(a, b) {
|
|
404
|
+
const [lighter, darker] = a > b ? [a, b] : [b, a];
|
|
405
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
406
|
+
}
|
|
407
|
+
function readableTextOn(fill) {
|
|
408
|
+
const background = relativeLuminance(fill);
|
|
409
|
+
if (background < 0)
|
|
410
|
+
return DARK_TEXT;
|
|
411
|
+
return contrast(background, relativeLuminance(LIGHT_TEXT)) > contrast(background, relativeLuminance(DARK_TEXT)) ? LIGHT_TEXT : DARK_TEXT;
|
|
412
|
+
}
|
|
413
|
+
|
|
351
414
|
// src/core/json.ts
|
|
352
415
|
function setJsonKey(target, key, value) {
|
|
353
416
|
if (key === "__proto__") {
|
|
@@ -980,6 +1043,9 @@ var SSDiagramHeadless = (() => {
|
|
|
980
1043
|
const normalized = normalizePortType(type);
|
|
981
1044
|
return normalized === "" || normalized === "*" || normalized === "any" || normalized === "anydata" || normalized === "any data" || normalized === "object" || normalized === "system.object" || normalized.startsWith("system.object,");
|
|
982
1045
|
}
|
|
1046
|
+
function isGrowAnchor(port) {
|
|
1047
|
+
return port.isDynamic && !port.isSibling;
|
|
1048
|
+
}
|
|
983
1049
|
function arePortTypesCompatible(fromType, toType, availableTypes) {
|
|
984
1050
|
if (isWildcardPortType(fromType) || isWildcardPortType(toType)) return true;
|
|
985
1051
|
const normalizedFrom = normalizePortType(fromType);
|
|
@@ -1187,6 +1253,8 @@ var SSDiagramHeadless = (() => {
|
|
|
1187
1253
|
this.hoverPort = null;
|
|
1188
1254
|
this.hoverNode = null;
|
|
1189
1255
|
this.hoveredLink = null;
|
|
1256
|
+
// Type feeding each wired input, keyed by node and port, rebuilt for every frame.
|
|
1257
|
+
this.portFeed = /* @__PURE__ */ new Map();
|
|
1190
1258
|
this.tipTimer = null;
|
|
1191
1259
|
this.tipShow = false;
|
|
1192
1260
|
this.tipTarget = null;
|
|
@@ -1229,8 +1297,20 @@ var SSDiagramHeadless = (() => {
|
|
|
1229
1297
|
if (ctx === null) throw new Error("ssdiagram: 2d context unavailable");
|
|
1230
1298
|
this.ctx = ctx;
|
|
1231
1299
|
this.resize(this.host.clientWidth || 800, this.host.clientHeight || 480);
|
|
1300
|
+
this.observeHostSize();
|
|
1232
1301
|
this.bind();
|
|
1233
1302
|
}
|
|
1303
|
+
/// The control creates the canvas, so it keeps it the size of the element it was mounted in.
|
|
1304
|
+
/// A host that lays its panels out after construction - a docking shell, a tab that opens
|
|
1305
|
+
/// hidden - measures nothing at that point, and the fallback size above would then outlive
|
|
1306
|
+
/// the layout: a strip of the panel that draws nothing and answers no clicks. resize()
|
|
1307
|
+
/// ignores anything smaller than two pixels, so a panel switched away from keeps its view.
|
|
1308
|
+
observeHostSize() {
|
|
1309
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
1310
|
+
const observer = new ResizeObserver(() => this.resize(this.host.clientWidth, this.host.clientHeight));
|
|
1311
|
+
observer.observe(this.host);
|
|
1312
|
+
this.domDisposables.push(() => observer.disconnect());
|
|
1313
|
+
}
|
|
1234
1314
|
// ---- events -----------------------------------------------------
|
|
1235
1315
|
on(ev, h) {
|
|
1236
1316
|
let set = this.handlers.get(ev);
|
|
@@ -2397,8 +2477,9 @@ var SSDiagramHeadless = (() => {
|
|
|
2397
2477
|
n2.h = Math.max(48, rows * PORT_ROW_H + 16);
|
|
2398
2478
|
const off = PORT_SQ / 2;
|
|
2399
2479
|
const place = (ports, x) => {
|
|
2400
|
-
const
|
|
2401
|
-
|
|
2480
|
+
const stack = ports.some(isGrowAnchor) ? [...ports.filter((p) => !isGrowAnchor(p)), ...ports.filter(isGrowAnchor)] : ports;
|
|
2481
|
+
const startY = n2.y + (n2.h - stack.length * PORT_ROW_H) / 2 + PORT_ROW_H / 2;
|
|
2482
|
+
stack.forEach((p, i) => {
|
|
2402
2483
|
p.cx = x;
|
|
2403
2484
|
p.cy = startY + i * PORT_ROW_H;
|
|
2404
2485
|
});
|
|
@@ -2415,6 +2496,26 @@ var SSDiagramHeadless = (() => {
|
|
|
2415
2496
|
toWorld(sx, sy) {
|
|
2416
2497
|
return [(sx - this.offX) / this.scale, (sy - this.offY) / this.scale];
|
|
2417
2498
|
}
|
|
2499
|
+
/// Type of the output wired into each connected input. First link wins, which is the one
|
|
2500
|
+
/// drawn first, so a socket accepting several links agrees with the wire on top.
|
|
2501
|
+
feedTypes() {
|
|
2502
|
+
const feed = /* @__PURE__ */ new Map();
|
|
2503
|
+
for (const l of this.links) {
|
|
2504
|
+
const from = this.findNode(l.from)?.outPorts.find((p) => p.id === l.fromPort);
|
|
2505
|
+
if (from === void 0 || isWildcardPortType(from.type)) continue;
|
|
2506
|
+
const to = this.findNode(l.to)?.inPorts.find((p) => p.id === l.toPort);
|
|
2507
|
+
if (to === void 0 || feed.has(to)) continue;
|
|
2508
|
+
feed.set(to, from.type);
|
|
2509
|
+
}
|
|
2510
|
+
return feed;
|
|
2511
|
+
}
|
|
2512
|
+
/// Colour type of a socket: its own, unless it is a wildcard input with something wired in.
|
|
2513
|
+
/// A wildcard promises to take whatever arrives, so once something has arrived the socket
|
|
2514
|
+
/// shows what that is. An untyped socket ('') promises nothing and keeps its neutral fill.
|
|
2515
|
+
portFillType(p) {
|
|
2516
|
+
if (p.direction !== "in" || p.type === "" || !isWildcardPortType(p.type)) return p.type;
|
|
2517
|
+
return this.portFeed.get(p) ?? p.type;
|
|
2518
|
+
}
|
|
2418
2519
|
portColor(type) {
|
|
2419
2520
|
const maxL = this.opts.linkMaxLightness;
|
|
2420
2521
|
const light = maxL !== void 0 && maxL < 0.5;
|
|
@@ -2755,10 +2856,36 @@ var SSDiagramHeadless = (() => {
|
|
|
2755
2856
|
}
|
|
2756
2857
|
this.lpStart = null;
|
|
2757
2858
|
}
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2859
|
+
/// Ends a node drag the way releasing the pointer does: the whole multi-node move becomes
|
|
2860
|
+
/// one undo step, and every node that actually moved is announced. Nodes are moved in place
|
|
2861
|
+
/// as the pointer travels, so a caller that clears the drag without coming through here
|
|
2862
|
+
/// leaves them where the drag put them with nothing in the history and the host unaware.
|
|
2863
|
+
commitNodeDrag() {
|
|
2864
|
+
if (this.dragNode === null) return;
|
|
2865
|
+
const moves = this.dragStart.map((it) => ({ id: it.n.id, fromX: it.x, fromY: it.y, toX: it.n.x, toY: it.n.y })).filter((m) => m.fromX !== m.toX || m.fromY !== m.toY);
|
|
2866
|
+
const moved = new Set(moves.map((m) => m.id));
|
|
2867
|
+
for (const it of this.dragStart) {
|
|
2868
|
+
if (moved.has(it.n.id)) this.emit("nodeMoved", { node: it.n });
|
|
2869
|
+
}
|
|
2870
|
+
this.dragNode = null;
|
|
2871
|
+
this.dragStart = [];
|
|
2872
|
+
if (moves.length > 0) {
|
|
2873
|
+
this.record({
|
|
2874
|
+
do: () => {
|
|
2875
|
+
for (const m of moves) this.doMoveNode(m.id, m.toX, m.toY);
|
|
2876
|
+
},
|
|
2877
|
+
undo: () => {
|
|
2878
|
+
for (const m of moves) this.doMoveNode(m.id, m.fromX, m.fromY);
|
|
2879
|
+
},
|
|
2880
|
+
label: "drag"
|
|
2881
|
+
});
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2884
|
+
// Hit-test at screen coords and emit a contextMenu event. Ends whatever gesture was in
|
|
2885
|
+
// flight — once the menu opens we don't want a half-drag racing it — and a node drag ends
|
|
2886
|
+
// properly rather than being dropped, because its nodes have already moved.
|
|
2761
2887
|
fireContextMenu(sx, sy, pageX, pageY) {
|
|
2888
|
+
this.commitNodeDrag();
|
|
2762
2889
|
const [wx, wy] = this.toWorld(sx, sy);
|
|
2763
2890
|
const port = this.portAt(wx, wy);
|
|
2764
2891
|
const node = port?.node ?? this.nodeAt(wx, wy);
|
|
@@ -2775,11 +2902,14 @@ var SSDiagramHeadless = (() => {
|
|
|
2775
2902
|
this.dragNode = null;
|
|
2776
2903
|
this.dragStart = [];
|
|
2777
2904
|
this.rubber = null;
|
|
2778
|
-
this.panning = false;
|
|
2779
2905
|
this.linking = null;
|
|
2780
2906
|
this.relinking = null;
|
|
2781
2907
|
this.relinkCandidate = null;
|
|
2782
2908
|
this.linkSnap = null;
|
|
2909
|
+
const viewportMoved = this.panning || this.ovDragging;
|
|
2910
|
+
this.panning = false;
|
|
2911
|
+
this.ovDragging = false;
|
|
2912
|
+
if (viewportMoved) this.emitViewChanged(false);
|
|
2783
2913
|
this.clearHover();
|
|
2784
2914
|
this.emit("contextMenu", { x: pageX, y: pageY, link, node, port });
|
|
2785
2915
|
}
|
|
@@ -3021,26 +3151,7 @@ var SSDiagramHeadless = (() => {
|
|
|
3021
3151
|
const finish = (e) => {
|
|
3022
3152
|
this.cancelLongPress();
|
|
3023
3153
|
this.relinkCandidate = null;
|
|
3024
|
-
|
|
3025
|
-
const moves = this.dragStart.map((it) => ({ id: it.n.id, fromX: it.x, fromY: it.y, toX: it.n.x, toY: it.n.y })).filter((m) => m.fromX !== m.toX || m.fromY !== m.toY);
|
|
3026
|
-
const moved = new Set(moves.map((m) => m.id));
|
|
3027
|
-
for (const it of this.dragStart) {
|
|
3028
|
-
if (moved.has(it.n.id)) this.emit("nodeMoved", { node: it.n });
|
|
3029
|
-
}
|
|
3030
|
-
this.dragNode = null;
|
|
3031
|
-
this.dragStart = [];
|
|
3032
|
-
if (moves.length > 0) {
|
|
3033
|
-
this.record({
|
|
3034
|
-
do: () => {
|
|
3035
|
-
for (const m of moves) this.doMoveNode(m.id, m.toX, m.toY);
|
|
3036
|
-
},
|
|
3037
|
-
undo: () => {
|
|
3038
|
-
for (const m of moves) this.doMoveNode(m.id, m.fromX, m.fromY);
|
|
3039
|
-
},
|
|
3040
|
-
label: "drag"
|
|
3041
|
-
});
|
|
3042
|
-
}
|
|
3043
|
-
}
|
|
3154
|
+
this.commitNodeDrag();
|
|
3044
3155
|
if (this.rubber !== null) {
|
|
3045
3156
|
const rx0 = Math.min(this.rubber.x0, this.rubber.x);
|
|
3046
3157
|
const ry0 = Math.min(this.rubber.y0, this.rubber.y);
|
|
@@ -3124,12 +3235,16 @@ var SSDiagramHeadless = (() => {
|
|
|
3124
3235
|
}
|
|
3125
3236
|
this.zoomToFit();
|
|
3126
3237
|
});
|
|
3127
|
-
|
|
3238
|
+
const openContextMenu = (e) => {
|
|
3128
3239
|
e.preventDefault();
|
|
3129
3240
|
if (!this.permissions.inspect) return;
|
|
3130
3241
|
const [sx, sy] = localXY(e);
|
|
3131
3242
|
this.cancelLongPress();
|
|
3132
3243
|
this.fireContextMenu(sx, sy, e.clientX, e.clientY);
|
|
3244
|
+
};
|
|
3245
|
+
this.listen(this.canvas, "contextmenu", openContextMenu);
|
|
3246
|
+
this.listen(this.host, "contextmenu", (e) => {
|
|
3247
|
+
if (e.target !== this.canvas) openContextMenu(e);
|
|
3133
3248
|
});
|
|
3134
3249
|
let pinchDist = 0;
|
|
3135
3250
|
let pinchScale = 1;
|
|
@@ -3285,6 +3400,7 @@ var SSDiagramHeadless = (() => {
|
|
|
3285
3400
|
}
|
|
3286
3401
|
}
|
|
3287
3402
|
if (options.transient && (this.linking !== null || this.relinking !== null)) this.drawPendingLink();
|
|
3403
|
+
this.portFeed = this.feedTypes();
|
|
3288
3404
|
for (const n2 of this.nodes) this.drawNode(n2, options.selection && this.selectedNodes.has(n2), options);
|
|
3289
3405
|
if (options.selection && this.permissions.createLinks && this.relinking === null)
|
|
3290
3406
|
this.drawSelectedLinkEndpoints();
|
|
@@ -3539,7 +3655,7 @@ ${runtime.error}`;
|
|
|
3539
3655
|
}
|
|
3540
3656
|
}
|
|
3541
3657
|
const titleShift = n2.icon ? iconW + 4 : 0;
|
|
3542
|
-
ctx.fillStyle = hasLoadError ? "#ffffff" : "#
|
|
3658
|
+
ctx.fillStyle = hasLoadError ? "#ffffff" : readableTextOn(active ? "#ffd1dc" : n2.color);
|
|
3543
3659
|
ctx.font = "600 12px Segoe UI, Tahoma, sans-serif";
|
|
3544
3660
|
ctx.textAlign = "center";
|
|
3545
3661
|
ctx.textBaseline = "middle";
|
|
@@ -3562,7 +3678,7 @@ ${runtime.error}`;
|
|
|
3562
3678
|
ctx.fill();
|
|
3563
3679
|
}
|
|
3564
3680
|
roundRect(ctx, x, y, s, s, r);
|
|
3565
|
-
ctx.fillStyle = this.portColor(p
|
|
3681
|
+
ctx.fillStyle = this.portColor(this.portFillType(p));
|
|
3566
3682
|
ctx.fill();
|
|
3567
3683
|
ctx.lineWidth = magnet ? 1.5 : 1;
|
|
3568
3684
|
ctx.strokeStyle = magnet ? "#ffffff" : "rgba(12,12,16,0.55)";
|