@stocksharp/diagram 0.1.0 → 0.2.0

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/README.md CHANGED
@@ -11,6 +11,7 @@ renderer, read-only web embed, and versioned diagram document model.
11
11
  ![StockSharp JS Strategy Diagram — visual strategy editor with typed connections and element palette](sample.png)
12
12
 
13
13
  [Live demo](https://stocksharp.github.io/Diagram/demo/) ·
14
+ [StockSharp website](https://stocksharp.com/) ·
14
15
  [GitHub repository](https://github.com/StockSharp/Diagram) ·
15
16
  [Issue tracker](https://github.com/StockSharp/Diagram/issues)
16
17
 
@@ -312,6 +313,9 @@ npm run build
312
313
  npm run serve
313
314
  npm run pack:check
314
315
  npm run release:check -- v0.1.0
316
+ npm run release:patch
317
+ npm run release:minor
318
+ npm run release:major
315
319
  npm run api:check
316
320
  npm run api:update # only after reviewing an intentional public API change
317
321
  npm run test:browser
@@ -326,10 +330,19 @@ CI verifies type checking, the reviewed declaration snapshot, unit/integration
326
330
  tests, Chromium smoke/interaction/lifecycle checks, all bundles and tarball
327
331
  contents. GitHub Pages publishes the demo from `main`.
328
332
 
329
- Publishing is release-driven. Set `package.json` to the intended version and
330
- publish a GitHub Release tagged `v<version>`. The `release.yml` workflow rejects
331
- a mismatched tag, rebuilds and tests the repository, attaches the exact `.tgz`
332
- artifact to the release, and publishes that tarball to npm with provenance.
333
+ Publishing is tag-driven. Create the version commit and tag, then push both:
334
+
335
+ ```sh
336
+ npm run release:patch
337
+ git push origin main --follow-tags
338
+ ```
339
+
340
+ Use `release:minor` or `release:major` when appropriate. A pushed `v<version>`
341
+ tag starts `release.yml`, which rejects a mismatched version, rebuilds and tests
342
+ the repository, creates the GitHub Release, attaches the exact `.tgz` artifact,
343
+ and publishes that tarball to npm with provenance. A failed publication can be
344
+ retried for the existing tag through the workflow's `Run workflow` action;
345
+ already-published versions are detected and skipped.
333
346
 
334
347
  The first publication needs a short-lived npm granular access token in the
335
348
  repository Actions secret `NPM_TOKEN`. After `@stocksharp/diagram` exists on npm,
@@ -170,6 +170,7 @@ const RELINK_DRAG_THRESHOLD_PX = 4;
170
170
  const PORT_SQ = 9; // socket square size — sits outside the node
171
171
  const LINK_STUB = 22; // min horizontal lead-in/out before the elbow
172
172
  const LINK_HOP = 5; // jump-over arc radius at crossings
173
+ const LINK_CORNER = 8; // corner rounding radius at elbows
173
174
  const SNAP_PX = 32; // plug↔socket magnet radius (screen px)
174
175
  const DEFAULT_GRID_SIZE = 28;
175
176
  const ZOOM_MIN = 0.15;
@@ -1345,6 +1346,8 @@ export class Diagram {
1345
1346
  this.opts.gridColor = t.gridColor;
1346
1347
  if (t.linkMaxLightness !== undefined)
1347
1348
  this.opts.linkMaxLightness = t.linkMaxLightness;
1349
+ if (t.typedLinkColors !== undefined)
1350
+ this.opts.typedLinkColors = t.typedLinkColors;
1348
1351
  if (t.overviewBackground !== undefined)
1349
1352
  this.opts.overviewBackground = t.overviewBackground;
1350
1353
  if (t.overviewBorderColor !== undefined)
@@ -1455,6 +1458,16 @@ export class Diagram {
1455
1458
  const base = map[type] ?? `hsl(${hashHue(type)}, 62%, 58%)`;
1456
1459
  return maxL !== undefined ? clampLightness(base, maxL) : base;
1457
1460
  }
1461
+ // Selection/hover accent. The dark-canvas blue washes out on a light canvas, so a light theme
1462
+ // (linkMaxLightness < 0.5) inverts to a darker, more saturated blue that stays clearly visible.
1463
+ selectionColor() {
1464
+ const maxL = this.opts.linkMaxLightness;
1465
+ return maxL !== undefined && maxL < 0.5 ? '#0d6efd' : '#4aa3ff';
1466
+ }
1467
+ linkHoverColor() {
1468
+ const maxL = this.opts.linkMaxLightness;
1469
+ return maxL !== undefined && maxL < 0.5 ? '#6aa9f0' : '#cfe3ff';
1470
+ }
1458
1471
  // ---- hit testing (world coords) ---------------------------------
1459
1472
  portAt(wx, wy) {
1460
1473
  for (let i = this.nodes.length - 1; i >= 0; i -= 1) {
@@ -2379,14 +2392,16 @@ export class Diagram {
2379
2392
  if (a === null || b === null)
2380
2393
  continue;
2381
2394
  const fp = this.nodes.find((n) => n.id === l.from)?.outPorts.find((p) => p.id === l.fromPort);
2382
- const baseColor = fp ? this.portColor(fp.type) : '#7d828a';
2395
+ const baseColor = (this.opts.typedLinkColors ?? false) && fp !== undefined
2396
+ ? this.portColor(fp.type)
2397
+ : this.portColor('');
2383
2398
  const state = options.selection && l === this.selectedLink
2384
2399
  ? 'sel'
2385
2400
  : options.transient && (l === this.hoveredLink
2386
2401
  || (hoveredNode !== null && (l.from === hoveredNode.id || l.to === hoveredNode.id)))
2387
2402
  ? 'hov'
2388
2403
  : 'norm';
2389
- const color = state === 'sel' ? '#4aa3ff' : state === 'hov' ? '#cfe3ff' : baseColor;
2404
+ const color = state === 'sel' ? this.selectionColor() : state === 'hov' ? this.linkHoverColor() : baseColor;
2390
2405
  const width = state === 'sel' ? 3 : state === 'hov' ? 2.6 : 2;
2391
2406
  const pts = this.routeLink(a, b, new Set([l.from, l.to]));
2392
2407
  this.strokeRoute(pts, color, width, prior);
@@ -2599,7 +2614,7 @@ export class Diagram {
2599
2614
  ctx.fillStyle = hasLoadError ? ERROR_BACKGROUND : active ? '#ffd1dc' : n.color;
2600
2615
  ctx.fill();
2601
2616
  ctx.lineWidth = selected ? 2 : 1.5;
2602
- ctx.strokeStyle = selected ? '#4aa3ff' : n.border;
2617
+ ctx.strokeStyle = selected ? this.selectionColor() : n.border;
2603
2618
  ctx.stroke();
2604
2619
  if (hasLoadError || hasRuntimeError) {
2605
2620
  let alpha = 1;
@@ -2706,6 +2721,20 @@ export class Diagram {
2706
2721
  ctx.beginPath();
2707
2722
  ctx.moveTo(pts[0][0], pts[0][1]);
2708
2723
  const H = LINK_HOP;
2724
+ // End a segment at its elbow with a rounded corner toward the next point (arcTo), clamped so it never
2725
+ // eats more than half of either adjacent segment. The final segment ends straight into the port.
2726
+ const finish = (i, x1, y1, x2, y2) => {
2727
+ if (i < pts.length - 1) {
2728
+ const nx = pts[i + 1][0];
2729
+ const ny = pts[i + 1][1];
2730
+ const seg1 = Math.abs(x2 - x1) + Math.abs(y2 - y1);
2731
+ const seg2 = Math.abs(nx - x2) + Math.abs(ny - y2);
2732
+ ctx.arcTo(x2, y2, nx, ny, Math.min(LINK_CORNER, seg1 / 2, seg2 / 2));
2733
+ }
2734
+ else {
2735
+ ctx.lineTo(x2, y2);
2736
+ }
2737
+ };
2709
2738
  for (let i = 1; i < pts.length; i += 1) {
2710
2739
  const [x1, y1] = pts[i - 1];
2711
2740
  const [x2, y2] = pts[i];
@@ -2723,7 +2752,7 @@ export class Diagram {
2723
2752
  else
2724
2753
  ctx.arc(cx, y1, H, 0, Math.PI, true);
2725
2754
  }
2726
- ctx.lineTo(x2, y2);
2755
+ finish(i, x1, y1, x2, y2);
2727
2756
  }
2728
2757
  else if (x1 === x2 && y1 !== y2) {
2729
2758
  const dir = y2 > y1 ? 1 : -1;
@@ -2739,7 +2768,7 @@ export class Diagram {
2739
2768
  else
2740
2769
  ctx.arc(x1, cy, H, Math.PI / 2, -Math.PI / 2, true);
2741
2770
  }
2742
- ctx.lineTo(x2, y2);
2771
+ finish(i, x1, y1, x2, y2);
2743
2772
  }
2744
2773
  else {
2745
2774
  ctx.lineTo(x2, y2);