pict-section-flow 0.0.17 → 0.0.19

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 (90) hide show
  1. package/README.md +18 -18
  2. package/docs/Architecture.md +1 -1
  3. package/docs/Data_Model.md +2 -2
  4. package/docs/Getting_Started.md +5 -5
  5. package/docs/Implementation_Reference.md +6 -6
  6. package/docs/Layout_Persistence.md +3 -3
  7. package/docs/README.md +12 -12
  8. package/docs/Theme_Integration.md +150 -0
  9. package/docs/_cover.md +1 -1
  10. package/docs/_sidebar.md +7 -6
  11. package/docs/_version.json +7 -0
  12. package/docs/api/PictFlowCard.md +6 -6
  13. package/docs/api/PictFlowCardPropertiesPanel.md +2 -2
  14. package/docs/api/addConnection.md +4 -4
  15. package/docs/api/addNode.md +6 -6
  16. package/docs/api/autoLayout.md +2 -2
  17. package/docs/api/getFlowData.md +5 -5
  18. package/docs/api/marshalToView.md +3 -3
  19. package/docs/api/openPanel.md +2 -2
  20. package/docs/api/registerHandler.md +3 -3
  21. package/docs/api/registerNodeType.md +3 -3
  22. package/docs/api/removeConnection.md +5 -5
  23. package/docs/api/removeNode.md +6 -6
  24. package/docs/api/saveLayout.md +2 -2
  25. package/docs/api/screenToSVGCoords.md +2 -2
  26. package/docs/api/selectNode.md +3 -3
  27. package/docs/api/setTheme.md +2 -2
  28. package/docs/api/setZoom.md +3 -3
  29. package/docs/api/toggleFullscreen.md +2 -2
  30. package/docs/card-help/EACH.md +3 -3
  31. package/docs/card-help/FREAD.md +5 -5
  32. package/docs/card-help/FWRITE.md +5 -5
  33. package/docs/card-help/GET.md +2 -2
  34. package/docs/card-help/ITE.md +3 -3
  35. package/docs/card-help/LOG.md +4 -4
  36. package/docs/card-help/NOTE.md +1 -1
  37. package/docs/card-help/PREV.md +2 -2
  38. package/docs/card-help/SET.md +5 -5
  39. package/docs/card-help/SPKL.md +2 -2
  40. package/docs/card-help/STAT.md +3 -3
  41. package/docs/card-help/SW.md +4 -4
  42. package/docs/css/docuserve.css +277 -23
  43. package/docs/index.html +2 -2
  44. package/docs/retold-catalog.json +1 -1
  45. package/docs/retold-keyword-index.json +1 -1
  46. package/example_applications/simple_cards/css/flowexample.css +2 -2
  47. package/example_applications/simple_cards/source/card-help-content.js +12 -12
  48. package/example_applications/simple_cards/source/cards/FlowCard-DataPreview.js +1 -1
  49. package/example_applications/simple_cards/source/sample-flows.js +410 -0
  50. package/example_applications/simple_cards/source/views/PictView-FlowExample-About.js +5 -5
  51. package/example_applications/simple_cards/source/views/PictView-FlowExample-Documentation.js +5 -5
  52. package/example_applications/simple_cards/source/views/PictView-FlowExample-FileWriteInfo.js +4 -4
  53. package/example_applications/simple_cards/source/views/PictView-FlowExample-MainWorkspace.js +141 -8
  54. package/example_applications/simple_cards/source/views/PictView-FlowExample-TopBar.js +2 -2
  55. package/package.json +8 -7
  56. package/source/Pict-Section-Flow.js +26 -0
  57. package/source/providers/PictProvider-Flow-CSS.js +435 -61
  58. package/source/providers/PictProvider-Flow-ConnectorShapes.js +9 -5
  59. package/source/providers/PictProvider-Flow-NodeTypes.js +10 -0
  60. package/source/providers/PictProvider-Flow-PanelChrome.js +7 -17
  61. package/source/providers/PictProvider-Flow-Theme.js +7 -7
  62. package/source/providers/edges/Edge-Bezier.js +41 -0
  63. package/source/providers/edges/Edge-Orthogonal.js +37 -0
  64. package/source/providers/edges/Edge-OrthogonalSnap.js +72 -0
  65. package/source/providers/edges/Edge-Perimeter-Linear.js +31 -0
  66. package/source/providers/edges/Edge-Perimeter-Orthogonal.js +39 -0
  67. package/source/providers/edges/Edge-Perimeter.js +48 -0
  68. package/source/providers/edges/Edge-PerimeterMath.js +92 -0
  69. package/source/providers/edges/Edge-Straight.js +24 -0
  70. package/source/providers/layouts/Layout-Circular.js +203 -0
  71. package/source/providers/layouts/Layout-Coerce.js +40 -0
  72. package/source/providers/layouts/Layout-Columnar.js +134 -0
  73. package/source/providers/layouts/Layout-Custom.js +27 -0
  74. package/source/providers/layouts/Layout-ForcedFromCenter.js +256 -0
  75. package/source/providers/layouts/Layout-Grid.js +134 -0
  76. package/source/providers/layouts/Layout-Layered.js +209 -0
  77. package/source/providers/layouts/Layout-Tabular.js +94 -0
  78. package/source/services/PictService-Flow-ConnectionRenderer.js +532 -28
  79. package/source/services/PictService-Flow-DataManager.js +12 -1
  80. package/source/services/PictService-Flow-InteractionManager.js +39 -42
  81. package/source/services/PictService-Flow-Layout.js +305 -121
  82. package/source/services/PictService-Flow-PortRenderer.js +108 -26
  83. package/source/services/PictService-Flow-RenderManager.js +41 -11
  84. package/source/views/PictView-Flow-FloatingToolbar.js +69 -61
  85. package/source/views/PictView-Flow-Node.js +47 -6
  86. package/source/views/PictView-Flow-PropertiesPanel.js +46 -53
  87. package/source/views/PictView-Flow-Toolbar.js +1065 -485
  88. package/source/views/PictView-Flow.js +455 -7
  89. package/test/Layout_tests.js +1400 -0
  90. package/test/PortRenderer_tests.js +11 -2
@@ -43,6 +43,12 @@ class PictServiceFlowPortRenderer extends libFableServiceProviderBase
43
43
  let tmpPortLabelsOutside = (pNodeTypeConfig && pNodeTypeConfig.PortLabelsOutside);
44
44
  let tmpGeometryProvider = this._FlowView._GeometryProvider;
45
45
 
46
+ // Connected ports get their colored dot drawn by the connection
47
+ // renderer at the actual line attachment (which may have been
48
+ // reshaped by an edge theme like Perimeter). Unconnected ports
49
+ // still draw their dot at the static card-defined position.
50
+ let tmpConnectedPortHashes = this._collectConnectedPortHashes(pNodeData.Hash);
51
+
46
52
  // Group ports by their Side value (supports all 12 positions)
47
53
  let tmpPortsBySide = {};
48
54
  for (let i = 0; i < pNodeData.Ports.length; i++)
@@ -167,6 +173,16 @@ class PictServiceFlowPortRenderer extends libFableServiceProviderBase
167
173
  + ' L ' + (tmpBadgeX + tmpBadgeWidth) + ' ' + (tmpBadgeY + tmpBadgeHeight);
168
174
  }
169
175
 
176
+ // Wrap the badge elements in a hoverable group so we can
177
+ // listen for mouseenter/mouseleave on the badge as a
178
+ // whole. Tagged with data-port-hash + data-node-hash so
179
+ // the hover handler can surface this port's hint paths.
180
+ let tmpBadgeGroup = this._FlowView._SVGHelperProvider.createSVGElement('g');
181
+ tmpBadgeGroup.setAttribute('class', 'pict-flow-port-badge');
182
+ tmpBadgeGroup.setAttribute('data-port-hash', tmpPort.Hash);
183
+ tmpBadgeGroup.setAttribute('data-node-hash', pNodeData.Hash);
184
+ this._wirePortHintHover(tmpBadgeGroup, tmpPort.Hash, null);
185
+
170
186
  // Background rect (cream, no stroke — border drawn separately)
171
187
  let tmpBgRect = this._FlowView._SVGHelperProvider.createSVGElement('rect');
172
188
  tmpBgRect.setAttribute('class', 'pict-flow-port-label-bg');
@@ -175,7 +191,7 @@ class PictServiceFlowPortRenderer extends libFableServiceProviderBase
175
191
  tmpBgRect.setAttribute('width', String(tmpBadgeWidth));
176
192
  tmpBgRect.setAttribute('height', String(tmpBadgeHeight));
177
193
  tmpBgRect.setAttribute('fill', 'var(--pf-port-label-bg, rgba(255, 253, 240, 0.5))');
178
- pGroup.appendChild(tmpBgRect);
194
+ tmpBadgeGroup.appendChild(tmpBgRect);
179
195
 
180
196
  // 3-sided border path (open on the edge-facing side)
181
197
  let tmpBorderPathEl = this._FlowView._SVGHelperProvider.createSVGElement('path');
@@ -184,7 +200,7 @@ class PictServiceFlowPortRenderer extends libFableServiceProviderBase
184
200
  tmpBorderPathEl.setAttribute('fill', 'none');
185
201
  tmpBorderPathEl.setAttribute('stroke', tmpBorderColor);
186
202
  tmpBorderPathEl.setAttribute('stroke-width', '0.75');
187
- pGroup.appendChild(tmpBorderPathEl);
203
+ tmpBadgeGroup.appendChild(tmpBorderPathEl);
188
204
 
189
205
  // Colored stripe on the inner side
190
206
  let tmpStripe = this._FlowView._SVGHelperProvider.createSVGElement('rect');
@@ -194,7 +210,9 @@ class PictServiceFlowPortRenderer extends libFableServiceProviderBase
194
210
  tmpStripe.setAttribute('width', String(tmpStripeW));
195
211
  tmpStripe.setAttribute('height', String(tmpStripeH));
196
212
  tmpStripe.setAttribute('fill', tmpBorderColor);
197
- pGroup.appendChild(tmpStripe);
213
+ tmpBadgeGroup.appendChild(tmpStripe);
214
+
215
+ pGroup.appendChild(tmpBadgeGroup);
198
216
 
199
217
  // Text label — appended after circle for z-order
200
218
  tmpLabelElement = this._FlowView._SVGHelperProvider.createSVGElement('text');
@@ -207,35 +225,41 @@ class PictServiceFlowPortRenderer extends libFableServiceProviderBase
207
225
  tmpLabelElement.setAttribute('dominant-baseline', 'central');
208
226
  }
209
227
 
210
- // Port circle (rendered on top of badge background)
211
- let tmpShapeProvider = this._FlowView._ConnectorShapesProvider;
212
- let tmpCircle;
213
- if (tmpShapeProvider)
228
+ // Port circle (rendered on top of badge background)
229
+ // only for unconnected ports. Connected ports get their
230
+ // dot painted by the connection renderer at the actual
231
+ // attachment point (so it follows edge-theme overrides).
232
+ if (!tmpConnectedPortHashes[tmpPort.Hash])
214
233
  {
215
- tmpCircle = tmpShapeProvider.createPortElement(tmpPort, tmpPosition, pNodeData.Hash);
216
- }
217
- else
218
- {
219
- tmpCircle = this._FlowView._SVGHelperProvider.createSVGElement('circle');
220
- let tmpPortClass = `pict-flow-port ${tmpPort.Direction}`;
221
- if (tmpPort.PortType)
234
+ let tmpShapeProvider = this._FlowView._ConnectorShapesProvider;
235
+ let tmpCircle;
236
+ if (tmpShapeProvider)
222
237
  {
223
- tmpPortClass += ` port-type-${tmpPort.PortType}`;
238
+ tmpCircle = tmpShapeProvider.createPortElement(tmpPort, tmpPosition, pNodeData.Hash);
224
239
  }
225
- tmpCircle.setAttribute('class', tmpPortClass);
226
- tmpCircle.setAttribute('cx', String(tmpPosition.x));
227
- tmpCircle.setAttribute('cy', String(tmpPosition.y));
228
- tmpCircle.setAttribute('r', '5');
229
- tmpCircle.setAttribute('data-port-hash', tmpPort.Hash);
230
- tmpCircle.setAttribute('data-node-hash', pNodeData.Hash);
231
- tmpCircle.setAttribute('data-port-direction', tmpPort.Direction);
232
- if (tmpPort.PortType)
240
+ else
233
241
  {
234
- tmpCircle.setAttribute('data-port-type', tmpPort.PortType);
242
+ tmpCircle = this._FlowView._SVGHelperProvider.createSVGElement('circle');
243
+ let tmpPortClass = `pict-flow-port ${tmpPort.Direction}`;
244
+ if (tmpPort.PortType)
245
+ {
246
+ tmpPortClass += ` port-type-${tmpPort.PortType}`;
247
+ }
248
+ tmpCircle.setAttribute('class', tmpPortClass);
249
+ tmpCircle.setAttribute('cx', String(tmpPosition.x));
250
+ tmpCircle.setAttribute('cy', String(tmpPosition.y));
251
+ tmpCircle.setAttribute('r', '5');
252
+ tmpCircle.setAttribute('data-port-hash', tmpPort.Hash);
253
+ tmpCircle.setAttribute('data-node-hash', pNodeData.Hash);
254
+ tmpCircle.setAttribute('data-port-direction', tmpPort.Direction);
255
+ if (tmpPort.PortType)
256
+ {
257
+ tmpCircle.setAttribute('data-port-type', tmpPort.PortType);
258
+ }
259
+ tmpCircle.setAttribute('data-element-type', 'port');
235
260
  }
236
- tmpCircle.setAttribute('data-element-type', 'port');
261
+ pGroup.appendChild(tmpCircle);
237
262
  }
238
- pGroup.appendChild(tmpCircle);
239
263
 
240
264
  // Port label text (on top of everything)
241
265
  if (tmpLabelElement)
@@ -246,6 +270,64 @@ class PictServiceFlowPortRenderer extends libFableServiceProviderBase
246
270
  }
247
271
  }
248
272
 
273
+ /**
274
+ * Wire mouseenter/mouseleave on a hoverable element so it activates
275
+ * port-hint paths in the port-hints layer. When `pPortHash` is set,
276
+ * only hints for that port light up; when `pNodeHash` is set (and
277
+ * port hash is null), every hint on that node lights up. Static —
278
+ * no closure-state dependency on the node/port objects themselves.
279
+ *
280
+ * @param {SVGElement} pHoverEl
281
+ * @param {string|null} pPortHash
282
+ * @param {string|null} pNodeHash
283
+ */
284
+ _wirePortHintHover(pHoverEl, pPortHash, pNodeHash)
285
+ {
286
+ if (!pHoverEl || typeof pHoverEl.setAttribute !== 'function') return;
287
+ if (!this._FlowView || !this._FlowView.options) return;
288
+
289
+ // Inline attribute handlers — survive the connection-layer rebuild
290
+ // without leaving stale listeners on detached DOM nodes.
291
+ let tmpFlowViewIdentifier = this._FlowView.options.ViewIdentifier;
292
+ let tmpHashArg = pPortHash ? ('"' + pPortHash + '", null') : ('null, "' + pNodeHash + '"');
293
+ pHoverEl.setAttribute('onmouseenter',
294
+ "_Pict.views['" + tmpFlowViewIdentifier + "']._activatePortHints(" + tmpHashArg + ")");
295
+ pHoverEl.setAttribute('onmouseleave',
296
+ "_Pict.views['" + tmpFlowViewIdentifier + "']._deactivatePortHints(" + tmpHashArg + ")");
297
+ }
298
+
299
+ /**
300
+ * Build a hash-set of port hashes on the given node that participate
301
+ * in any connection (either as source or target). Used to suppress
302
+ * the static port circle so the connection renderer can paint the
303
+ * dot at the resolved attachment point instead.
304
+ *
305
+ * @param {string} pNodeHash
306
+ * @returns {Object} keyed by port hash → true
307
+ */
308
+ _collectConnectedPortHashes(pNodeHash)
309
+ {
310
+ let tmpResult = {};
311
+ if (!this._FlowView || !this._FlowView._FlowData || !Array.isArray(this._FlowView._FlowData.Connections))
312
+ {
313
+ return tmpResult;
314
+ }
315
+ let tmpConns = this._FlowView._FlowData.Connections;
316
+ for (let i = 0; i < tmpConns.length; i++)
317
+ {
318
+ let tmpConn = tmpConns[i];
319
+ if (tmpConn.SourceNodeHash === pNodeHash && tmpConn.SourcePortHash)
320
+ {
321
+ tmpResult[tmpConn.SourcePortHash] = true;
322
+ }
323
+ if (tmpConn.TargetNodeHash === pNodeHash && tmpConn.TargetPortHash)
324
+ {
325
+ tmpResult[tmpConn.TargetPortHash] = true;
326
+ }
327
+ }
328
+ return tmpResult;
329
+ }
330
+
249
331
  /**
250
332
  * Calculate port position relative to node origin.
251
333
  *
@@ -37,6 +37,14 @@ class PictServiceFlowRenderManager extends libFableServiceProviderBase
37
37
  {
38
38
  this._FlowView._ConnectionsLayer.removeChild(this._FlowView._ConnectionsLayer.firstChild);
39
39
  }
40
+ if (this._FlowView._EndpointsLayer)
41
+ {
42
+ while (this._FlowView._EndpointsLayer.firstChild) this._FlowView._EndpointsLayer.removeChild(this._FlowView._EndpointsLayer.firstChild);
43
+ }
44
+ if (this._FlowView._PortHintsLayer)
45
+ {
46
+ while (this._FlowView._PortHintsLayer.firstChild) this._FlowView._PortHintsLayer.removeChild(this._FlowView._PortHintsLayer.firstChild);
47
+ }
40
48
 
41
49
  // Render connections first (behind nodes)
42
50
  for (let i = 0; i < this._FlowView._FlowData.Connections.length; i++)
@@ -112,12 +120,10 @@ class PictServiceFlowRenderManager extends libFableServiceProviderBase
112
120
  {
113
121
  if (!this._FlowView || !this._FlowView._ConnectionsLayer) return;
114
122
 
115
- // Remove existing elements for this connection
116
- let tmpExisting = this._FlowView._ConnectionsLayer.querySelectorAll(`[data-connection-hash="${pConnectionHash}"]`);
117
- for (let i = 0; i < tmpExisting.length; i++)
118
- {
119
- tmpExisting[i].remove();
120
- }
123
+ // Remove existing elements for this connection across all layers
124
+ // where it might live (path / hit-area in connections, dots in
125
+ // endpoints, hint paths in port-hints).
126
+ this._removeConnectionElements(pConnectionHash);
121
127
 
122
128
  let tmpConnection = this._FlowView.getConnection(pConnectionHash);
123
129
  if (!tmpConnection) return;
@@ -208,15 +214,39 @@ class PictServiceFlowRenderManager extends libFableServiceProviderBase
208
214
  let tmpConn = tmpAffectedConnections[i];
209
215
  let tmpIsSelected = (this._FlowView._FlowData.ViewState.SelectedConnectionHash === tmpConn.Hash);
210
216
 
211
- // Remove existing connection SVG elements
212
- let tmpExisting = this._FlowView._ConnectionsLayer.querySelectorAll(`[data-connection-hash="${tmpConn.Hash}"]`);
217
+ // Remove existing connection SVG elements across all layers
218
+ this._removeConnectionElements(tmpConn.Hash);
219
+
220
+ // Re-render this connection
221
+ this._FlowView._ConnectionRenderer.renderConnection(tmpConn, this._FlowView._ConnectionsLayer, tmpIsSelected);
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Remove every SVG element tagged with `data-connection-hash="<hash>"`
227
+ * from any of the layers a connection might write into (connections,
228
+ * endpoints, port-hints). Centralised so per-connection re-render
229
+ * paths stay in sync with whatever layers ConnectionRenderer touches.
230
+ *
231
+ * @param {string} pConnectionHash
232
+ */
233
+ _removeConnectionElements(pConnectionHash)
234
+ {
235
+ let tmpLayers =
236
+ [
237
+ this._FlowView._ConnectionsLayer,
238
+ this._FlowView._EndpointsLayer,
239
+ this._FlowView._PortHintsLayer
240
+ ];
241
+ for (let i = 0; i < tmpLayers.length; i++)
242
+ {
243
+ let tmpLayer = tmpLayers[i];
244
+ if (!tmpLayer) continue;
245
+ let tmpExisting = tmpLayer.querySelectorAll(`[data-connection-hash="${pConnectionHash}"]`);
213
246
  for (let j = 0; j < tmpExisting.length; j++)
214
247
  {
215
248
  tmpExisting[j].remove();
216
249
  }
217
-
218
- // Re-render this connection
219
- this._FlowView._ConnectionRenderer.renderConnection(tmpConn, this._FlowView._ConnectionsLayer, tmpIsSelected);
220
250
  }
221
251
  }
222
252
 
@@ -17,42 +17,56 @@ const _DefaultConfiguration =
17
17
  [
18
18
  {
19
19
  Hash: 'Flow-FloatingToolbar-Template',
20
+ // Inline handlers route to the floating toolbar view via _Pict.views
21
+ // (see _handleButtonClick / _startDrag / _toggleCollapse).
20
22
  Template: /*html*/`
21
23
  <div class="pict-flow-floating-toolbar" id="Flow-FloatingToolbar-{~D:Record.FlowViewIdentifier~}">
22
- <div class="pict-flow-floating-grip" id="Flow-FloatingGrip-{~D:Record.FlowViewIdentifier~}" title="Drag to move · Double-click to collapse">
24
+ <div class="pict-flow-floating-grip" id="Flow-FloatingGrip-{~D:Record.FlowViewIdentifier~}" title="Drag to move · Double-click to collapse"
25
+ onmousedown="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._startDrag(event)"
26
+ ondblclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleGripDoubleClick(event)">
23
27
  <span id="Flow-FloatingIcon-grip-{~D:Record.FlowViewIdentifier~}"></span>
24
28
  </div>
25
- <button class="pict-flow-floating-btn" data-flow-action="add-node" title="Add Node">
29
+ <button class="pict-flow-floating-btn" data-flow-action="add-node" title="Add Node"
30
+ onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('add-node')">
26
31
  <span id="Flow-FloatingIcon-plus-{~D:Record.FlowViewIdentifier~}"></span>
27
32
  </button>
28
- <button class="pict-flow-floating-btn" data-flow-action="cards-popup" title="Cards">
33
+ <button class="pict-flow-floating-btn" data-flow-action="cards-popup" title="Cards"
34
+ onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('cards-popup')">
29
35
  <span id="Flow-FloatingIcon-cards-{~D:Record.FlowViewIdentifier~}"></span>
30
36
  </button>
31
- <button class="pict-flow-floating-btn" data-flow-action="delete-selected" title="Delete Selected">
37
+ <button class="pict-flow-floating-btn" data-flow-action="delete-selected" title="Delete Selected"
38
+ onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('delete-selected')">
32
39
  <span id="Flow-FloatingIcon-trash-{~D:Record.FlowViewIdentifier~}"></span>
33
40
  </button>
34
41
  <div class="pict-flow-floating-separator"></div>
35
- <button class="pict-flow-floating-btn" data-flow-action="zoom-in" title="Zoom In">
42
+ <button class="pict-flow-floating-btn" data-flow-action="zoom-in" title="Zoom In"
43
+ onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('zoom-in')">
36
44
  <span id="Flow-FloatingIcon-zoom-in-{~D:Record.FlowViewIdentifier~}"></span>
37
45
  </button>
38
- <button class="pict-flow-floating-btn" data-flow-action="zoom-out" title="Zoom Out">
46
+ <button class="pict-flow-floating-btn" data-flow-action="zoom-out" title="Zoom Out"
47
+ onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('zoom-out')">
39
48
  <span id="Flow-FloatingIcon-zoom-out-{~D:Record.FlowViewIdentifier~}"></span>
40
49
  </button>
41
- <button class="pict-flow-floating-btn" data-flow-action="zoom-fit" title="Fit to View">
50
+ <button class="pict-flow-floating-btn" data-flow-action="zoom-fit" title="Fit to View"
51
+ onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('zoom-fit')">
42
52
  <span id="Flow-FloatingIcon-zoom-fit-{~D:Record.FlowViewIdentifier~}"></span>
43
53
  </button>
44
54
  <div class="pict-flow-floating-separator"></div>
45
- <button class="pict-flow-floating-btn" data-flow-action="auto-layout" title="Auto Layout">
55
+ <button class="pict-flow-floating-btn" data-flow-action="auto-layout" title="Auto Layout"
56
+ onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('auto-layout')">
46
57
  <span id="Flow-FloatingIcon-auto-layout-{~D:Record.FlowViewIdentifier~}"></span>
47
58
  </button>
48
- <button class="pict-flow-floating-btn" data-flow-action="layout-popup" title="Layout">
59
+ <button class="pict-flow-floating-btn" data-flow-action="layout-popup" title="Layout"
60
+ onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('layout-popup')">
49
61
  <span id="Flow-FloatingIcon-layout-{~D:Record.FlowViewIdentifier~}"></span>
50
62
  </button>
51
- <button class="pict-flow-floating-btn" data-flow-action="fullscreen" title="Toggle Fullscreen">
63
+ <button class="pict-flow-floating-btn" data-flow-action="fullscreen" title="Toggle Fullscreen"
64
+ onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('fullscreen')">
52
65
  <span id="Flow-FloatingIcon-fullscreen-{~D:Record.FlowViewIdentifier~}"></span>
53
66
  </button>
54
67
  <div class="pict-flow-floating-separator"></div>
55
- <button class="pict-flow-floating-btn" data-flow-action="dock-toolbar" title="Dock Toolbar">
68
+ <button class="pict-flow-floating-btn" data-flow-action="dock-toolbar" title="Dock Toolbar"
69
+ onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('dock-toolbar')">
56
70
  <span id="Flow-FloatingIcon-dock-{~D:Record.FlowViewIdentifier~}"></span>
57
71
  </button>
58
72
  </div>
@@ -104,58 +118,13 @@ class PictViewFlowFloatingToolbar extends libPictView
104
118
  {
105
119
  let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
106
120
 
107
- // Bind click delegation for action buttons
108
- let tmpFloatingToolbar = this.pict.ContentAssignment.getElement(`#Flow-FloatingToolbar-${tmpFlowViewIdentifier}`);
109
- if (tmpFloatingToolbar.length > 0)
110
- {
111
- tmpFloatingToolbar[0].addEventListener('click', (pEvent) =>
112
- {
113
- let tmpTarget = pEvent.target;
114
- if (!tmpTarget) return;
115
-
116
- let tmpButton = tmpTarget.closest('[data-flow-action]');
117
- if (!tmpButton) return;
118
-
119
- let tmpAction = tmpButton.getAttribute('data-flow-action');
120
- if (tmpAction === 'dock-toolbar')
121
- {
122
- if (this._ToolbarView)
123
- {
124
- this._ToolbarView._setToolbarMode('docked');
125
- }
126
- return;
127
- }
128
-
129
- // Delegate all other actions to the docked toolbar
130
- if (this._ToolbarView)
131
- {
132
- this._ToolbarView._handleToolbarAction(tmpAction);
133
- }
134
- });
135
- }
136
-
137
- // Bind drag behavior on the grip
138
- let tmpGrip = this.pict.ContentAssignment.getElement(`#Flow-FloatingGrip-${tmpFlowViewIdentifier}`);
139
- if (tmpGrip.length > 0)
140
- {
141
- tmpGrip[0].addEventListener('mousedown', (pEvent) =>
142
- {
143
- this._startDrag(pEvent);
144
- });
145
-
146
- // Double-click grip to toggle collapsed state
147
- tmpGrip[0].addEventListener('dblclick', (pEvent) =>
148
- {
149
- pEvent.preventDefault();
150
- pEvent.stopPropagation();
151
- this._toggleCollapse();
152
- });
153
- }
154
-
155
- // Populate icons
121
+ // Button clicks and grip drag/dblclick are wired inline in
122
+ // Flow-FloatingToolbar-Template — see _handleButtonClick / _startDrag /
123
+ // _handleGripDoubleClick. Only icon population and option-based
124
+ // pruning happen here.
156
125
  this._populateIcons();
157
126
 
158
- // Remove buttons from DOM based on options
127
+ let tmpFloatingToolbar = this.pict.ContentAssignment.getElement(`#Flow-FloatingToolbar-${tmpFlowViewIdentifier}`);
159
128
  if (tmpFloatingToolbar.length > 0)
160
129
  {
161
130
  if (this.options.EnableAddNode === false)
@@ -179,6 +148,45 @@ class PictViewFlowFloatingToolbar extends libPictView
179
148
  return super.onAfterRender(pRenderable, pRenderDestinationAddress, pRecord, pContent);
180
149
  }
181
150
 
151
+ /**
152
+ * Handle a click on a floating toolbar action button. Called from the
153
+ * inline `onclick` handler on each button.
154
+ *
155
+ * @param {string} pAction - Value of the button's data-flow-action attr
156
+ */
157
+ _handleButtonClick(pAction)
158
+ {
159
+ if (pAction === 'dock-toolbar')
160
+ {
161
+ if (this._ToolbarView)
162
+ {
163
+ this._ToolbarView._setToolbarMode('docked');
164
+ }
165
+ return;
166
+ }
167
+ if (this._ToolbarView)
168
+ {
169
+ this._ToolbarView._handleToolbarAction(pAction);
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Handle a double-click on the grip. Toggles collapsed state and
175
+ * prevents the event from reaching the surrounding canvas. Called
176
+ * from the inline `ondblclick` handler.
177
+ *
178
+ * @param {Event} pEvent
179
+ */
180
+ _handleGripDoubleClick(pEvent)
181
+ {
182
+ if (pEvent && typeof pEvent.preventDefault === 'function')
183
+ {
184
+ pEvent.preventDefault();
185
+ pEvent.stopPropagation();
186
+ }
187
+ this._toggleCollapse();
188
+ }
189
+
182
190
  /**
183
191
  * Populate SVG icons into all floating toolbar button spans.
184
192
  */
@@ -40,11 +40,31 @@ class PictViewFlowNode extends libPictView
40
40
  if (pNodeTypeConfig.PortLabelsOnHover) tmpClassList += ' pict-flow-node-port-labels-hover';
41
41
  if (pNodeTypeConfig.PortLabelsVertical) tmpClassList += ' pict-flow-node-port-labels-vertical';
42
42
  }
43
+
44
+ // Resolve theme-aware ColorRole. Per-node override wins; falls back
45
+ // to the node type's role. A role of 'none' (or empty string)
46
+ // explicitly opts out — useful when a card wants to keep its
47
+ // hex BodyStyle/TitleBarColor regardless of the host theme.
48
+ let tmpColorRole = (typeof pNodeData.ColorRole !== 'undefined')
49
+ ? pNodeData.ColorRole
50
+ : (pNodeTypeConfig && pNodeTypeConfig.ColorRole);
51
+ if (tmpColorRole && tmpColorRole !== 'none')
52
+ {
53
+ tmpClassList += ' pict-flow-node-color-' + tmpColorRole;
54
+ }
43
55
  tmpGroup.setAttribute('class', tmpClassList);
44
56
  tmpGroup.setAttribute('transform', `translate(${pNodeData.X}, ${pNodeData.Y})`);
45
57
  tmpGroup.setAttribute('data-node-hash', pNodeData.Hash);
46
58
  tmpGroup.setAttribute('data-element-type', 'node');
47
59
 
60
+ // Hover the whole node → activate every port-hint that points
61
+ // at any of this node's ports. Lets the user see *all* of the
62
+ // node's reroute lines at once, not just one badge at a time.
63
+ if (this._FlowView._PortRenderer && typeof this._FlowView._PortRenderer._wirePortHintHover === 'function')
64
+ {
65
+ this._FlowView._PortRenderer._wirePortHintHover(tmpGroup, null, pNodeData.Hash);
66
+ }
67
+
48
68
  let tmpWidth = pNodeData.Width || 180;
49
69
  let tmpHeight = pNodeData.Height || 80;
50
70
  let tmpTitleBarHeight = this.options.NodeTitleBarHeight;
@@ -422,9 +442,10 @@ class PictViewFlowNode extends libPictView
422
442
  tmpDiv.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
423
443
  tmpDiv.setAttribute('class', 'pict-flow-node-body-content-html');
424
444
 
425
- // Pointer event isolation — prevent node drag/canvas pan
426
- tmpDiv.addEventListener('pointerdown', (pEvent) => { pEvent.stopPropagation(); });
427
- tmpDiv.addEventListener('wheel', (pEvent) => { pEvent.stopPropagation(); });
445
+ // Pointer event isolation — prevent node drag/canvas pan.
446
+ // Inline attribute handlers (visible in DOM, no closure leak).
447
+ tmpDiv.setAttribute('onpointerdown', 'event.stopPropagation()');
448
+ tmpDiv.setAttribute('onwheel', 'event.stopPropagation()');
428
449
 
429
450
  // Render template content
430
451
  let tmpRenderedContent = this._resolveBodyTemplate(pBodyContent, pNodeData, pPict);
@@ -463,9 +484,9 @@ class PictViewFlowNode extends libPictView
463
484
  tmpCanvas.style.width = '100%';
464
485
  tmpCanvas.style.height = '100%';
465
486
 
466
- // Pointer event isolation
467
- tmpCanvas.addEventListener('pointerdown', (pEvent) => { pEvent.stopPropagation(); });
468
- tmpCanvas.addEventListener('wheel', (pEvent) => { pEvent.stopPropagation(); });
487
+ // Pointer event isolation — inline attribute handlers.
488
+ tmpCanvas.setAttribute('onpointerdown', 'event.stopPropagation()');
489
+ tmpCanvas.setAttribute('onwheel', 'event.stopPropagation()');
469
490
 
470
491
  // Invoke render callback (the primary rendering path for canvas)
471
492
  if (typeof pBodyContent.RenderCallback === 'function')
@@ -529,6 +550,17 @@ class PictViewFlowNode extends libPictView
529
550
  }
530
551
  }
531
552
 
553
+ // Per-node BodyStyle override (wins over the node-type default).
554
+ // Lets a flow author paint individual cards distinctly without
555
+ // registering a new node type per category.
556
+ if (pNodeData.BodyStyle)
557
+ {
558
+ for (let tmpStyleKey in pNodeData.BodyStyle)
559
+ {
560
+ tmpBody.setAttribute(tmpStyleKey, pNodeData.BodyStyle[tmpStyleKey]);
561
+ }
562
+ }
563
+
532
564
  // Apply per-instance style overrides (for node-specific editing)
533
565
  // These must be applied as inline styles so they override CSS rules
534
566
  // (CSS declarations take precedence over SVG presentation attributes).
@@ -561,6 +593,11 @@ class PictViewFlowNode extends libPictView
561
593
  {
562
594
  tmpTitleBar.setAttribute('fill', pNodeTypeConfig.TitleBarColor);
563
595
  }
596
+ // Per-node TitleBarColor (wins over node-type default).
597
+ if (pNodeData.TitleBarColor)
598
+ {
599
+ tmpTitleBar.setAttribute('fill', pNodeData.TitleBarColor);
600
+ }
564
601
 
565
602
  pGroup.appendChild(tmpTitleBar);
566
603
 
@@ -578,6 +615,10 @@ class PictViewFlowNode extends libPictView
578
615
  {
579
616
  tmpTitleBarBottom.setAttribute('fill', pNodeTypeConfig.TitleBarColor);
580
617
  }
618
+ if (pNodeData.TitleBarColor)
619
+ {
620
+ tmpTitleBarBottom.setAttribute('fill', pNodeData.TitleBarColor);
621
+ }
581
622
 
582
623
  // Per-instance title bar color override
583
624
  // Applied as inline style to override CSS rules.