@elyra/canvas 13.47.0 → 13.47.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.
Files changed (40) hide show
  1. package/dist/Close-10f1a909.js +2 -0
  2. package/dist/{Close-ec5c2552.js.map → Close-10f1a909.js.map} +1 -1
  3. package/dist/{Close-45a7703f.js → Close-c106c4d1.js} +2 -2
  4. package/dist/{Close-45a7703f.js.map → Close-c106c4d1.js.map} +1 -1
  5. package/dist/{canvas-controller-035b844d.js → canvas-controller-4a117bab.js} +2 -2
  6. package/dist/{canvas-controller-035b844d.js.map → canvas-controller-4a117bab.js.map} +1 -1
  7. package/dist/{canvas-controller-28eb66c7.js → canvas-controller-7077c820.js} +2 -2
  8. package/dist/{canvas-controller-28eb66c7.js.map → canvas-controller-7077c820.js.map} +1 -1
  9. package/dist/common-canvas-ad71b995.js +2 -0
  10. package/dist/common-canvas-ad71b995.js.map +1 -0
  11. package/dist/common-canvas-e6bd5796.js +2 -0
  12. package/dist/common-canvas-e6bd5796.js.map +1 -0
  13. package/dist/common-canvas.cjs +1 -1
  14. package/dist/common-canvas.js +1 -1
  15. package/dist/{common-properties-7744267c.js → common-properties-e6044a7e.js} +2 -2
  16. package/dist/{common-properties-7744267c.js.map → common-properties-e6044a7e.js.map} +1 -1
  17. package/dist/{common-properties-532a5a7e.js → common-properties-fd7dd5f2.js} +2 -2
  18. package/dist/{common-properties-532a5a7e.js.map → common-properties-fd7dd5f2.js.map} +1 -1
  19. package/dist/lib/canvas-controller.cjs +1 -1
  20. package/dist/lib/canvas-controller.js +1 -1
  21. package/dist/lib/canvas.cjs +1 -1
  22. package/dist/lib/canvas.js +1 -1
  23. package/dist/lib/properties.cjs +1 -1
  24. package/dist/lib/properties.js +1 -1
  25. package/dist/styles/common-canvas.min.css +1 -1
  26. package/dist/styles/common-canvas.min.css.map +1 -1
  27. package/package.json +2 -2
  28. package/src/common-canvas/svg-canvas-d3.scss +10 -6
  29. package/src/common-canvas/svg-canvas-renderer.js +8 -189
  30. package/src/common-canvas/svg-canvas-utils-nodes.js +227 -0
  31. package/src/object-model/layout-dimensions.js +2 -2
  32. package/src/palette/palette.scss +3 -2
  33. package/src/toolbar/toolbar.jsx +20 -8
  34. package/src/toolbar/toolbar.scss +1 -1
  35. package/stats.html +1 -1
  36. package/dist/Close-ec5c2552.js +0 -2
  37. package/dist/common-canvas-7076af7a.js +0 -2
  38. package/dist/common-canvas-7076af7a.js.map +0 -1
  39. package/dist/common-canvas-f222fa61.js +0 -2
  40. package/dist/common-canvas-f222fa61.js.map +0 -1
@@ -473,4 +473,231 @@ export default class SvgCanvasNodes {
473
473
  });
474
474
  return node;
475
475
  }
476
+
477
+ /**
478
+ * Returns an SVG path that draws the shape for a rectangle node. This is
479
+ * drawn as a path rather than an SVG rect element to keep calling code generic.
480
+ * highlightGap may be a number (uniform gap on all sides) or an object with
481
+ * leftGap, rightGap, topGap, bottomGap properties for per-side control.
482
+ *
483
+ * @param {Object} data - Node data with width and height properties.
484
+ * @param {number|Object} highlightGap - Outward inflation applied to all sides.
485
+ * @returns {string} SVG path string.
486
+ */
487
+ static getRectangleNodeShapePath(data, highlightGap = 0) {
488
+ const gaps = typeof highlightGap === "object"
489
+ ? highlightGap
490
+ : {
491
+ leftGap: highlightGap,
492
+ rightGap: highlightGap,
493
+ topGap: highlightGap,
494
+ bottomGap: highlightGap
495
+ };
496
+
497
+ const l = 0 - gaps.leftGap;
498
+ const t = 0 - gaps.topGap;
499
+ const r = data.width + gaps.rightGap;
500
+ const b = data.height + gaps.bottomGap;
501
+
502
+ return "M " + l + " " + t + " L " + r + " " + t + " " + r + " " + b + " " + l + " " + b + " Z";
503
+ }
504
+
505
+ /**
506
+ * Returns an SVG path that draws a rectangle-with-rounded-corners node shape.
507
+ *
508
+ * @param {Object} data - Node data with width and height properties.
509
+ * @param {number} highlightGap - Uniform outward inflation applied to all sides.
510
+ * @returns {string} SVG path string.
511
+ */
512
+ static getRectRoundCornersShapePath(data, highlightGap = 0) {
513
+ const c = 10; // Corner size
514
+ const l = 0 - highlightGap;
515
+ const t = 0 - highlightGap;
516
+ const r = data.width + highlightGap;
517
+ const b = data.height + highlightGap;
518
+ const lc = l + c;
519
+ const rc = r - c;
520
+ const tc = t + c;
521
+ const bc = b - c;
522
+
523
+ return "M " + lc + " " + t + " L " + rc + " " + t + " " +
524
+ "Q " + r + " " + t + " " + r + " " + tc + " " +
525
+ "L " + r + " " + bc + " " +
526
+ "Q " + r + " " + b + " " + rc + " " + b + " " +
527
+ "L " + lc + " " + b + " " +
528
+ "Q " + l + " " + b + " " + l + " " + bc + " " +
529
+ "L " + l + " " + tc + " " +
530
+ "Q " + l + " " + t + " " + lc + " " + t +
531
+ " Z";
532
+ }
533
+
534
+ /**
535
+ * Returns an SVG path for the 'port-arcs' outline shape that encloses both
536
+ * the node body and all ports regardless of where the ports are positioned.
537
+ * The highlightGap uniformly inflates the outline away from the node and ports.
538
+ *
539
+ * The path is built by walking each side of the inflated node rectangle in
540
+ * order and inserting an outward circular arc bump wherever a port's arc
541
+ * circle protrudes through that side. Port arcs are emitted sorted along the
542
+ * walking direction so the path never doubles back.
543
+ *
544
+ * @param {Object} data - Node data object with width, height, inputs, outputs, layout.
545
+ * @param {number} highlightGap - Additional outward offset applied to the whole outline.
546
+ * @returns {string} SVG path string.
547
+ */
548
+ static getPortArcsNodeShapePath(data, highlightGap = 0) {
549
+ const gap = highlightGap || 0;
550
+ const arcR = data.layout.portArcRadius;
551
+ const allPorts = [...(data.inputs || []), ...(data.outputs || [])];
552
+
553
+ // Inflated rectangle bounds
554
+ const L = -gap;
555
+ const T = -gap;
556
+ const R = data.width + gap;
557
+ const B = data.height + gap;
558
+
559
+ // Returns the two y-intercepts where a port's arc circle crosses a
560
+ // vertical edge at xEdge, or null if it does not reach the edge.
561
+ const verticalEdgeIntercepts = (port, xEdge) => {
562
+ const dx = xEdge - port.cx;
563
+ const disc = (arcR * arcR) - (dx * dx);
564
+ if (disc < 0) {
565
+ return null;
566
+ }
567
+ const half = Math.sqrt(disc);
568
+ return [port.cy - half, port.cy + half];
569
+ };
570
+
571
+ // Returns the two x-intercepts where a port's arc circle crosses a
572
+ // horizontal edge at yEdge, or null if it does not reach the edge.
573
+ const horizEdgeIntercepts = (port, yEdge) => {
574
+ const dy = yEdge - port.cy;
575
+ const disc = (arcR * arcR) - (dy * dy);
576
+ if (disc < 0) {
577
+ return null;
578
+ }
579
+ const half = Math.sqrt(disc);
580
+ return [port.cx - half, port.cx + half];
581
+ };
582
+
583
+ // Builds the segment of the path that walks along a vertical edge
584
+ // (left or right) inserting outward arc bumps for ports that protrude.
585
+ // xEdge - x coordinate of the inflated edge line.
586
+ // fromY/toY - start and end y along the edge (fromY < toY when going
587
+ // downward; toY < fromY when going upward).
588
+ // outwardSweep - SVG arc sweep-flag (1 = clockwise, 0 = anti-clockwise).
589
+ const verticalEdgeWithBumps = (xEdge, fromY, toY, outwardSweep) => {
590
+ const goingDown = toY > fromY;
591
+ const exterior = goingDown ? "right" : "left";
592
+
593
+ // Collect ports whose arc circles protrude through this edge, keeping
594
+ // only those whose centre is on the exterior side (or on the edge
595
+ // itself) so that interior ports don't create outward bumps.
596
+ const bumps = [];
597
+ allPorts.forEach((port) => {
598
+ if (!verticalEdgeIntercepts(port, xEdge)) {
599
+ return;
600
+ }
601
+ const isExterior = exterior === "right"
602
+ ? port.cx >= data.width - data.layout.portArcRadius
603
+ : port.cx <= data.layout.portArcRadius;
604
+ if (!isExterior) {
605
+ return;
606
+ }
607
+ // Arc endpoints are always the full diameter span centred on port.cy,
608
+ // clamped to the edge's travel range so arcs near corners don't overshoot.
609
+ const yMin = Math.min(fromY, toY);
610
+ const yMax = Math.max(fromY, toY);
611
+ const entryY = Math.max(port.cy - arcR, yMin);
612
+ const exitY = Math.min(port.cy + arcR, yMax);
613
+ if (exitY > entryY) {
614
+ bumps.push({ entryY, exitY, port });
615
+ }
616
+ });
617
+
618
+ // Sort bumps in the direction of travel.
619
+ bumps.sort((a, b) => (goingDown ? a.entryY - b.entryY : b.entryY - a.entryY));
620
+
621
+ let segment = "";
622
+ let cursor = fromY;
623
+ bumps.forEach((bump) => {
624
+ const bumpEntry = goingDown ? bump.entryY : bump.exitY;
625
+ const bumpExit = goingDown ? bump.exitY : bump.entryY;
626
+ if (goingDown ? bumpEntry > cursor : bumpEntry < cursor) {
627
+ segment += ` L ${xEdge} ${bumpEntry}`;
628
+ }
629
+ segment += ` A ${arcR} ${arcR} 180 0 ${outwardSweep} ${xEdge} ${bumpExit}`;
630
+ cursor = bumpExit;
631
+ });
632
+
633
+ // Final straight segment to the corner.
634
+ if (goingDown ? cursor < toY : cursor > toY) {
635
+ segment += ` L ${xEdge} ${toY}`;
636
+ }
637
+ return segment;
638
+ };
639
+
640
+ // Builds the segment of the path that walks along a horizontal edge
641
+ // (top or bottom) inserting outward arc bumps for ports that protrude.
642
+ // yEdge - y coordinate of the inflated edge line.
643
+ // fromX/toX - start and end x along the edge.
644
+ // outwardSweep - SVG arc sweep-flag (1 = clockwise, 0 = anti-clockwise).
645
+ const horizEdgeWithBumps = (yEdge, fromX, toX, outwardSweep) => {
646
+ const goingRight = toX > fromX;
647
+ const exterior = goingRight ? "top" : "bottom";
648
+
649
+ const bumps = [];
650
+ allPorts.forEach((port) => {
651
+ if (!horizEdgeIntercepts(port, yEdge)) {
652
+ return;
653
+ }
654
+ const isExterior = exterior === "top"
655
+ ? port.cy <= data.layout.portArcRadius
656
+ : port.cy >= data.height - data.layout.portArcRadius;
657
+ if (!isExterior) {
658
+ return;
659
+ }
660
+ // Arc endpoints are always the full diameter span centred on port.cx,
661
+ // clamped to the edge's travel range so arcs near corners don't overshoot.
662
+ const xMin = Math.min(fromX, toX);
663
+ const xMax = Math.max(fromX, toX);
664
+ const entryX = Math.max(port.cx - arcR, xMin);
665
+ const exitX = Math.min(port.cx + arcR, xMax);
666
+ if (exitX > entryX) {
667
+ bumps.push({ entryX, exitX, port });
668
+ }
669
+ });
670
+
671
+ bumps.sort((a, b) => (goingRight ? a.entryX - b.entryX : b.entryX - a.entryX));
672
+
673
+ let segment = "";
674
+ let cursor = fromX;
675
+ bumps.forEach((bump) => {
676
+ const bumpEntry = goingRight ? bump.entryX : bump.exitX;
677
+ const bumpExit = goingRight ? bump.exitX : bump.entryX;
678
+ if (goingRight ? bumpEntry > cursor : bumpEntry < cursor) {
679
+ segment += ` L ${bumpEntry} ${yEdge}`;
680
+ }
681
+ segment += ` A ${arcR} ${arcR} 180 0 ${outwardSweep} ${bumpExit} ${yEdge}`;
682
+ cursor = bumpExit;
683
+ });
684
+
685
+ if (goingRight ? cursor < toX : cursor > toX) {
686
+ segment += ` L ${toX} ${yEdge}`;
687
+ }
688
+ return segment;
689
+ };
690
+
691
+ // Assemble the full closed path walking clockwise around the inflated rectangle.
692
+ // All four edges use sweep=1 to produce outward-facing bulges. In SVG's y-down
693
+ // coordinate system with the entry/exit reversal logic in horizEdgeWithBumps,
694
+ // sweep=1 consistently bows away from the node centre for all sides.
695
+ let path = `M ${L} ${T}`;
696
+ path += horizEdgeWithBumps(T, L, R, 1); // top edge, left-to-right, bumps upward
697
+ path += verticalEdgeWithBumps(R, T, B, 1); // right edge, top-to-bottom, bumps rightward
698
+ path += horizEdgeWithBumps(B, R, L, 1); // bottom edge, right-to-left, bumps downward
699
+ path += verticalEdgeWithBumps(L, B, T, 1); // left edge, bottom-to-top, bumps leftward
700
+ path += " Z";
701
+ return path;
702
+ }
476
703
  }
@@ -120,7 +120,7 @@ const horizontalDefaultLayout = {
120
120
  dropShadow: true,
121
121
 
122
122
  // The gap between a node and its selection highlight rectangle
123
- nodeHighlightGap: 1,
123
+ nodeHighlightGap: 4,
124
124
 
125
125
  // Allows the user to resize the node.
126
126
  nodeResizable: false,
@@ -444,7 +444,7 @@ const horizontalDefaultLayout = {
444
444
  commentCornerResizeArea: 10,
445
445
 
446
446
  // The gap between a comment and its selection highlight rectangle
447
- commentHighlightGap: 1,
447
+ commentHighlightGap: 4,
448
448
 
449
449
  // The gap between a comment and its sizing area rectangle
450
450
  commentSizingArea: 10,
@@ -158,7 +158,7 @@ $palette-dialog-list-item-height: 46px;
158
158
 
159
159
  .palette-list-item-icon {
160
160
  width: 28px; /* Not specifying height preserves the image's aspect ratio. */
161
- color: carbon.$text-secondary;
161
+ fill: carbon.$icon-primary;
162
162
  }
163
163
 
164
164
  .palette-list-item-text-div {
@@ -432,7 +432,7 @@ $palette-dialog-list-item-height: 46px;
432
432
  .palette-list-item-icon {
433
433
  width: 28px; /* Not specifying height preserves the image's aspect ratio. */
434
434
  line-height: $palette-dialog-list-item-height; /* Must be same as containing div height */
435
- color: carbon.$text-secondary;
435
+ fill: carbon.$icon-primary;
436
436
  margin-left: 0;
437
437
  margin-right: 10px;
438
438
  }
@@ -542,6 +542,7 @@ $palette-dialog-list-item-height: 46px;
542
542
  .palette-dialog-grid-node-icon .node-icon {
543
543
  width: carbon.$spacing-10;
544
544
  height: carbon.$spacing-10;
545
+ fill: carbon.$icon-primary;
545
546
  }
546
547
 
547
548
  .palette-dialog-grid-node-icon-warning {
@@ -96,7 +96,7 @@ class Toolbar extends React.Component {
96
96
  // toolbar config is updated and the current focusAction item is removed.
97
97
  componentDidUpdate() {
98
98
  const focusableItems = this.getFocusableItemRefs();
99
- const index = focusableItems.findIndex((item) => this.getRefAction(item) === this.state.focusAction);
99
+ const index = this.getFocusActionIndex(focusableItems);
100
100
 
101
101
  if (focusableItems.length === 0 && this.state.focusAction !== "disabled") {
102
102
  this.setFocusOnFirstDisabledItem();
@@ -122,15 +122,16 @@ class Toolbar extends React.Component {
122
122
  this.isFocusInToolbar = true;
123
123
 
124
124
  // If focus occurs because of a click on the toolbar body
125
- // (not on a button) and no button has focus move focus to
126
- // the first item otherwise just keep focus the same.
125
+ // (not on a button), move focus to the first item only if
126
+ // no button currently has focus (focusAction === "toolbar").
127
+ // Otherwise, maintain focus on the button specified by focusAction.
127
128
  if (evt.target?.classList?.contains("toolbar-div")) {
128
129
  if (this.state.focusAction === "toolbar") {
129
130
  this.setFocusOnFirstItem();
130
-
131
-
132
131
  } else {
133
- this.setFocusOnItem(); // Reset focus on current focusAction.
132
+ // Keep focus on the button specified by this.state.focusAction
133
+ const currentFocusRef = this.getFocusActionRef();
134
+ currentFocusRef?.current?.buttonItemRef?.current?.setButtonFocus();
134
135
  }
135
136
  }
136
137
  }
@@ -279,7 +280,7 @@ class Toolbar extends React.Component {
279
280
  }
280
281
 
281
282
  getPreviousItemRef(focusableItemRefs) {
282
- const index = focusableItemRefs.findIndex((item) => this.getRefAction(item) === this.state.focusAction);
283
+ const index = this.getFocusActionIndex(focusableItemRefs);
283
284
  if (index === 0) {
284
285
  return focusableItemRefs[focusableItemRefs.length - 1];
285
286
  }
@@ -287,13 +288,24 @@ class Toolbar extends React.Component {
287
288
  }
288
289
 
289
290
  getNextItemRef(focusableItemRefs) {
290
- const index = focusableItemRefs.findIndex((item) => this.getRefAction(item) === this.state.focusAction);
291
+ const index = this.getFocusActionIndex(focusableItemRefs);
291
292
  if (index === focusableItemRefs.length - 1) {
292
293
  return focusableItemRefs[0];
293
294
  }
294
295
  return focusableItemRefs[index + 1];
295
296
  }
296
297
 
298
+ // Returns the index of the item in focusableItemRefs that matches this.state.focusAction
299
+ getFocusActionIndex(focusableItemRefs) {
300
+ return focusableItemRefs.findIndex((item) => this.getRefAction(item) === this.state.focusAction);
301
+ }
302
+
303
+ // Returns the ref of the item that matches this.state.focusAction
304
+ getFocusActionRef() {
305
+ const focusableItemRefs = this.getFocusableItemRefs();
306
+ return focusableItemRefs.find((ref) => this.getRefAction(ref) === this.state.focusAction);
307
+ }
308
+
297
309
  getRefAction(ref) {
298
310
  return ref.current?.getAction();
299
311
  }
@@ -370,7 +370,7 @@ $toolbar-divider-width: 1px;
370
370
  }
371
371
 
372
372
  &.disabled.default {
373
- color: carbon.$button-disabled;
373
+ color: carbon.$icon-disabled;
374
374
  fill: carbon.$button-disabled; // For custom svg images
375
375
  & .toolbar-tick-mark {
376
376
  fill: carbon.$button-disabled;