@stocksharp/diagram 1.2.1 → 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 +105 -35
- package/dist/esm/canvas-renderer.js.map +1 -1
- 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/ssdiagram-headless.js +81 -28
- package/dist/ssdiagram-headless.js.map +2 -2
- package/dist/ssdiagram.js +92 -28
- package/dist/ssdiagram.js.map +2 -2
- package/dist/types/canvas-renderer.d.ts +5 -0
- package/dist/types/canvas-renderer.d.ts.map +1 -1
- package/dist/types/diagram/stocksharp-diagram.d.ts +8 -0
- package/dist/types/diagram/stocksharp-diagram.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/canvas-renderer.ts +103 -32
- package/src/diagram/context-menu.ts +1 -1
- package/src/diagram/stocksharp-diagram.ts +11 -0
package/dist/ssdiagram.js
CHANGED
|
@@ -1104,6 +1104,9 @@ var SSDiagram = (() => {
|
|
|
1104
1104
|
const normalized = normalizePortType(type);
|
|
1105
1105
|
return normalized === "" || normalized === "*" || normalized === "any" || normalized === "anydata" || normalized === "any data" || normalized === "object" || normalized === "system.object" || normalized.startsWith("system.object,");
|
|
1106
1106
|
}
|
|
1107
|
+
function isGrowAnchor(port) {
|
|
1108
|
+
return port.isDynamic && !port.isSibling;
|
|
1109
|
+
}
|
|
1107
1110
|
function arePortTypesCompatible(fromType, toType, availableTypes) {
|
|
1108
1111
|
if (isWildcardPortType(fromType) || isWildcardPortType(toType)) return true;
|
|
1109
1112
|
const normalizedFrom = normalizePortType(fromType);
|
|
@@ -1311,6 +1314,8 @@ var SSDiagram = (() => {
|
|
|
1311
1314
|
this.hoverPort = null;
|
|
1312
1315
|
this.hoverNode = null;
|
|
1313
1316
|
this.hoveredLink = null;
|
|
1317
|
+
// Type feeding each wired input, keyed by node and port, rebuilt for every frame.
|
|
1318
|
+
this.portFeed = /* @__PURE__ */ new Map();
|
|
1314
1319
|
this.tipTimer = null;
|
|
1315
1320
|
this.tipShow = false;
|
|
1316
1321
|
this.tipTarget = null;
|
|
@@ -1353,8 +1358,20 @@ var SSDiagram = (() => {
|
|
|
1353
1358
|
if (ctx === null) throw new Error("ssdiagram: 2d context unavailable");
|
|
1354
1359
|
this.ctx = ctx;
|
|
1355
1360
|
this.resize(this.host.clientWidth || 800, this.host.clientHeight || 480);
|
|
1361
|
+
this.observeHostSize();
|
|
1356
1362
|
this.bind();
|
|
1357
1363
|
}
|
|
1364
|
+
/// The control creates the canvas, so it keeps it the size of the element it was mounted in.
|
|
1365
|
+
/// A host that lays its panels out after construction - a docking shell, a tab that opens
|
|
1366
|
+
/// hidden - measures nothing at that point, and the fallback size above would then outlive
|
|
1367
|
+
/// the layout: a strip of the panel that draws nothing and answers no clicks. resize()
|
|
1368
|
+
/// ignores anything smaller than two pixels, so a panel switched away from keeps its view.
|
|
1369
|
+
observeHostSize() {
|
|
1370
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
1371
|
+
const observer = new ResizeObserver(() => this.resize(this.host.clientWidth, this.host.clientHeight));
|
|
1372
|
+
observer.observe(this.host);
|
|
1373
|
+
this.domDisposables.push(() => observer.disconnect());
|
|
1374
|
+
}
|
|
1358
1375
|
// ---- events -----------------------------------------------------
|
|
1359
1376
|
on(ev, h) {
|
|
1360
1377
|
let set = this.handlers.get(ev);
|
|
@@ -2521,8 +2538,9 @@ var SSDiagram = (() => {
|
|
|
2521
2538
|
n2.h = Math.max(48, rows * PORT_ROW_H + 16);
|
|
2522
2539
|
const off = PORT_SQ / 2;
|
|
2523
2540
|
const place = (ports, x) => {
|
|
2524
|
-
const
|
|
2525
|
-
|
|
2541
|
+
const stack = ports.some(isGrowAnchor) ? [...ports.filter((p) => !isGrowAnchor(p)), ...ports.filter(isGrowAnchor)] : ports;
|
|
2542
|
+
const startY = n2.y + (n2.h - stack.length * PORT_ROW_H) / 2 + PORT_ROW_H / 2;
|
|
2543
|
+
stack.forEach((p, i) => {
|
|
2526
2544
|
p.cx = x;
|
|
2527
2545
|
p.cy = startY + i * PORT_ROW_H;
|
|
2528
2546
|
});
|
|
@@ -2539,6 +2557,26 @@ var SSDiagram = (() => {
|
|
|
2539
2557
|
toWorld(sx, sy) {
|
|
2540
2558
|
return [(sx - this.offX) / this.scale, (sy - this.offY) / this.scale];
|
|
2541
2559
|
}
|
|
2560
|
+
/// Type of the output wired into each connected input. First link wins, which is the one
|
|
2561
|
+
/// drawn first, so a socket accepting several links agrees with the wire on top.
|
|
2562
|
+
feedTypes() {
|
|
2563
|
+
const feed = /* @__PURE__ */ new Map();
|
|
2564
|
+
for (const l of this.links) {
|
|
2565
|
+
const from = this.findNode(l.from)?.outPorts.find((p) => p.id === l.fromPort);
|
|
2566
|
+
if (from === void 0 || isWildcardPortType(from.type)) continue;
|
|
2567
|
+
const to = this.findNode(l.to)?.inPorts.find((p) => p.id === l.toPort);
|
|
2568
|
+
if (to === void 0 || feed.has(to)) continue;
|
|
2569
|
+
feed.set(to, from.type);
|
|
2570
|
+
}
|
|
2571
|
+
return feed;
|
|
2572
|
+
}
|
|
2573
|
+
/// Colour type of a socket: its own, unless it is a wildcard input with something wired in.
|
|
2574
|
+
/// A wildcard promises to take whatever arrives, so once something has arrived the socket
|
|
2575
|
+
/// shows what that is. An untyped socket ('') promises nothing and keeps its neutral fill.
|
|
2576
|
+
portFillType(p) {
|
|
2577
|
+
if (p.direction !== "in" || p.type === "" || !isWildcardPortType(p.type)) return p.type;
|
|
2578
|
+
return this.portFeed.get(p) ?? p.type;
|
|
2579
|
+
}
|
|
2542
2580
|
portColor(type) {
|
|
2543
2581
|
const maxL = this.opts.linkMaxLightness;
|
|
2544
2582
|
const light = maxL !== void 0 && maxL < 0.5;
|
|
@@ -2879,10 +2917,36 @@ var SSDiagram = (() => {
|
|
|
2879
2917
|
}
|
|
2880
2918
|
this.lpStart = null;
|
|
2881
2919
|
}
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2920
|
+
/// Ends a node drag the way releasing the pointer does: the whole multi-node move becomes
|
|
2921
|
+
/// one undo step, and every node that actually moved is announced. Nodes are moved in place
|
|
2922
|
+
/// as the pointer travels, so a caller that clears the drag without coming through here
|
|
2923
|
+
/// leaves them where the drag put them with nothing in the history and the host unaware.
|
|
2924
|
+
commitNodeDrag() {
|
|
2925
|
+
if (this.dragNode === null) return;
|
|
2926
|
+
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);
|
|
2927
|
+
const moved = new Set(moves.map((m) => m.id));
|
|
2928
|
+
for (const it of this.dragStart) {
|
|
2929
|
+
if (moved.has(it.n.id)) this.emit("nodeMoved", { node: it.n });
|
|
2930
|
+
}
|
|
2931
|
+
this.dragNode = null;
|
|
2932
|
+
this.dragStart = [];
|
|
2933
|
+
if (moves.length > 0) {
|
|
2934
|
+
this.record({
|
|
2935
|
+
do: () => {
|
|
2936
|
+
for (const m of moves) this.doMoveNode(m.id, m.toX, m.toY);
|
|
2937
|
+
},
|
|
2938
|
+
undo: () => {
|
|
2939
|
+
for (const m of moves) this.doMoveNode(m.id, m.fromX, m.fromY);
|
|
2940
|
+
},
|
|
2941
|
+
label: "drag"
|
|
2942
|
+
});
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
// Hit-test at screen coords and emit a contextMenu event. Ends whatever gesture was in
|
|
2946
|
+
// flight — once the menu opens we don't want a half-drag racing it — and a node drag ends
|
|
2947
|
+
// properly rather than being dropped, because its nodes have already moved.
|
|
2885
2948
|
fireContextMenu(sx, sy, pageX, pageY) {
|
|
2949
|
+
this.commitNodeDrag();
|
|
2886
2950
|
const [wx, wy] = this.toWorld(sx, sy);
|
|
2887
2951
|
const port = this.portAt(wx, wy);
|
|
2888
2952
|
const node = port?.node ?? this.nodeAt(wx, wy);
|
|
@@ -2899,11 +2963,14 @@ var SSDiagram = (() => {
|
|
|
2899
2963
|
this.dragNode = null;
|
|
2900
2964
|
this.dragStart = [];
|
|
2901
2965
|
this.rubber = null;
|
|
2902
|
-
this.panning = false;
|
|
2903
2966
|
this.linking = null;
|
|
2904
2967
|
this.relinking = null;
|
|
2905
2968
|
this.relinkCandidate = null;
|
|
2906
2969
|
this.linkSnap = null;
|
|
2970
|
+
const viewportMoved = this.panning || this.ovDragging;
|
|
2971
|
+
this.panning = false;
|
|
2972
|
+
this.ovDragging = false;
|
|
2973
|
+
if (viewportMoved) this.emitViewChanged(false);
|
|
2907
2974
|
this.clearHover();
|
|
2908
2975
|
this.emit("contextMenu", { x: pageX, y: pageY, link, node, port });
|
|
2909
2976
|
}
|
|
@@ -3145,26 +3212,7 @@ var SSDiagram = (() => {
|
|
|
3145
3212
|
const finish = (e) => {
|
|
3146
3213
|
this.cancelLongPress();
|
|
3147
3214
|
this.relinkCandidate = null;
|
|
3148
|
-
|
|
3149
|
-
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);
|
|
3150
|
-
const moved = new Set(moves.map((m) => m.id));
|
|
3151
|
-
for (const it of this.dragStart) {
|
|
3152
|
-
if (moved.has(it.n.id)) this.emit("nodeMoved", { node: it.n });
|
|
3153
|
-
}
|
|
3154
|
-
this.dragNode = null;
|
|
3155
|
-
this.dragStart = [];
|
|
3156
|
-
if (moves.length > 0) {
|
|
3157
|
-
this.record({
|
|
3158
|
-
do: () => {
|
|
3159
|
-
for (const m of moves) this.doMoveNode(m.id, m.toX, m.toY);
|
|
3160
|
-
},
|
|
3161
|
-
undo: () => {
|
|
3162
|
-
for (const m of moves) this.doMoveNode(m.id, m.fromX, m.fromY);
|
|
3163
|
-
},
|
|
3164
|
-
label: "drag"
|
|
3165
|
-
});
|
|
3166
|
-
}
|
|
3167
|
-
}
|
|
3215
|
+
this.commitNodeDrag();
|
|
3168
3216
|
if (this.rubber !== null) {
|
|
3169
3217
|
const rx0 = Math.min(this.rubber.x0, this.rubber.x);
|
|
3170
3218
|
const ry0 = Math.min(this.rubber.y0, this.rubber.y);
|
|
@@ -3248,12 +3296,16 @@ var SSDiagram = (() => {
|
|
|
3248
3296
|
}
|
|
3249
3297
|
this.zoomToFit();
|
|
3250
3298
|
});
|
|
3251
|
-
|
|
3299
|
+
const openContextMenu = (e) => {
|
|
3252
3300
|
e.preventDefault();
|
|
3253
3301
|
if (!this.permissions.inspect) return;
|
|
3254
3302
|
const [sx, sy] = localXY(e);
|
|
3255
3303
|
this.cancelLongPress();
|
|
3256
3304
|
this.fireContextMenu(sx, sy, e.clientX, e.clientY);
|
|
3305
|
+
};
|
|
3306
|
+
this.listen(this.canvas, "contextmenu", openContextMenu);
|
|
3307
|
+
this.listen(this.host, "contextmenu", (e) => {
|
|
3308
|
+
if (e.target !== this.canvas) openContextMenu(e);
|
|
3257
3309
|
});
|
|
3258
3310
|
let pinchDist = 0;
|
|
3259
3311
|
let pinchScale = 1;
|
|
@@ -3409,6 +3461,7 @@ var SSDiagram = (() => {
|
|
|
3409
3461
|
}
|
|
3410
3462
|
}
|
|
3411
3463
|
if (options.transient && (this.linking !== null || this.relinking !== null)) this.drawPendingLink();
|
|
3464
|
+
this.portFeed = this.feedTypes();
|
|
3412
3465
|
for (const n2 of this.nodes) this.drawNode(n2, options.selection && this.selectedNodes.has(n2), options);
|
|
3413
3466
|
if (options.selection && this.permissions.createLinks && this.relinking === null)
|
|
3414
3467
|
this.drawSelectedLinkEndpoints();
|
|
@@ -3686,7 +3739,7 @@ ${runtime.error}`;
|
|
|
3686
3739
|
ctx.fill();
|
|
3687
3740
|
}
|
|
3688
3741
|
roundRect(ctx, x, y, s, s, r);
|
|
3689
|
-
ctx.fillStyle = this.portColor(p
|
|
3742
|
+
ctx.fillStyle = this.portColor(this.portFillType(p));
|
|
3690
3743
|
ctx.fill();
|
|
3691
3744
|
ctx.lineWidth = magnet ? 1.5 : 1;
|
|
3692
3745
|
ctx.strokeStyle = magnet ? "#ffffff" : "rgba(12,12,16,0.55)";
|
|
@@ -4528,6 +4581,7 @@ ${runtime.error}`;
|
|
|
4528
4581
|
});
|
|
4529
4582
|
this.on(root, "contextmenu", (event) => {
|
|
4530
4583
|
event.preventDefault();
|
|
4584
|
+
event.stopPropagation();
|
|
4531
4585
|
});
|
|
4532
4586
|
this.container.appendChild(root);
|
|
4533
4587
|
this.root = root;
|
|
@@ -5359,6 +5413,16 @@ ${runtime.error}`;
|
|
|
5359
5413
|
isContextMenuEnabled() {
|
|
5360
5414
|
return this.contextMenu !== null;
|
|
5361
5415
|
}
|
|
5416
|
+
/**
|
|
5417
|
+
* Whether the built-in menu is on screen right now.
|
|
5418
|
+
*
|
|
5419
|
+
* The menu closes itself on Escape. A host that binds the same key - to leave a fullscreen
|
|
5420
|
+
* layout, to close its own dialog - needs to know the key was already spoken for, or one
|
|
5421
|
+
* press does both.
|
|
5422
|
+
*/
|
|
5423
|
+
isContextMenuOpen() {
|
|
5424
|
+
return this.contextMenu?.isOpen ?? false;
|
|
5425
|
+
}
|
|
5362
5426
|
getContextCommands() {
|
|
5363
5427
|
const context = this.contextActionContext();
|
|
5364
5428
|
const state = (command) => {
|