pict-section-flow 1.3.0 → 2.0.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.
Files changed (162) hide show
  1. package/package.json +7 -2
  2. package/source/Pict-Section-Flow.js +20 -14
  3. package/source/providers/PictProvider-Flow-Background.js +303 -0
  4. package/source/providers/PictProvider-Flow-CSS.js +99 -7
  5. package/source/providers/PictProvider-Flow-ConnectorShapes.js +8 -0
  6. package/source/providers/PictProvider-Flow-Geometry.js +11 -421
  7. package/source/providers/PictProvider-Flow-Icons.js +20 -0
  8. package/source/providers/PictProvider-Flow-Layouts.js +107 -0
  9. package/source/services/PictService-Flow-ConnectionRenderer.js +77 -5
  10. package/source/services/PictService-Flow-CursorManager.js +113 -0
  11. package/source/services/PictService-Flow-InteractionManager.js +443 -61
  12. package/source/services/PictService-Flow-Layout.js +21 -16
  13. package/source/services/PictService-Flow-PathGenerator.js +30 -417
  14. package/source/services/PictService-Flow-RenderManager.js +9 -1
  15. package/source/services/PictService-Flow-ViewportManager.js +102 -0
  16. package/source/views/PictView-Flow-FloatingToolbar.js +57 -0
  17. package/source/views/PictView-Flow-Node.js +36 -0
  18. package/source/views/PictView-Flow-PropertiesPanel.js +27 -5
  19. package/source/views/PictView-Flow-Toolbar.js +148 -13
  20. package/source/views/PictView-Flow.js +628 -3
  21. package/.claude/launch.json +0 -11
  22. package/docs/.nojekyll +0 -0
  23. package/docs/Architecture.md +0 -163
  24. package/docs/Custom-Styling.md +0 -275
  25. package/docs/Data_Model.md +0 -149
  26. package/docs/Event_System.md +0 -156
  27. package/docs/Getting_Started.md +0 -237
  28. package/docs/Implementation_Reference.md +0 -528
  29. package/docs/Layout_Persistence.md +0 -117
  30. package/docs/README.md +0 -103
  31. package/docs/Theme_Integration.md +0 -150
  32. package/docs/_brand.json +0 -18
  33. package/docs/_cover.md +0 -17
  34. package/docs/_playground.json +0 -24
  35. package/docs/_sidebar.md +0 -57
  36. package/docs/_topbar.md +0 -8
  37. package/docs/_version.json +0 -7
  38. package/docs/api/PictFlowCard.md +0 -216
  39. package/docs/api/PictFlowCardPropertiesPanel.md +0 -235
  40. package/docs/api/addConnection.md +0 -101
  41. package/docs/api/addNode.md +0 -137
  42. package/docs/api/autoLayout.md +0 -77
  43. package/docs/api/getFlowData.md +0 -112
  44. package/docs/api/marshalToView.md +0 -95
  45. package/docs/api/openPanel.md +0 -128
  46. package/docs/api/registerHandler.md +0 -174
  47. package/docs/api/registerNodeType.md +0 -142
  48. package/docs/api/removeConnection.md +0 -57
  49. package/docs/api/removeNode.md +0 -80
  50. package/docs/api/saveLayout.md +0 -152
  51. package/docs/api/screenToSVGCoords.md +0 -68
  52. package/docs/api/selectNode.md +0 -116
  53. package/docs/api/setTheme.md +0 -168
  54. package/docs/api/setZoom.md +0 -97
  55. package/docs/api/toggleFullscreen.md +0 -68
  56. package/docs/card-help/EACH.md +0 -19
  57. package/docs/card-help/FREAD.md +0 -24
  58. package/docs/card-help/FWRITE.md +0 -24
  59. package/docs/card-help/GET.md +0 -22
  60. package/docs/card-help/ITE.md +0 -23
  61. package/docs/card-help/LOG.md +0 -23
  62. package/docs/card-help/NOTE.md +0 -17
  63. package/docs/card-help/PREV.md +0 -18
  64. package/docs/card-help/SET.md +0 -27
  65. package/docs/card-help/SPKL.md +0 -22
  66. package/docs/card-help/STAT.md +0 -23
  67. package/docs/card-help/SW.md +0 -25
  68. package/docs/diagrams/architecture-at-a-glance.excalidraw +0 -4270
  69. package/docs/diagrams/architecture-at-a-glance.mmd +0 -30
  70. package/docs/diagrams/architecture-at-a-glance.svg +0 -2
  71. package/docs/diagrams/data-flow.excalidraw +0 -1451
  72. package/docs/diagrams/data-flow.mmd +0 -17
  73. package/docs/diagrams/data-flow.svg +0 -2
  74. package/docs/diagrams/high-level-design.excalidraw +0 -5767
  75. package/docs/diagrams/high-level-design.mmd +0 -86
  76. package/docs/diagrams/high-level-design.svg +0 -2
  77. package/docs/diagrams/relationships.excalidraw +0 -3852
  78. package/docs/diagrams/relationships.mmd +0 -9
  79. package/docs/diagrams/relationships.svg +0 -2
  80. package/docs/diagrams/service-initialization-sequence.excalidraw +0 -1466
  81. package/docs/diagrams/service-initialization-sequence.mmd +0 -19
  82. package/docs/diagrams/service-initialization-sequence.svg +0 -2
  83. package/docs/diagrams/svg-layer-structure.excalidraw +0 -1060
  84. package/docs/diagrams/svg-layer-structure.mmd +0 -18
  85. package/docs/diagrams/svg-layer-structure.svg +0 -2
  86. package/docs/examples/README.md +0 -9
  87. package/docs/examples/simple_cards/README.md +0 -677
  88. package/docs/examples/simple_cards/css/flowexample.css +0 -65
  89. package/docs/examples/simple_cards/index.html +0 -32
  90. package/docs/examples/simple_cards/js/pict.min.js +0 -12
  91. package/docs/examples/simple_cards/pict-section-flow-example-simple-cards.compatible.min.js +0 -1
  92. package/docs/index.html +0 -38
  93. package/docs/playground/app.json +0 -6
  94. package/docs/playground/appdata.json +0 -85
  95. package/docs/playground/application.js +0 -23
  96. package/docs/playground/pict.json +0 -17
  97. package/docs/playground/runtime/pict-application.min.js +0 -2
  98. package/docs/playground/runtime/pict-section-flow.min.js +0 -2
  99. package/docs/playground/runtime/pict-section-modal.min.js +0 -2
  100. package/docs/playground/runtime/pict.min.js +0 -12
  101. package/docs/retold-catalog.json +0 -244
  102. package/docs/retold-keyword-index.json +0 -26028
  103. package/example_applications/simple_cards/css/flowexample.css +0 -65
  104. package/example_applications/simple_cards/html/index.html +0 -32
  105. package/example_applications/simple_cards/package.json +0 -52
  106. package/example_applications/simple_cards/source/Pict-Application-FlowExample-Configuration.json +0 -15
  107. package/example_applications/simple_cards/source/Pict-Application-FlowExample.js +0 -539
  108. package/example_applications/simple_cards/source/card-help-content.js +0 -16
  109. package/example_applications/simple_cards/source/cards/FlowCard-Comment.js +0 -38
  110. package/example_applications/simple_cards/source/cards/FlowCard-DataPreview.js +0 -44
  111. package/example_applications/simple_cards/source/cards/FlowCard-Each.js +0 -38
  112. package/example_applications/simple_cards/source/cards/FlowCard-FileRead.js +0 -56
  113. package/example_applications/simple_cards/source/cards/FlowCard-FileWrite.js +0 -50
  114. package/example_applications/simple_cards/source/cards/FlowCard-GetValue.js +0 -37
  115. package/example_applications/simple_cards/source/cards/FlowCard-IfThenElse.js +0 -49
  116. package/example_applications/simple_cards/source/cards/FlowCard-LogValues.js +0 -55
  117. package/example_applications/simple_cards/source/cards/FlowCard-SetValue.js +0 -97
  118. package/example_applications/simple_cards/source/cards/FlowCard-Sparkline.js +0 -100
  119. package/example_applications/simple_cards/source/cards/FlowCard-StatusMonitor.js +0 -46
  120. package/example_applications/simple_cards/source/cards/FlowCard-Switch.js +0 -39
  121. package/example_applications/simple_cards/source/providers/PictRouter-FlowExample-Configuration.json +0 -22
  122. package/example_applications/simple_cards/source/sample-flows.js +0 -410
  123. package/example_applications/simple_cards/source/views/PictView-FlowExample-About.js +0 -184
  124. package/example_applications/simple_cards/source/views/PictView-FlowExample-BottomBar.js +0 -77
  125. package/example_applications/simple_cards/source/views/PictView-FlowExample-Documentation.js +0 -325
  126. package/example_applications/simple_cards/source/views/PictView-FlowExample-FileWriteInfo.js +0 -59
  127. package/example_applications/simple_cards/source/views/PictView-FlowExample-Layout.js +0 -90
  128. package/example_applications/simple_cards/source/views/PictView-FlowExample-MainWorkspace.js +0 -453
  129. package/example_applications/simple_cards/source/views/PictView-FlowExample-TopBar.js +0 -95
  130. package/scripts/generate-card-help.js +0 -214
  131. package/source/providers/edges/Edge-Bezier.js +0 -41
  132. package/source/providers/edges/Edge-Orthogonal.js +0 -37
  133. package/source/providers/edges/Edge-OrthogonalSnap.js +0 -72
  134. package/source/providers/edges/Edge-Perimeter-Linear.js +0 -31
  135. package/source/providers/edges/Edge-Perimeter-Orthogonal.js +0 -39
  136. package/source/providers/edges/Edge-Perimeter.js +0 -48
  137. package/source/providers/edges/Edge-PerimeterMath.js +0 -92
  138. package/source/providers/edges/Edge-Straight.js +0 -24
  139. package/source/providers/layouts/Layout-Circular.js +0 -203
  140. package/source/providers/layouts/Layout-Coerce.js +0 -40
  141. package/source/providers/layouts/Layout-Columnar.js +0 -134
  142. package/source/providers/layouts/Layout-Custom.js +0 -27
  143. package/source/providers/layouts/Layout-ForcedFromCenter.js +0 -256
  144. package/source/providers/layouts/Layout-Grid.js +0 -134
  145. package/source/providers/layouts/Layout-Layered.js +0 -155
  146. package/source/providers/layouts/Layout-Rank.js +0 -141
  147. package/source/providers/layouts/Layout-Staggered.js +0 -131
  148. package/source/providers/layouts/Layout-Tabular.js +0 -94
  149. package/test/ConnectionHandleManager_tests.js +0 -717
  150. package/test/ConnectionRenderer_tests.js +0 -591
  151. package/test/DataManager_tests.js +0 -859
  152. package/test/Geometry_tests.js +0 -767
  153. package/test/InteractionManager_tests.js +0 -279
  154. package/test/Layout_tests.js +0 -1604
  155. package/test/NodeView_tests.js +0 -66
  156. package/test/PanelManager_tests.js +0 -172
  157. package/test/PathGenerator_tests.js +0 -978
  158. package/test/PortRenderer_tests.js +0 -376
  159. package/test/RenderManager_tests.js +0 -756
  160. package/test/Renderer_tests.js +0 -133
  161. package/test/SelectionManager_tests.js +0 -185
  162. package/test/StylePresets_tests.js +0 -153
@@ -11,6 +11,10 @@ const _DefaultConfiguration =
11
11
 
12
12
  FlowViewIdentifier: 'Pict-Flow',
13
13
 
14
+ // Host-supplied buttons (mirrors the docked toolbar's ToolbarExtraButtons), so floating mode keeps
15
+ // the same custom buttons. Each entry is { Hash, Icon, Label?, Tooltip?, Active? }.
16
+ ToolbarExtraButtons: [],
17
+
14
18
  CSS: false,
15
19
 
16
20
  Templates:
@@ -64,6 +68,7 @@ const _DefaultConfiguration =
64
68
  onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('fullscreen')">
65
69
  <span id="Flow-FloatingIcon-fullscreen-{~D:Record.FlowViewIdentifier~}"></span>
66
70
  </button>
71
+ {~TS:Flow-FloatingToolbar-Extra-Button:Record.ToolbarExtraButtons~}
67
72
  <div class="pict-flow-floating-separator"></div>
68
73
  <button class="pict-flow-floating-btn" data-flow-action="dock-toolbar" title="Dock Toolbar"
69
74
  onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleButtonClick('dock-toolbar')">
@@ -71,6 +76,16 @@ const _DefaultConfiguration =
71
76
  </button>
72
77
  </div>
73
78
  `
79
+ },
80
+ {
81
+ Hash: 'Flow-FloatingToolbar-Extra-Button',
82
+ // Icon-only host button (the floating toolbar is compact). Icon span
83
+ // is filled post-render by _populateIcons (keyed by Hash).
84
+ Template: /*html*/`<button class="pict-flow-floating-btn{~D:Record.ToggleClass~}{~D:Record.ActiveClass~}" title="{~D:Record.Tooltip~}" data-flow-action="extra" data-extra-hash="{~D:Record.Hash~}"
85
+ onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._FloatingToolbarView._handleExtraClick('{~D:Record.Hash~}', this)">
86
+ <span id="Flow-FloatingExtraIcon-{~D:Record.Hash~}-{~D:Record.FlowViewIdentifier~}"></span>
87
+ <span class="pict-flow-toolbar-btn-led" aria-hidden="true"></span>
88
+ </button>`
74
89
  }
75
90
  ],
76
91
 
@@ -111,9 +126,37 @@ class PictViewFlowFloatingToolbar extends libPictView
111
126
 
112
127
  render(pRenderableHash, pRenderDestinationAddress, pTemplateRecordAddress)
113
128
  {
129
+ // Stamp the owning view onto each host button so its row resolves.
130
+ let tmpExtraButtons = this.options.ToolbarExtraButtons;
131
+ if (Array.isArray(tmpExtraButtons))
132
+ {
133
+ for (let i = 0; i < tmpExtraButtons.length; i++)
134
+ {
135
+ tmpExtraButtons[i].FlowViewIdentifier = this.options.FlowViewIdentifier;
136
+ // Mirror the docked toolbar so toggle buttons show the same status LED here.
137
+ tmpExtraButtons[i].ToggleClass = tmpExtraButtons[i].Toggle ? ' pict-flow-toolbar-btn-toggle' : '';
138
+ tmpExtraButtons[i].ActiveClass = tmpExtraButtons[i].Active ? ' pict-flow-toolbar-btn-active' : '';
139
+ }
140
+ }
114
141
  return super.render(pRenderableHash, pRenderDestinationAddress, this.options);
115
142
  }
116
143
 
144
+ /**
145
+ * Handle a click on a host-supplied (ToolbarExtraButtons) floating button.
146
+ * Routes to the docked toolbar's _handleExtraAction (the single dispatch
147
+ * point that fires the FlowView's onToolbarButton hook).
148
+ *
149
+ * @param {string} pHash - The button's Hash
150
+ * @param {HTMLElement} pElement - The clicked button element
151
+ */
152
+ _handleExtraClick(pHash, pElement)
153
+ {
154
+ if (this._ToolbarView)
155
+ {
156
+ this._ToolbarView._handleExtraAction(pHash, pElement);
157
+ }
158
+ }
159
+
117
160
  onAfterRender(pRenderable, pRenderDestinationAddress, pRecord, pContent)
118
161
  {
119
162
  let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
@@ -222,6 +265,20 @@ class PictViewFlowFloatingToolbar extends libPictView
222
265
  tmpElements[0].innerHTML = tmpIconProvider.getIconSVGMarkup(tmpIconMap[tmpKeys[i]], 16);
223
266
  }
224
267
  }
268
+
269
+ // Host-supplied extra buttons (keyed by Hash, icon from the button's Icon key).
270
+ let tmpExtraButtons = this.options.ToolbarExtraButtons;
271
+ if (Array.isArray(tmpExtraButtons))
272
+ {
273
+ for (let i = 0; i < tmpExtraButtons.length; i++)
274
+ {
275
+ let tmpExtraIcon = this.pict.ContentAssignment.getElement(`#Flow-FloatingExtraIcon-${tmpExtraButtons[i].Hash}-${tmpFlowViewIdentifier}`);
276
+ if (tmpExtraIcon.length > 0)
277
+ {
278
+ tmpExtraIcon[0].innerHTML = tmpIconProvider.getIconSVGMarkup(tmpExtraButtons[i].Icon, 16);
279
+ }
280
+ }
281
+ }
225
282
  }
226
283
 
227
284
  /**
@@ -52,6 +52,13 @@ class PictViewFlowNode extends libPictView
52
52
  {
53
53
  tmpClassList += ' pict-flow-node-color-' + tmpColorRole;
54
54
  }
55
+ // A host can stamp an extra CSS class onto a node via node.NodeClass (e.g. a moodboard marks a
56
+ // card whose connection points should stay visible on a read-only board). Survives re-renders
57
+ // because it lives on the node data.
58
+ if (typeof pNodeData.NodeClass === 'string' && pNodeData.NodeClass)
59
+ {
60
+ tmpClassList += ' ' + pNodeData.NodeClass;
61
+ }
55
62
  tmpGroup.setAttribute('class', tmpClassList);
56
63
  tmpGroup.setAttribute('transform', `translate(${pNodeData.X}, ${pNodeData.Y})`);
57
64
  tmpGroup.setAttribute('data-node-hash', pNodeData.Hash);
@@ -339,6 +346,35 @@ class PictViewFlowNode extends libPictView
339
346
  tmpGroup.appendChild(tmpHandle);
340
347
  }
341
348
 
349
+ // Rotate handle: a grip on a short arm above the node, shown when this node is selected, the
350
+ // flow allows rotation, and we are not read-only. Its data-element-type routes pointer-down to
351
+ // the InteractionManager's node-rotate path. Lives in node-local space, so it orbits with the card.
352
+ if (pIsSelected && this._FlowView.options && this._FlowView.options.EnableNodeRotation
353
+ && !(typeof this._FlowView.isReadOnly === 'function' && this._FlowView.isReadOnly()))
354
+ {
355
+ let tmpArmLength = 22;
356
+ let tmpRotateCenterX = tmpWidth / 2;
357
+
358
+ let tmpArm = this._FlowView._SVGHelperProvider.createSVGElement('line');
359
+ tmpArm.setAttribute('class', 'pict-flow-node-rotate-arm');
360
+ tmpArm.setAttribute('x1', String(tmpRotateCenterX));
361
+ tmpArm.setAttribute('y1', '0');
362
+ tmpArm.setAttribute('x2', String(tmpRotateCenterX));
363
+ tmpArm.setAttribute('y2', String(-tmpArmLength));
364
+ tmpArm.setAttribute('data-node-hash', pNodeData.Hash);
365
+ tmpArm.setAttribute('data-element-type', 'node-rotate');
366
+ tmpGroup.appendChild(tmpArm);
367
+
368
+ let tmpGrip = this._FlowView._SVGHelperProvider.createSVGElement('circle');
369
+ tmpGrip.setAttribute('class', 'pict-flow-node-rotate-handle');
370
+ tmpGrip.setAttribute('cx', String(tmpRotateCenterX));
371
+ tmpGrip.setAttribute('cy', String(-tmpArmLength - 5));
372
+ tmpGrip.setAttribute('r', '6');
373
+ tmpGrip.setAttribute('data-node-hash', pNodeData.Hash);
374
+ tmpGrip.setAttribute('data-element-type', 'node-rotate');
375
+ tmpGroup.appendChild(tmpGrip);
376
+ }
377
+
342
378
  pNodesLayer.appendChild(tmpGroup);
343
379
  }
344
380
 
@@ -226,14 +226,36 @@ class PictViewFlowPropertiesPanel extends libPictView
226
226
  let tmpFO = pPanelsLayer.querySelector(`[data-panel-hash="${pPanelData.Hash}"]`);
227
227
  if (tmpFO)
228
228
  {
229
- // Render appearance and help tabs. Tab-switching click handlers
230
- // are inline `onclick=` attributes in Flow-PanelChrome-Template
231
- // that call FlowView._handlePanelTabClick switchPanelTab.
232
- this._renderAppearanceTab(pPanelData, tmpFO);
233
- this._renderHelpTab(pPanelData, tmpFO);
229
+ // The Appearance tab edits a node's body/title appearance, which a connection (edge) has none
230
+ // of -- so for a connection panel, hide that tab (and the now-single-tab bar) and leave just
231
+ // the connection's own panel. Node panels keep the appearance + help tabs.
232
+ if (pPanelData.ConnectionHash)
233
+ {
234
+ this._hidePanelTabsForConnection(tmpFO);
235
+ }
236
+ else
237
+ {
238
+ // Tab-switching click handlers are inline `onclick=` attributes in Flow-PanelChrome-Template
239
+ // that call FlowView._handlePanelTabClick → switchPanelTab.
240
+ this._renderAppearanceTab(pPanelData, tmpFO);
241
+ this._renderHelpTab(pPanelData, tmpFO);
242
+ }
234
243
  }
235
244
  }
236
245
 
246
+ // Hide the node-oriented Appearance (and Help) tabs plus the tab bar for a connection panel, so it
247
+ // shows only its single Properties pane with no lone tab.
248
+ _hidePanelTabsForConnection(pForeignObject)
249
+ {
250
+ if (!pForeignObject) { return; }
251
+ let tmpAppearanceTab = pForeignObject.querySelector('.pict-flow-panel-tab[data-tab-target="appearance"]');
252
+ if (tmpAppearanceTab) { tmpAppearanceTab.style.display = 'none'; }
253
+ let tmpAppearancePane = pForeignObject.querySelector('.pict-flow-panel-tab-pane[data-tab="appearance"]');
254
+ if (tmpAppearancePane) { tmpAppearancePane.style.display = 'none'; }
255
+ let tmpTabbar = pForeignObject.querySelector('.pict-flow-panel-tabbar');
256
+ if (tmpTabbar) { tmpTabbar.style.display = 'none'; }
257
+ }
258
+
237
259
  /**
238
260
  * Instantiate (or reuse) the panel type implementation and render into the body container.
239
261
  *
@@ -15,6 +15,10 @@ const _DefaultConfiguration =
15
15
  EnableAddNode: true,
16
16
  EnableCardPalette: true,
17
17
 
18
+ // Host-supplied buttons (set by the FlowView from its own ToolbarExtraButtons option). Each entry
19
+ // is { Hash, Icon, Label?, Tooltip?, Active? }. Rendered as a group via Flow-Toolbar-Extra-Button.
20
+ ToolbarExtraButtons: [],
21
+
18
22
  CSS: false,
19
23
 
20
24
  Templates:
@@ -77,6 +81,7 @@ const _DefaultConfiguration =
77
81
  <span class="pict-flow-toolbar-btn-icon" id="Flow-Toolbar-Icon-zoom-fit-{~D:Record.FlowViewIdentifier~}"></span>
78
82
  </button>
79
83
  </div>
84
+ <div class="pict-flow-toolbar-group pict-flow-toolbar-extra">{~TS:Flow-Toolbar-Extra-Button:Record.ToolbarExtraButtons~}</div>
80
85
  <div class="pict-flow-toolbar-group pict-flow-toolbar-right">
81
86
  <button class="pict-flow-toolbar-btn" data-flow-action="settings-popup" id="Flow-Toolbar-Settings-{~D:Record.FlowViewIdentifier~}" title="Theme Settings"
82
87
  onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._handleToolbarAction('settings-popup')">
@@ -105,6 +110,19 @@ const _DefaultConfiguration =
105
110
  <div class="pict-flow-toolbar-popup-anchor" id="Flow-Toolbar-PopupAnchor-{~D:Record.FlowViewIdentifier~}">
106
111
  </div>
107
112
  `
113
+ },
114
+ {
115
+ Hash: 'Flow-Toolbar-Extra-Button',
116
+ // Host-supplied button. The icon span is filled post-render by
117
+ // _populateToolbarIcons (keyed by Hash), matching how the built-in
118
+ // button icons are injected. FlowViewIdentifier + ActiveClass are
119
+ // stamped onto each row in render().
120
+ Template: /*html*/`<button class="pict-flow-toolbar-btn{~D:Record.ToggleClass~}{~D:Record.ActiveClass~}" id="Flow-Toolbar-Extra-{~D:Record.Hash~}-{~D:Record.FlowViewIdentifier~}" title="{~D:Record.Tooltip~}" data-flow-action="extra" data-extra-hash="{~D:Record.Hash~}"
121
+ onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._ToolbarView._handleExtraAction('{~D:Record.Hash~}', this)">
122
+ <span class="pict-flow-toolbar-btn-icon" id="Flow-Toolbar-ExtraIcon-{~D:Record.Hash~}-{~D:Record.FlowViewIdentifier~}"></span>
123
+ <span class="pict-flow-toolbar-btn-text">{~D:Record.Label~}</span>
124
+ <span class="pict-flow-toolbar-btn-led" aria-hidden="true"></span>
125
+ </button>`
108
126
  },
109
127
  {
110
128
  Hash: 'Flow-AddNode-List',
@@ -121,7 +139,7 @@ const _DefaultConfiguration =
121
139
  + ' onclick="_Pict.views[\'{~D:Record.FlowViewIdentifier~}\']._ToolbarView._addNodeFromPopup(this.getAttribute(\'data-node-type\'))">'
122
140
  + '<span class="pict-flow-popup-list-item-icon">{~D:Record.IconHTML~}</span>'
123
141
  + '<span class="pict-flow-popup-list-item-label">{~D:Record.Label~}</span>'
124
- + '{~NE:Record.Code^<span class="pict-flow-popup-list-item-code">{~D:Record.Code~}</span>~}'
142
+ + '{~D:Record.CodeBlock~}'
125
143
  + '</div>'
126
144
  },
127
145
  {
@@ -137,13 +155,16 @@ const _DefaultConfiguration =
137
155
  },
138
156
  {
139
157
  Hash: 'Flow-Cards-Card',
158
+ // The icon / swatch / code spans are pre-rendered into complete HTML blocks by
159
+ // _buildCardsPopup (a block is '' when its piece is absent). The template can't build them
160
+ // inline: the engine does not parse a nested {~D:~} inside a {~NE:~} (its `~}` terminator
161
+ // collides with the inner tag's), which left the palette showing raw template literals.
140
162
  Template: '<div class="pict-flow-palette-card{~D:Record.DisabledClass~}" data-card-type="{~D:Record.CardType~}" title="{~D:Record.Tooltip~}"'
141
163
  + ' onclick="_Pict.views[\'{~D:Record.FlowViewIdentifier~}\']._ToolbarView._addCardFromPopup(this.getAttribute(\'data-card-type\'))">'
142
- + '{~NE:Record.IconHTML^<span class="pict-flow-palette-card-icon">{~D:Record.IconHTML~}</span>~}'
143
- + '{~NE:Record.IconEmoji^<span class="pict-flow-palette-card-icon">{~D:Record.IconEmoji~}</span>~}'
144
- + '{~NE:Record.SwatchColor^<span class="pict-flow-palette-card-swatch" style="background-color: {~D:Record.SwatchColor~};"></span>~}'
164
+ + '{~D:Record.IconBlock~}'
165
+ + '{~D:Record.SwatchBlock~}'
145
166
  + '<span class="pict-flow-palette-card-title">{~D:Record.Label~}</span>'
146
- + '{~NE:Record.Code^<span class="pict-flow-palette-card-code">{~D:Record.Code~}</span>~}'
167
+ + '{~D:Record.CodeBlock~}'
147
168
  + '</div>'
148
169
  },
149
170
  {
@@ -212,11 +233,35 @@ class PictViewFlowToolbar extends libPictView
212
233
 
213
234
  render(pRenderableHash, pRenderDestinationAddress, pTemplateRecordAddress)
214
235
  {
236
+ // Stamp the per-row render fields onto each host-supplied button so the
237
+ // Flow-Toolbar-Extra-Button rows resolve their owning view and active
238
+ // state (nested {~D:~} addressing inside {~TS:~} is not supported).
239
+ this._stampExtraButtons();
215
240
  // Pass this.options as the template record so {~D:Record.FlowViewIdentifier~}
216
241
  // resolves correctly in the toolbar template.
217
242
  return super.render(pRenderableHash, pRenderDestinationAddress, this.options);
218
243
  }
219
244
 
245
+ /**
246
+ * Stamp FlowViewIdentifier + ActiveClass onto each ToolbarExtraButtons entry
247
+ * so the row template can address them.
248
+ */
249
+ _stampExtraButtons()
250
+ {
251
+ let tmpExtraButtons = this.options.ToolbarExtraButtons;
252
+ if (!Array.isArray(tmpExtraButtons)) return;
253
+ for (let i = 0; i < tmpExtraButtons.length; i++)
254
+ {
255
+ tmpExtraButtons[i].FlowViewIdentifier = this.options.FlowViewIdentifier;
256
+ tmpExtraButtons[i].ActiveClass = tmpExtraButtons[i].Active ? ' pict-flow-toolbar-btn-active' : '';
257
+ // Toggle buttons (stateful on/off, e.g. a pan or connect toggle) get a status LED that the
258
+ // ActiveClass fills; plain action buttons (Toggle falsy) render no LED.
259
+ tmpExtraButtons[i].ToggleClass = tmpExtraButtons[i].Toggle ? ' pict-flow-toolbar-btn-toggle' : '';
260
+ // A label-less (icon-only) button renders an empty text span; CSS (:empty) collapses it.
261
+ if (typeof tmpExtraButtons[i].Label !== 'string') { tmpExtraButtons[i].Label = ''; }
262
+ }
263
+ }
264
+
220
265
  onAfterRender(pRenderable, pRenderDestinationAddress, pRecord, pContent)
221
266
  {
222
267
  let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
@@ -321,6 +366,20 @@ class PictViewFlowToolbar extends libPictView
321
366
  {
322
367
  tmpAutoChevron[0].innerHTML = tmpIconProvider.getIconSVGMarkup('chevron-down', 8);
323
368
  }
369
+
370
+ // Host-supplied extra buttons (keyed by Hash, icon from the button's Icon key).
371
+ let tmpExtraButtons = this.options.ToolbarExtraButtons;
372
+ if (Array.isArray(tmpExtraButtons))
373
+ {
374
+ for (let i = 0; i < tmpExtraButtons.length; i++)
375
+ {
376
+ let tmpExtraIcon = this.pict.ContentAssignment.getElement(`#Flow-Toolbar-ExtraIcon-${tmpExtraButtons[i].Hash}-${tmpFlowViewIdentifier}`);
377
+ if (tmpExtraIcon.length > 0)
378
+ {
379
+ tmpExtraIcon[0].innerHTML = tmpIconProvider.getIconSVGMarkup(tmpExtraButtons[i].Icon, 14);
380
+ }
381
+ }
382
+ }
324
383
  }
325
384
 
326
385
  // ── Popup Management ──────────────────────────────────────────────────
@@ -548,12 +607,16 @@ class PictViewFlowToolbar extends libPictView
548
607
  let tmpResolvedKey = tmpIconProvider.resolveIconKey(tmpMeta);
549
608
  tmpIconHTML = tmpIconProvider.getIconSVGMarkup(tmpResolvedKey, 16);
550
609
  }
610
+ let tmpRowCode = tmpMeta.Code || '';
551
611
  tmpRows.push(
552
612
  {
553
613
  NodeType: tmpTypeKeys[i],
554
614
  Label: tmpTypeConfig.Label || '',
555
615
  IconHTML: tmpIconHTML,
556
- Code: tmpMeta.Code || '',
616
+ Code: tmpRowCode,
617
+ // Pre-rendered so the template renders it with {~D:~} (a nested {~D:~} inside {~NE:~} is
618
+ // not parsed by the engine).
619
+ CodeBlock: tmpRowCode ? ('<span class="pict-flow-popup-list-item-code">' + tmpRowCode + '</span>') : '',
557
620
  FlowViewIdentifier: tmpFlowViewIdentifier
558
621
  });
559
622
  }
@@ -682,16 +745,27 @@ class PictViewFlowToolbar extends libPictView
682
745
  tmpIconHTML = tmpIconProvider.getIconSVGMarkup('default', 14);
683
746
  }
684
747
 
748
+ // Pre-render each conditional span into a complete HTML block ('' when absent). The
749
+ // template renders these directly with {~D:~}; it cannot wrap them inline because the
750
+ // engine does not parse a nested {~D:~} inside a {~NE:~}.
751
+ let tmpCode = tmpMeta.Code || '';
752
+ let tmpSwatchColor = (!tmpIconHTML && !tmpIsEmoji && tmpCardConfig.TitleBarColor) ? tmpCardConfig.TitleBarColor : '';
753
+ let tmpIconBlock = '';
754
+ if (tmpIconHTML) { tmpIconBlock = '<span class="pict-flow-palette-card-icon">' + tmpIconHTML + '</span>'; }
755
+ else if (tmpIsEmoji) { tmpIconBlock = '<span class="pict-flow-palette-card-icon">' + tmpMeta.Icon + '</span>'; }
756
+ let tmpSwatchBlock = tmpSwatchColor ? ('<span class="pict-flow-palette-card-swatch" style="background-color: ' + tmpSwatchColor + ';"></span>') : '';
757
+ let tmpCodeBlock = tmpCode ? ('<span class="pict-flow-palette-card-code">' + tmpCode + '</span>') : '';
758
+
685
759
  tmpMatching.push(
686
760
  {
687
761
  CardType: tmpCardConfig.Hash,
688
762
  Label: tmpCardConfig.Label || '',
689
- Code: tmpMeta.Code || '',
690
- IconHTML: tmpIconHTML,
691
- IconEmoji: tmpIsEmoji ? tmpMeta.Icon : '',
763
+ Code: tmpCode,
764
+ IconBlock: tmpIconBlock,
765
+ SwatchBlock: tmpSwatchBlock,
766
+ CodeBlock: tmpCodeBlock,
692
767
  DisabledClass: (tmpMeta.Enabled === false) ? ' disabled' : '',
693
768
  Tooltip: tmpMeta.Tooltip || tmpMeta.Description || '',
694
- SwatchColor: (!tmpIconHTML && !tmpIsEmoji && tmpCardConfig.TitleBarColor) ? tmpCardConfig.TitleBarColor : '',
695
769
  FlowViewIdentifier: tmpFlowViewIdentifier
696
770
  });
697
771
  }
@@ -1532,7 +1606,7 @@ class PictViewFlowToolbar extends libPictView
1532
1606
  let tmpNoiseEnabled = !!(tmpActiveRenderer && tmpActiveRenderer.NoiseConfig && tmpActiveRenderer.NoiseConfig.Enabled);
1533
1607
  let tmpNoiseDisplay = tmpNoiseEnabled ? '' : 'display:none;';
1534
1608
 
1535
- this.pict.ContentAssignment.assignContent(pContainer,
1609
+ let tmpHTML =
1536
1610
  '<div class="pict-flow-popup-settings-section">'
1537
1611
  + '<label class="pict-flow-popup-settings-label">Theme</label>'
1538
1612
  + '<select class="pict-flow-popup-settings-select"'
@@ -1551,7 +1625,50 @@ class PictViewFlowToolbar extends libPictView
1551
1625
  + ' oninput="_Pict.views[\'' + tmpFlowViewIdentifier + '\']._ToolbarView._handleNoiseSliderInput(this)" />'
1552
1626
  + '<span class="pict-flow-popup-settings-slider-value">' + tmpNoiseLevel + '%</span>'
1553
1627
  + '</div>'
1554
- + '</div>');
1628
+ + '</div>';
1629
+
1630
+ tmpHTML += this._buildHostSettingsSections();
1631
+
1632
+ this.pict.ContentAssignment.assignContent(pContainer, tmpHTML);
1633
+ }
1634
+
1635
+ /**
1636
+ * Host-contributed settings sections. A consumer adds entries to the flow view's
1637
+ * `SettingsSections` option so its own controls (e.g. a whiteboard's background
1638
+ * picker) live in the gear popup instead of a separate panel. Each entry is
1639
+ * `{ Label?, HTML? , Build?(flowView) }`; Build is evaluated at open time so a
1640
+ * section can reflect live state. Returns concatenated section markup ('' when none).
1641
+ * @returns {string}
1642
+ */
1643
+ _buildHostSettingsSections()
1644
+ {
1645
+ let tmpSections = (this._FlowView && this._FlowView.options && Array.isArray(this._FlowView.options.SettingsSections))
1646
+ ? this._FlowView.options.SettingsSections
1647
+ : [];
1648
+ let tmpOut = '';
1649
+ for (let i = 0; i < tmpSections.length; i++)
1650
+ {
1651
+ let tmpSection = tmpSections[i];
1652
+ let tmpSectionHTML = '';
1653
+ if (typeof tmpSection.Build === 'function')
1654
+ {
1655
+ tmpSectionHTML = tmpSection.Build(this._FlowView) || '';
1656
+ }
1657
+ else if (typeof tmpSection.HTML === 'string')
1658
+ {
1659
+ tmpSectionHTML = tmpSection.HTML;
1660
+ }
1661
+ if (!tmpSectionHTML)
1662
+ {
1663
+ continue;
1664
+ }
1665
+ tmpOut += '<div class="pict-flow-popup-divider"></div>'
1666
+ + '<div class="pict-flow-popup-settings-section">'
1667
+ + (tmpSection.Label ? '<label class="pict-flow-popup-settings-label">' + tmpSection.Label + '</label>' : '')
1668
+ + tmpSectionHTML
1669
+ + '</div>';
1670
+ }
1671
+ return tmpOut;
1555
1672
  }
1556
1673
 
1557
1674
  /**
@@ -1677,7 +1794,8 @@ class PictViewFlowToolbar extends libPictView
1677
1794
  FlowViewIdentifier: tmpFlowViewIdentifier,
1678
1795
  DefaultDestinationAddress: `#Flow-FloatingToolbar-Container-${tmpFlowViewIdentifier}`,
1679
1796
  EnableAddNode: this.options.EnableAddNode,
1680
- EnableCardPalette: this.options.EnableCardPalette
1797
+ EnableCardPalette: this.options.EnableCardPalette,
1798
+ ToolbarExtraButtons: this.options.ToolbarExtraButtons
1681
1799
  }
1682
1800
  );
1683
1801
  this._FloatingToolbarView._ToolbarView = this;
@@ -1742,6 +1860,23 @@ class PictViewFlowToolbar extends libPictView
1742
1860
  * Handle a toolbar action
1743
1861
  * @param {string} pAction
1744
1862
  */
1863
+ /**
1864
+ * Handle a click on a host-supplied (ToolbarExtraButtons) button. Routes to
1865
+ * the FlowView's onToolbarButton hook with the button hash and the clicked
1866
+ * element (so the host can anchor a popover next to it).
1867
+ *
1868
+ * @param {string} pHash - The button's Hash
1869
+ * @param {HTMLElement} pElement - The clicked button element
1870
+ */
1871
+ _handleExtraAction(pHash, pElement)
1872
+ {
1873
+ if (!this._FlowView) return;
1874
+ if (typeof this._FlowView.options.onToolbarButton === 'function')
1875
+ {
1876
+ this._FlowView.options.onToolbarButton(pHash, pElement);
1877
+ }
1878
+ }
1879
+
1745
1880
  _handleToolbarAction(pAction)
1746
1881
  {
1747
1882
  if (!this._FlowView) return;