@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
package/dist/ssdiagram.js
CHANGED
|
@@ -796,6 +796,76 @@ var SSDiagram = (() => {
|
|
|
796
796
|
}
|
|
797
797
|
};
|
|
798
798
|
|
|
799
|
+
// src/color.ts
|
|
800
|
+
var CSS_COLOR_KEYWORDS = {
|
|
801
|
+
white: [255, 255, 255],
|
|
802
|
+
black: [0, 0, 0],
|
|
803
|
+
transparent: [255, 255, 255]
|
|
804
|
+
};
|
|
805
|
+
function parseRgb(color) {
|
|
806
|
+
const value = color.trim().toLowerCase();
|
|
807
|
+
if (value === "")
|
|
808
|
+
return null;
|
|
809
|
+
const keyword = CSS_COLOR_KEYWORDS[value];
|
|
810
|
+
if (keyword !== void 0)
|
|
811
|
+
return [...keyword];
|
|
812
|
+
const long = value.match(/^#?([0-9a-f]{6})$/);
|
|
813
|
+
if (long !== null) {
|
|
814
|
+
const n2 = parseInt(long[1], 16);
|
|
815
|
+
return [n2 >> 16 & 255, n2 >> 8 & 255, n2 & 255];
|
|
816
|
+
}
|
|
817
|
+
const short = value.match(/^#?([0-9a-f]{3})([0-9a-f])?$/);
|
|
818
|
+
if (short !== null) {
|
|
819
|
+
const [r, g, b] = [...short[1]].map((digit) => parseInt(digit + digit, 16));
|
|
820
|
+
return [r, g, b];
|
|
821
|
+
}
|
|
822
|
+
const long8 = value.match(/^#?([0-9a-f]{6})[0-9a-f]{2}$/);
|
|
823
|
+
if (long8 !== null) {
|
|
824
|
+
const n2 = parseInt(long8[1], 16);
|
|
825
|
+
return [n2 >> 16 & 255, n2 >> 8 & 255, n2 & 255];
|
|
826
|
+
}
|
|
827
|
+
const rgb = value.match(/^rgba?\(([^)]+)\)$/);
|
|
828
|
+
if (rgb !== null) {
|
|
829
|
+
const parts = rgb[1].split(/[\s,/]+/).filter((part) => part !== "");
|
|
830
|
+
if (parts.length >= 3) {
|
|
831
|
+
const [r, g, b] = parts.slice(0, 3).map((part) => part.endsWith("%") ? parseFloat(part) / 100 * 255 : parseFloat(part));
|
|
832
|
+
if ([r, g, b].every((channel) => Number.isFinite(channel)))
|
|
833
|
+
return [r, g, b];
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return null;
|
|
837
|
+
}
|
|
838
|
+
function luminance(color) {
|
|
839
|
+
const rgb = parseRgb(color);
|
|
840
|
+
if (rgb === null)
|
|
841
|
+
return -1;
|
|
842
|
+
const [r, g, b] = rgb;
|
|
843
|
+
return 0.299 * r + 0.587 * g + 0.114 * b;
|
|
844
|
+
}
|
|
845
|
+
var DARK_TEXT = "#1b1b1b";
|
|
846
|
+
var LIGHT_TEXT = "#f5f5f5";
|
|
847
|
+
function toLinear(channel) {
|
|
848
|
+
const v = channel / 255;
|
|
849
|
+
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
|
850
|
+
}
|
|
851
|
+
function relativeLuminance(color) {
|
|
852
|
+
const rgb = parseRgb(color);
|
|
853
|
+
if (rgb === null)
|
|
854
|
+
return -1;
|
|
855
|
+
const [r, g, b] = rgb;
|
|
856
|
+
return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
|
|
857
|
+
}
|
|
858
|
+
function contrast(a, b) {
|
|
859
|
+
const [lighter, darker] = a > b ? [a, b] : [b, a];
|
|
860
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
861
|
+
}
|
|
862
|
+
function readableTextOn(fill) {
|
|
863
|
+
const background = relativeLuminance(fill);
|
|
864
|
+
if (background < 0)
|
|
865
|
+
return DARK_TEXT;
|
|
866
|
+
return contrast(background, relativeLuminance(LIGHT_TEXT)) > contrast(background, relativeLuminance(DARK_TEXT)) ? LIGHT_TEXT : DARK_TEXT;
|
|
867
|
+
}
|
|
868
|
+
|
|
799
869
|
// src/core/history.ts
|
|
800
870
|
var DiagramCommandHistory = class {
|
|
801
871
|
constructor(listener) {
|
|
@@ -1034,6 +1104,9 @@ var SSDiagram = (() => {
|
|
|
1034
1104
|
const normalized = normalizePortType(type);
|
|
1035
1105
|
return normalized === "" || normalized === "*" || normalized === "any" || normalized === "anydata" || normalized === "any data" || normalized === "object" || normalized === "system.object" || normalized.startsWith("system.object,");
|
|
1036
1106
|
}
|
|
1107
|
+
function isGrowAnchor(port) {
|
|
1108
|
+
return port.isDynamic && !port.isSibling;
|
|
1109
|
+
}
|
|
1037
1110
|
function arePortTypesCompatible(fromType, toType, availableTypes) {
|
|
1038
1111
|
if (isWildcardPortType(fromType) || isWildcardPortType(toType)) return true;
|
|
1039
1112
|
const normalizedFrom = normalizePortType(fromType);
|
|
@@ -1241,6 +1314,8 @@ var SSDiagram = (() => {
|
|
|
1241
1314
|
this.hoverPort = null;
|
|
1242
1315
|
this.hoverNode = null;
|
|
1243
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();
|
|
1244
1319
|
this.tipTimer = null;
|
|
1245
1320
|
this.tipShow = false;
|
|
1246
1321
|
this.tipTarget = null;
|
|
@@ -1283,8 +1358,20 @@ var SSDiagram = (() => {
|
|
|
1283
1358
|
if (ctx === null) throw new Error("ssdiagram: 2d context unavailable");
|
|
1284
1359
|
this.ctx = ctx;
|
|
1285
1360
|
this.resize(this.host.clientWidth || 800, this.host.clientHeight || 480);
|
|
1361
|
+
this.observeHostSize();
|
|
1286
1362
|
this.bind();
|
|
1287
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
|
+
}
|
|
1288
1375
|
// ---- events -----------------------------------------------------
|
|
1289
1376
|
on(ev, h) {
|
|
1290
1377
|
let set = this.handlers.get(ev);
|
|
@@ -2451,8 +2538,9 @@ var SSDiagram = (() => {
|
|
|
2451
2538
|
n2.h = Math.max(48, rows * PORT_ROW_H + 16);
|
|
2452
2539
|
const off = PORT_SQ / 2;
|
|
2453
2540
|
const place = (ports, x) => {
|
|
2454
|
-
const
|
|
2455
|
-
|
|
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) => {
|
|
2456
2544
|
p.cx = x;
|
|
2457
2545
|
p.cy = startY + i * PORT_ROW_H;
|
|
2458
2546
|
});
|
|
@@ -2469,6 +2557,26 @@ var SSDiagram = (() => {
|
|
|
2469
2557
|
toWorld(sx, sy) {
|
|
2470
2558
|
return [(sx - this.offX) / this.scale, (sy - this.offY) / this.scale];
|
|
2471
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
|
+
}
|
|
2472
2580
|
portColor(type) {
|
|
2473
2581
|
const maxL = this.opts.linkMaxLightness;
|
|
2474
2582
|
const light = maxL !== void 0 && maxL < 0.5;
|
|
@@ -2809,10 +2917,36 @@ var SSDiagram = (() => {
|
|
|
2809
2917
|
}
|
|
2810
2918
|
this.lpStart = null;
|
|
2811
2919
|
}
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
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.
|
|
2815
2948
|
fireContextMenu(sx, sy, pageX, pageY) {
|
|
2949
|
+
this.commitNodeDrag();
|
|
2816
2950
|
const [wx, wy] = this.toWorld(sx, sy);
|
|
2817
2951
|
const port = this.portAt(wx, wy);
|
|
2818
2952
|
const node = port?.node ?? this.nodeAt(wx, wy);
|
|
@@ -2829,11 +2963,14 @@ var SSDiagram = (() => {
|
|
|
2829
2963
|
this.dragNode = null;
|
|
2830
2964
|
this.dragStart = [];
|
|
2831
2965
|
this.rubber = null;
|
|
2832
|
-
this.panning = false;
|
|
2833
2966
|
this.linking = null;
|
|
2834
2967
|
this.relinking = null;
|
|
2835
2968
|
this.relinkCandidate = null;
|
|
2836
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);
|
|
2837
2974
|
this.clearHover();
|
|
2838
2975
|
this.emit("contextMenu", { x: pageX, y: pageY, link, node, port });
|
|
2839
2976
|
}
|
|
@@ -3075,26 +3212,7 @@ var SSDiagram = (() => {
|
|
|
3075
3212
|
const finish = (e) => {
|
|
3076
3213
|
this.cancelLongPress();
|
|
3077
3214
|
this.relinkCandidate = null;
|
|
3078
|
-
|
|
3079
|
-
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);
|
|
3080
|
-
const moved = new Set(moves.map((m) => m.id));
|
|
3081
|
-
for (const it of this.dragStart) {
|
|
3082
|
-
if (moved.has(it.n.id)) this.emit("nodeMoved", { node: it.n });
|
|
3083
|
-
}
|
|
3084
|
-
this.dragNode = null;
|
|
3085
|
-
this.dragStart = [];
|
|
3086
|
-
if (moves.length > 0) {
|
|
3087
|
-
this.record({
|
|
3088
|
-
do: () => {
|
|
3089
|
-
for (const m of moves) this.doMoveNode(m.id, m.toX, m.toY);
|
|
3090
|
-
},
|
|
3091
|
-
undo: () => {
|
|
3092
|
-
for (const m of moves) this.doMoveNode(m.id, m.fromX, m.fromY);
|
|
3093
|
-
},
|
|
3094
|
-
label: "drag"
|
|
3095
|
-
});
|
|
3096
|
-
}
|
|
3097
|
-
}
|
|
3215
|
+
this.commitNodeDrag();
|
|
3098
3216
|
if (this.rubber !== null) {
|
|
3099
3217
|
const rx0 = Math.min(this.rubber.x0, this.rubber.x);
|
|
3100
3218
|
const ry0 = Math.min(this.rubber.y0, this.rubber.y);
|
|
@@ -3178,12 +3296,16 @@ var SSDiagram = (() => {
|
|
|
3178
3296
|
}
|
|
3179
3297
|
this.zoomToFit();
|
|
3180
3298
|
});
|
|
3181
|
-
|
|
3299
|
+
const openContextMenu = (e) => {
|
|
3182
3300
|
e.preventDefault();
|
|
3183
3301
|
if (!this.permissions.inspect) return;
|
|
3184
3302
|
const [sx, sy] = localXY(e);
|
|
3185
3303
|
this.cancelLongPress();
|
|
3186
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);
|
|
3187
3309
|
});
|
|
3188
3310
|
let pinchDist = 0;
|
|
3189
3311
|
let pinchScale = 1;
|
|
@@ -3339,6 +3461,7 @@ var SSDiagram = (() => {
|
|
|
3339
3461
|
}
|
|
3340
3462
|
}
|
|
3341
3463
|
if (options.transient && (this.linking !== null || this.relinking !== null)) this.drawPendingLink();
|
|
3464
|
+
this.portFeed = this.feedTypes();
|
|
3342
3465
|
for (const n2 of this.nodes) this.drawNode(n2, options.selection && this.selectedNodes.has(n2), options);
|
|
3343
3466
|
if (options.selection && this.permissions.createLinks && this.relinking === null)
|
|
3344
3467
|
this.drawSelectedLinkEndpoints();
|
|
@@ -3593,7 +3716,7 @@ ${runtime.error}`;
|
|
|
3593
3716
|
}
|
|
3594
3717
|
}
|
|
3595
3718
|
const titleShift = n2.icon ? iconW + 4 : 0;
|
|
3596
|
-
ctx.fillStyle = hasLoadError ? "#ffffff" : "#
|
|
3719
|
+
ctx.fillStyle = hasLoadError ? "#ffffff" : readableTextOn(active ? "#ffd1dc" : n2.color);
|
|
3597
3720
|
ctx.font = "600 12px Segoe UI, Tahoma, sans-serif";
|
|
3598
3721
|
ctx.textAlign = "center";
|
|
3599
3722
|
ctx.textBaseline = "middle";
|
|
@@ -3616,7 +3739,7 @@ ${runtime.error}`;
|
|
|
3616
3739
|
ctx.fill();
|
|
3617
3740
|
}
|
|
3618
3741
|
roundRect(ctx, x, y, s, s, r);
|
|
3619
|
-
ctx.fillStyle = this.portColor(p
|
|
3742
|
+
ctx.fillStyle = this.portColor(this.portFillType(p));
|
|
3620
3743
|
ctx.fill();
|
|
3621
3744
|
ctx.lineWidth = magnet ? 1.5 : 1;
|
|
3622
3745
|
ctx.strokeStyle = magnet ? "#ffffff" : "rgba(12,12,16,0.55)";
|
|
@@ -4458,6 +4581,7 @@ ${runtime.error}`;
|
|
|
4458
4581
|
});
|
|
4459
4582
|
this.on(root, "contextmenu", (event) => {
|
|
4460
4583
|
event.preventDefault();
|
|
4584
|
+
event.stopPropagation();
|
|
4461
4585
|
});
|
|
4462
4586
|
this.container.appendChild(root);
|
|
4463
4587
|
this.root = root;
|
|
@@ -5289,6 +5413,16 @@ ${runtime.error}`;
|
|
|
5289
5413
|
isContextMenuEnabled() {
|
|
5290
5414
|
return this.contextMenu !== null;
|
|
5291
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
|
+
}
|
|
5292
5426
|
getContextCommands() {
|
|
5293
5427
|
const context = this.contextActionContext();
|
|
5294
5428
|
const state = (command) => {
|
|
@@ -6079,44 +6213,6 @@ ${runtime.error}`;
|
|
|
6079
6213
|
function iconUrl(name) {
|
|
6080
6214
|
return name ? `/diagram-icons/${name}.svg` : "";
|
|
6081
6215
|
}
|
|
6082
|
-
var CSS_COLOR_KEYWORDS = {
|
|
6083
|
-
white: [255, 255, 255],
|
|
6084
|
-
black: [0, 0, 0],
|
|
6085
|
-
transparent: [255, 255, 255]
|
|
6086
|
-
};
|
|
6087
|
-
function luminance(color) {
|
|
6088
|
-
const value = color.trim().toLowerCase();
|
|
6089
|
-
if (value === "")
|
|
6090
|
-
return -1;
|
|
6091
|
-
const keyword = CSS_COLOR_KEYWORDS[value];
|
|
6092
|
-
if (keyword !== void 0)
|
|
6093
|
-
return 0.299 * keyword[0] + 0.587 * keyword[1] + 0.114 * keyword[2];
|
|
6094
|
-
const long = value.match(/^#?([0-9a-f]{6})$/);
|
|
6095
|
-
if (long !== null) {
|
|
6096
|
-
const n2 = parseInt(long[1], 16);
|
|
6097
|
-
return 0.299 * (n2 >> 16 & 255) + 0.587 * (n2 >> 8 & 255) + 0.114 * (n2 & 255);
|
|
6098
|
-
}
|
|
6099
|
-
const short = value.match(/^#?([0-9a-f]{3})([0-9a-f])?$/);
|
|
6100
|
-
if (short !== null) {
|
|
6101
|
-
const [r, g, b] = [...short[1]].map((digit) => parseInt(digit + digit, 16));
|
|
6102
|
-
return 0.299 * r + 0.587 * g + 0.114 * b;
|
|
6103
|
-
}
|
|
6104
|
-
const long8 = value.match(/^#?([0-9a-f]{6})[0-9a-f]{2}$/);
|
|
6105
|
-
if (long8 !== null) {
|
|
6106
|
-
const n2 = parseInt(long8[1], 16);
|
|
6107
|
-
return 0.299 * (n2 >> 16 & 255) + 0.587 * (n2 >> 8 & 255) + 0.114 * (n2 & 255);
|
|
6108
|
-
}
|
|
6109
|
-
const rgb = value.match(/^rgba?\(([^)]+)\)$/);
|
|
6110
|
-
if (rgb !== null) {
|
|
6111
|
-
const parts = rgb[1].split(/[\s,/]+/).filter((part) => part !== "");
|
|
6112
|
-
if (parts.length >= 3) {
|
|
6113
|
-
const [r, g, b] = parts.slice(0, 3).map((part) => part.endsWith("%") ? parseFloat(part) / 100 * 255 : parseFloat(part));
|
|
6114
|
-
if ([r, g, b].every((channel) => Number.isFinite(channel)))
|
|
6115
|
-
return 0.299 * r + 0.587 * g + 0.114 * b;
|
|
6116
|
-
}
|
|
6117
|
-
}
|
|
6118
|
-
return -1;
|
|
6119
|
-
}
|
|
6120
6216
|
function makePort(p) {
|
|
6121
6217
|
return new Port({
|
|
6122
6218
|
id: p.key,
|