pict-section-flow 0.0.10 → 0.0.13

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 (88) hide show
  1. package/.claude/launch.json +1 -1
  2. package/README.md +176 -0
  3. package/docs/.nojekyll +0 -0
  4. package/docs/Architecture.md +303 -0
  5. package/docs/Custom-Styling.md +275 -0
  6. package/docs/Data_Model.md +158 -0
  7. package/docs/Event_System.md +156 -0
  8. package/docs/Getting_Started.md +237 -0
  9. package/docs/Implementation_Reference.md +528 -0
  10. package/docs/Layout_Persistence.md +117 -0
  11. package/docs/README.md +115 -52
  12. package/docs/_cover.md +11 -0
  13. package/docs/_sidebar.md +52 -0
  14. package/docs/_topbar.md +8 -0
  15. package/docs/api/PictFlowCard.md +216 -0
  16. package/docs/api/PictFlowCardPropertiesPanel.md +235 -0
  17. package/docs/api/addConnection.md +101 -0
  18. package/docs/api/addNode.md +137 -0
  19. package/docs/api/autoLayout.md +77 -0
  20. package/docs/api/getFlowData.md +112 -0
  21. package/docs/api/marshalToView.md +95 -0
  22. package/docs/api/openPanel.md +128 -0
  23. package/docs/api/registerHandler.md +174 -0
  24. package/docs/api/registerNodeType.md +142 -0
  25. package/docs/api/removeConnection.md +57 -0
  26. package/docs/api/removeNode.md +80 -0
  27. package/docs/api/saveLayout.md +152 -0
  28. package/docs/api/screenToSVGCoords.md +68 -0
  29. package/docs/api/selectNode.md +116 -0
  30. package/docs/api/setTheme.md +168 -0
  31. package/docs/api/setZoom.md +97 -0
  32. package/docs/api/toggleFullscreen.md +68 -0
  33. package/docs/card-help/EACH.md +19 -0
  34. package/docs/card-help/FREAD.md +24 -0
  35. package/docs/card-help/FWRITE.md +24 -0
  36. package/docs/card-help/GET.md +22 -0
  37. package/docs/card-help/ITE.md +23 -0
  38. package/docs/card-help/LOG.md +23 -0
  39. package/docs/card-help/NOTE.md +17 -0
  40. package/docs/card-help/PREV.md +18 -0
  41. package/docs/card-help/SET.md +27 -0
  42. package/docs/card-help/SPKL.md +22 -0
  43. package/docs/card-help/STAT.md +23 -0
  44. package/docs/card-help/SW.md +25 -0
  45. package/docs/css/docuserve.css +73 -0
  46. package/docs/index.html +39 -0
  47. package/docs/retold-catalog.json +169 -0
  48. package/docs/retold-keyword-index.json +13942 -0
  49. package/example_applications/simple_cards/package.json +1 -0
  50. package/example_applications/simple_cards/source/card-help-content.js +16 -0
  51. package/example_applications/simple_cards/source/cards/FlowCard-Comment.js +2 -0
  52. package/example_applications/simple_cards/source/cards/FlowCard-DataPreview.js +2 -0
  53. package/example_applications/simple_cards/source/cards/FlowCard-Each.js +2 -0
  54. package/example_applications/simple_cards/source/cards/FlowCard-FileRead.js +2 -0
  55. package/example_applications/simple_cards/source/cards/FlowCard-FileWrite.js +2 -0
  56. package/example_applications/simple_cards/source/cards/FlowCard-GetValue.js +2 -0
  57. package/example_applications/simple_cards/source/cards/FlowCard-IfThenElse.js +2 -0
  58. package/example_applications/simple_cards/source/cards/FlowCard-LogValues.js +2 -0
  59. package/example_applications/simple_cards/source/cards/FlowCard-SetValue.js +2 -0
  60. package/example_applications/simple_cards/source/cards/FlowCard-Sparkline.js +2 -0
  61. package/example_applications/simple_cards/source/cards/FlowCard-StatusMonitor.js +2 -0
  62. package/example_applications/simple_cards/source/cards/FlowCard-Switch.js +2 -0
  63. package/package.json +11 -7
  64. package/scripts/generate-card-help.js +214 -0
  65. package/source/Pict-Section-Flow.js +4 -0
  66. package/source/PictFlowCard.js +3 -1
  67. package/source/providers/PictProvider-Flow-CSS.js +245 -152
  68. package/source/providers/PictProvider-Flow-ConnectorShapes.js +24 -0
  69. package/source/providers/PictProvider-Flow-Geometry.js +195 -38
  70. package/source/providers/PictProvider-Flow-PanelChrome.js +14 -12
  71. package/source/services/PictService-Flow-ConnectionHandleManager.js +263 -0
  72. package/source/services/PictService-Flow-ConnectionRenderer.js +134 -183
  73. package/source/services/PictService-Flow-DataManager.js +338 -0
  74. package/source/services/PictService-Flow-InteractionManager.js +165 -7
  75. package/source/services/PictService-Flow-PathGenerator.js +282 -0
  76. package/source/services/PictService-Flow-PortRenderer.js +269 -0
  77. package/source/services/PictService-Flow-RenderManager.js +281 -0
  78. package/source/services/PictService-Flow-Tether.js +6 -42
  79. package/source/views/PictView-Flow-Node.js +2 -220
  80. package/source/views/PictView-Flow-PropertiesPanel.js +89 -44
  81. package/source/views/PictView-Flow.js +130 -882
  82. package/test/ConnectionHandleManager_tests.js +717 -0
  83. package/test/ConnectionRenderer_tests.js +591 -0
  84. package/test/DataManager_tests.js +859 -0
  85. package/test/Geometry_tests.js +767 -0
  86. package/test/PathGenerator_tests.js +978 -0
  87. package/test/PortRenderer_tests.js +367 -0
  88. package/test/RenderManager_tests.js +756 -0
@@ -8,6 +8,10 @@ const libPictServiceFlowPathGenerator = require('../services/PictService-Flow-Pa
8
8
  const libPictServiceFlowViewportManager = require('../services/PictService-Flow-ViewportManager.js');
9
9
  const libPictServiceFlowSelectionManager = require('../services/PictService-Flow-SelectionManager.js');
10
10
  const libPictServiceFlowPanelManager = require('../services/PictService-Flow-PanelManager.js');
11
+ const libPictServiceFlowDataManager = require('../services/PictService-Flow-DataManager.js');
12
+ const libPictServiceFlowConnectionHandleManager = require('../services/PictService-Flow-ConnectionHandleManager.js');
13
+ const libPictServiceFlowRenderManager = require('../services/PictService-Flow-RenderManager.js');
14
+ const libPictServiceFlowPortRenderer = require('../services/PictService-Flow-PortRenderer.js');
11
15
 
12
16
  const libPictProviderFlowNodeTypes = require('../providers/PictProvider-Flow-NodeTypes.js');
13
17
  const libPictProviderFlowEventHandler = require('../providers/PictProvider-Flow-EventHandler.js');
@@ -67,7 +71,7 @@ const _DefaultConfiguration =
67
71
  [
68
72
  {
69
73
  Hash: 'Flow-PanelChrome-Template',
70
- Template: /*html*/`<div class="pict-flow-panel" xmlns="http://www.w3.org/1999/xhtml"><div class="pict-flow-panel-titlebar" data-element-type="panel-titlebar" data-panel-hash="{~D:Record.Hash~}"><span class="pict-flow-panel-title-text">{~D:Record.Title~}</span><span class="pict-flow-panel-close-btn" data-element-type="panel-close" data-panel-hash="{~D:Record.Hash~}"><span class="pict-flow-panel-close-icon"></span></span></div><div class="pict-flow-panel-body" data-panel-hash="{~D:Record.Hash~}"></div><div class="pict-flow-panel-node-props" data-panel-hash="{~D:Record.Hash~}"><div class="pict-flow-panel-node-props-header" data-element-type="node-props-toggle" data-panel-hash="{~D:Record.Hash~}"><span class="pict-flow-panel-node-props-chevron">&#9654;</span><span class="pict-flow-panel-node-props-title">Node Properties</span></div><div class="pict-flow-panel-node-props-body" style="display:none;"></div></div></div>`
74
+ Template: /*html*/`<div class="pict-flow-panel" xmlns="http://www.w3.org/1999/xhtml"><div class="pict-flow-panel-titlebar" data-element-type="panel-titlebar" data-panel-hash="{~D:Record.Hash~}"><span class="pict-flow-panel-title-text">{~D:Record.Title~}</span><span class="pict-flow-panel-close-btn" data-element-type="panel-close" data-panel-hash="{~D:Record.Hash~}"><span class="pict-flow-panel-close-icon"></span></span></div><div class="pict-flow-panel-content" data-panel-hash="{~D:Record.Hash~}"><div class="pict-flow-panel-tab-pane active" data-tab="properties" data-panel-hash="{~D:Record.Hash~}"></div><div class="pict-flow-panel-tab-pane" data-tab="help" data-panel-hash="{~D:Record.Hash~}" style="display:none;"></div><div class="pict-flow-panel-tab-pane" data-tab="appearance" data-panel-hash="{~D:Record.Hash~}" style="display:none;"></div></div><div class="pict-flow-panel-resize-handle" data-element-type="panel-resize" data-panel-hash="{~D:Record.Hash~}"></div><div class="pict-flow-panel-tabbar" data-panel-hash="{~D:Record.Hash~}"><div class="pict-flow-panel-tab active" data-tab-target="properties" data-panel-hash="{~D:Record.Hash~}">Properties</div><div class="pict-flow-panel-tab" data-tab-target="help" data-panel-hash="{~D:Record.Hash~}" style="display:none;">Help</div><div class="pict-flow-panel-tab" data-tab-target="appearance" data-panel-hash="{~D:Record.Hash~}">Appearance</div></div></div>`
71
75
  },
72
76
  {
73
77
  Hash: 'Flow-Container-Template',
@@ -122,119 +126,58 @@ class PictViewFlow extends libPictView
122
126
 
123
127
  this.serviceType = 'PictSectionFlow';
124
128
 
125
- // Register service types with fable so they can be instantiated
126
- if (!this.fable.servicesMap.hasOwnProperty('PictServiceFlowInteractionManager'))
127
- {
128
- this.fable.addServiceType('PictServiceFlowInteractionManager', libPictServiceFlowInteractionManager);
129
- }
130
- if (!this.fable.servicesMap.hasOwnProperty('PictServiceFlowConnectionRenderer'))
131
- {
132
- this.fable.addServiceType('PictServiceFlowConnectionRenderer', libPictServiceFlowConnectionRenderer);
133
- }
134
- if (!this.fable.servicesMap.hasOwnProperty('PictServiceFlowTether'))
135
- {
136
- this.fable.addServiceType('PictServiceFlowTether', libPictServiceFlowTether);
137
- }
138
- if (!this.fable.servicesMap.hasOwnProperty('PictServiceFlowLayout'))
139
- {
140
- this.fable.addServiceType('PictServiceFlowLayout', libPictServiceFlowLayout);
141
- }
142
- if (!this.fable.servicesMap.hasOwnProperty('PictServiceFlowPathGenerator'))
143
- {
144
- this.fable.addServiceType('PictServiceFlowPathGenerator', libPictServiceFlowPathGenerator);
145
- }
146
- if (!this.fable.servicesMap.hasOwnProperty('PictServiceFlowViewportManager'))
147
- {
148
- this.fable.addServiceType('PictServiceFlowViewportManager', libPictServiceFlowViewportManager);
149
- }
150
- if (!this.fable.servicesMap.hasOwnProperty('PictServiceFlowSelectionManager'))
151
- {
152
- this.fable.addServiceType('PictServiceFlowSelectionManager', libPictServiceFlowSelectionManager);
153
- }
154
- if (!this.fable.servicesMap.hasOwnProperty('PictServiceFlowPanelManager'))
155
- {
156
- this.fable.addServiceType('PictServiceFlowPanelManager', libPictServiceFlowPanelManager);
157
- }
158
- if (!this.fable.servicesMap.hasOwnProperty('PictProviderFlowSVGHelpers'))
159
- {
160
- this.fable.addServiceType('PictProviderFlowSVGHelpers', libPictProviderFlowSVGHelpers);
161
- }
162
- if (!this.fable.servicesMap.hasOwnProperty('PictProviderFlowGeometry'))
163
- {
164
- this.fable.addServiceType('PictProviderFlowGeometry', libPictProviderFlowGeometry);
165
- }
166
- if (!this.fable.servicesMap.hasOwnProperty('PictProviderFlowPanelChrome'))
167
- {
168
- this.fable.addServiceType('PictProviderFlowPanelChrome', libPictProviderFlowPanelChrome);
169
- }
170
- if (!this.fable.servicesMap.hasOwnProperty('PictProviderFlowCSS'))
171
- {
172
- this.fable.addServiceType('PictProviderFlowCSS', libPictProviderFlowCSS);
173
- }
174
- if (!this.fable.servicesMap.hasOwnProperty('PictProviderFlowIcons'))
175
- {
176
- this.fable.addServiceType('PictProviderFlowIcons', libPictProviderFlowIcons);
177
- }
178
- if (!this.fable.servicesMap.hasOwnProperty('PictProviderFlowConnectorShapes'))
179
- {
180
- this.fable.addServiceType('PictProviderFlowConnectorShapes', libPictProviderFlowConnectorShapes);
181
- }
182
- if (!this.fable.servicesMap.hasOwnProperty('PictProviderFlowTheme'))
183
- {
184
- this.fable.addServiceType('PictProviderFlowTheme', libPictProviderFlowTheme);
185
- }
186
- if (!this.fable.servicesMap.hasOwnProperty('PictProviderFlowNoise'))
187
- {
188
- this.fable.addServiceType('PictProviderFlowNoise', libPictProviderFlowNoise);
189
- }
190
- if (!this.fable.servicesMap.hasOwnProperty('PictProviderFlowNodeTypes'))
191
- {
192
- this.fable.addServiceType('PictProviderFlowNodeTypes', libPictProviderFlowNodeTypes);
193
- }
194
- if (!this.fable.servicesMap.hasOwnProperty('PictProviderFlowEventHandler'))
195
- {
196
- this.fable.addServiceType('PictProviderFlowEventHandler', libPictProviderFlowEventHandler);
197
- }
198
- if (!this.fable.servicesMap.hasOwnProperty('PictProviderFlowLayouts'))
199
- {
200
- this.fable.addServiceType('PictProviderFlowLayouts', libPictProviderFlowLayouts);
201
- }
202
- if (!this.fable.servicesMap.hasOwnProperty('PictViewFlowNode'))
203
- {
204
- this.fable.addServiceType('PictViewFlowNode', libPictViewFlowNode);
205
- }
206
- if (!this.fable.servicesMap.hasOwnProperty('PictViewFlowToolbar'))
207
- {
208
- this.fable.addServiceType('PictViewFlowToolbar', libPictViewFlowToolbar);
209
- }
210
- if (!this.fable.servicesMap.hasOwnProperty('PictViewFlowFloatingToolbar'))
211
- {
212
- this.fable.addServiceType('PictViewFlowFloatingToolbar', libPictViewFlowFloatingToolbar);
213
- }
214
- if (!this.fable.servicesMap.hasOwnProperty('PictViewFlowPropertiesPanel'))
215
- {
216
- this.fable.addServiceType('PictViewFlowPropertiesPanel', libPictViewFlowPropertiesPanel);
217
- }
218
- if (!this.fable.servicesMap.hasOwnProperty('PictFlowCardPropertiesPanel'))
219
- {
220
- this.fable.addServiceType('PictFlowCardPropertiesPanel', libPictFlowCardPropertiesPanel);
221
- }
222
- if (!this.fable.servicesMap.hasOwnProperty('PictFlowCardPropertiesPanel-Template'))
223
- {
224
- this.fable.addServiceType('PictFlowCardPropertiesPanel-Template', libPanelTemplate);
225
- }
226
- if (!this.fable.servicesMap.hasOwnProperty('PictFlowCardPropertiesPanel-Markdown'))
227
- {
228
- this.fable.addServiceType('PictFlowCardPropertiesPanel-Markdown', libPanelMarkdown);
229
- }
230
- if (!this.fable.servicesMap.hasOwnProperty('PictFlowCardPropertiesPanel-Form'))
231
- {
232
- this.fable.addServiceType('PictFlowCardPropertiesPanel-Form', libPanelForm);
233
- }
234
- if (!this.fable.servicesMap.hasOwnProperty('PictFlowCardPropertiesPanel-View'))
235
- {
236
- this.fable.addServiceType('PictFlowCardPropertiesPanel-View', libPanelView);
237
- }
129
+ // ---- Declarative service registry ----
130
+ // Each entry defines a service to register, instantiate, and guard.
131
+ // Optional flags:
132
+ // NoFlowView — instantiate without { FlowView: this }
133
+ // PostInit — method name to call on the instance after creation
134
+ // RegisterOnly — only register the type; do not bulk-instantiate
135
+ this._ServiceRegistry =
136
+ [
137
+ // Providers (stateless or config-only — no FlowView needed)
138
+ { ServiceType: 'PictProviderFlowSVGHelpers', Library: libPictProviderFlowSVGHelpers, Property: '_SVGHelperProvider', NoFlowView: true },
139
+ { ServiceType: 'PictProviderFlowGeometry', Library: libPictProviderFlowGeometry, Property: '_GeometryProvider', NoFlowView: true },
140
+ { ServiceType: 'PictProviderFlowNoise', Library: libPictProviderFlowNoise, Property: '_NoiseProvider', NoFlowView: true },
141
+
142
+ // Providers (need FlowView)
143
+ { ServiceType: 'PictProviderFlowTheme', Library: libPictProviderFlowTheme, Property: '_ThemeProvider' },
144
+ { ServiceType: 'PictProviderFlowCSS', Library: libPictProviderFlowCSS, Property: '_CSSProvider', PostInit: 'registerCSS' },
145
+ { ServiceType: 'PictProviderFlowIcons', Library: libPictProviderFlowIcons, Property: '_IconProvider', PostInit: 'registerIconTemplates' },
146
+ { ServiceType: 'PictProviderFlowConnectorShapes', Library: libPictProviderFlowConnectorShapes, Property: '_ConnectorShapesProvider' },
147
+ { ServiceType: 'PictProviderFlowPanelChrome', Library: libPictProviderFlowPanelChrome, Property: '_PanelChromeProvider' },
148
+ { ServiceType: 'PictProviderFlowNodeTypes', Library: libPictProviderFlowNodeTypes, Property: '_NodeTypeProvider', ExtraOptions: () => ({ AdditionalNodeTypes: this.options.NodeTypes }) },
149
+ { ServiceType: 'PictProviderFlowEventHandler', Library: libPictProviderFlowEventHandler, Property: '_EventHandlerProvider' },
150
+ { ServiceType: 'PictProviderFlowLayouts', Library: libPictProviderFlowLayouts, Property: '_LayoutProvider', PostInit: 'loadPersistedLayouts' },
151
+
152
+ // Services
153
+ { ServiceType: 'PictServiceFlowPathGenerator', Library: libPictServiceFlowPathGenerator, Property: '_PathGenerator' },
154
+ { ServiceType: 'PictServiceFlowDataManager', Library: libPictServiceFlowDataManager, Property: '_DataManager' },
155
+ { ServiceType: 'PictServiceFlowConnectionHandleManager', Library: libPictServiceFlowConnectionHandleManager, Property: '_ConnectionHandleManager' },
156
+ { ServiceType: 'PictServiceFlowRenderManager', Library: libPictServiceFlowRenderManager, Property: '_RenderManager' },
157
+ { ServiceType: 'PictServiceFlowPortRenderer', Library: libPictServiceFlowPortRenderer, Property: '_PortRenderer' },
158
+ { ServiceType: 'PictServiceFlowInteractionManager', Library: libPictServiceFlowInteractionManager, Property: '_InteractionManager' },
159
+ { ServiceType: 'PictServiceFlowConnectionRenderer', Library: libPictServiceFlowConnectionRenderer, Property: '_ConnectionRenderer' },
160
+ { ServiceType: 'PictServiceFlowTether', Library: libPictServiceFlowTether, Property: '_TetherService' },
161
+ { ServiceType: 'PictServiceFlowLayout', Library: libPictServiceFlowLayout, Property: '_LayoutService' },
162
+ { ServiceType: 'PictServiceFlowViewportManager', Library: libPictServiceFlowViewportManager, Property: '_ViewportManager' },
163
+ { ServiceType: 'PictServiceFlowSelectionManager', Library: libPictServiceFlowSelectionManager, Property: '_SelectionManager' },
164
+ { ServiceType: 'PictServiceFlowPanelManager', Library: libPictServiceFlowPanelManager, Property: '_PanelManager' },
165
+
166
+ // View types (register only — instantiated with custom config in onAfterInitialRender)
167
+ { ServiceType: 'PictViewFlowNode', Library: libPictViewFlowNode, RegisterOnly: true },
168
+ { ServiceType: 'PictViewFlowToolbar', Library: libPictViewFlowToolbar, RegisterOnly: true },
169
+ { ServiceType: 'PictViewFlowFloatingToolbar', Library: libPictViewFlowFloatingToolbar, RegisterOnly: true },
170
+ { ServiceType: 'PictViewFlowPropertiesPanel', Library: libPictViewFlowPropertiesPanel, RegisterOnly: true },
171
+
172
+ // Panel types (register only)
173
+ { ServiceType: 'PictFlowCardPropertiesPanel', Library: libPictFlowCardPropertiesPanel, RegisterOnly: true },
174
+ { ServiceType: 'PictFlowCardPropertiesPanel-Template', Library: libPanelTemplate, RegisterOnly: true },
175
+ { ServiceType: 'PictFlowCardPropertiesPanel-Markdown', Library: libPanelMarkdown, RegisterOnly: true },
176
+ { ServiceType: 'PictFlowCardPropertiesPanel-Form', Library: libPanelForm, RegisterOnly: true },
177
+ { ServiceType: 'PictFlowCardPropertiesPanel-View', Library: libPanelView, RegisterOnly: true }
178
+ ];
179
+
180
+ this._registerServiceTypes();
238
181
 
239
182
  // Internal state
240
183
  this._FlowData = {
@@ -259,6 +202,11 @@ class PictViewFlow extends libPictView
259
202
  this._TethersLayer = null;
260
203
  this._PanelsLayer = null;
261
204
 
205
+ this._DataManager = null;
206
+ this._ConnectionHandleManager = null;
207
+ this._RenderManager = null;
208
+ this._PortRenderer = null;
209
+
262
210
  this._InteractionManager = null;
263
211
  this._ConnectionRenderer = null;
264
212
  this._TetherService = null;
@@ -285,6 +233,41 @@ class PictViewFlow extends libPictView
285
233
  this.initialRenderComplete = false;
286
234
  }
287
235
 
236
+ _registerServiceTypes()
237
+ {
238
+ for (let i = 0; i < this._ServiceRegistry.length; i++)
239
+ {
240
+ let tmpEntry = this._ServiceRegistry[i];
241
+ if (!this.fable.servicesMap.hasOwnProperty(tmpEntry.ServiceType))
242
+ {
243
+ this.fable.addServiceType(tmpEntry.ServiceType, tmpEntry.Library);
244
+ }
245
+ }
246
+ }
247
+
248
+ _instantiateServices()
249
+ {
250
+ for (let i = 0; i < this._ServiceRegistry.length; i++)
251
+ {
252
+ let tmpEntry = this._ServiceRegistry[i];
253
+ if (tmpEntry.RegisterOnly) continue;
254
+ if (this[tmpEntry.Property]) continue;
255
+
256
+ let tmpOptions = tmpEntry.NoFlowView ? {} : { FlowView: this };
257
+ if (typeof tmpEntry.ExtraOptions === 'function')
258
+ {
259
+ Object.assign(tmpOptions, tmpEntry.ExtraOptions());
260
+ }
261
+
262
+ this[tmpEntry.Property] = this.fable.instantiateServiceProviderWithoutRegistration(tmpEntry.ServiceType, tmpOptions);
263
+
264
+ if (tmpEntry.PostInit && typeof this[tmpEntry.Property][tmpEntry.PostInit] === 'function')
265
+ {
266
+ this[tmpEntry.Property][tmpEntry.PostInit]();
267
+ }
268
+ }
269
+ }
270
+
288
271
  get flowData()
289
272
  {
290
273
  return this._FlowData;
@@ -324,7 +307,7 @@ class PictViewFlow extends libPictView
324
307
  {
325
308
  super.onBeforeInitialize();
326
309
 
327
- // Instantiate theme and noise providers (before CSS so theme state is available)
310
+ // Theme + Noise must be created first (CSS PostInit depends on theme state)
328
311
  this._ThemeProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowTheme', { FlowView: this });
329
312
  this._NoiseProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowNoise');
330
313
 
@@ -338,37 +321,8 @@ class PictViewFlow extends libPictView
338
321
  this._ThemeProvider.setNoiseLevel(this.options.NoiseLevel);
339
322
  }
340
323
 
341
- // Instantiate and register the centralized CSS provider
342
- this._CSSProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowCSS', { FlowView: this });
343
- this._CSSProvider.registerCSS();
344
-
345
- // Instantiate the SVG icon provider
346
- this._IconProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowIcons', { FlowView: this });
347
- this._IconProvider.registerIconTemplates();
348
-
349
- // Instantiate the connector shapes provider
350
- this._ConnectorShapesProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowConnectorShapes', { FlowView: this });
351
-
352
- // Instantiate shared utility providers first (used by services below)
353
- this._SVGHelperProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowSVGHelpers');
354
- this._GeometryProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowGeometry');
355
- this._PathGenerator = this.fable.instantiateServiceProviderWithoutRegistration('PictServiceFlowPathGenerator', { FlowView: this });
356
- this._PanelChromeProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowPanelChrome', { FlowView: this });
357
-
358
- // Instantiate services
359
- this._InteractionManager = this.fable.instantiateServiceProviderWithoutRegistration('PictServiceFlowInteractionManager', { FlowView: this });
360
- this._ConnectionRenderer = this.fable.instantiateServiceProviderWithoutRegistration('PictServiceFlowConnectionRenderer', { FlowView: this });
361
- this._TetherService = this.fable.instantiateServiceProviderWithoutRegistration('PictServiceFlowTether', { FlowView: this });
362
- this._LayoutService = this.fable.instantiateServiceProviderWithoutRegistration('PictServiceFlowLayout', { FlowView: this });
363
- this._ViewportManager = this.fable.instantiateServiceProviderWithoutRegistration('PictServiceFlowViewportManager', { FlowView: this });
364
- this._SelectionManager = this.fable.instantiateServiceProviderWithoutRegistration('PictServiceFlowSelectionManager', { FlowView: this });
365
- this._PanelManager = this.fable.instantiateServiceProviderWithoutRegistration('PictServiceFlowPanelManager', { FlowView: this });
366
-
367
- // Instantiate providers, passing any additional node types from view options
368
- this._NodeTypeProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowNodeTypes', { FlowView: this, AdditionalNodeTypes: this.options.NodeTypes });
369
- this._EventHandlerProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowEventHandler', { FlowView: this });
370
- this._LayoutProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowLayouts', { FlowView: this });
371
- this._LayoutProvider.loadPersistedLayouts();
324
+ // Instantiate all remaining services (skips Theme + Noise since already set)
325
+ this._instantiateServices();
372
326
 
373
327
  return super.onBeforeInitialize();
374
328
  }
@@ -426,96 +380,8 @@ class PictViewFlow extends libPictView
426
380
  this._PanelsLayer = tmpPanelsElements[0];
427
381
  }
428
382
 
429
- // Initialize theme and noise providers (fallback if not already created)
430
- if (!this._ThemeProvider)
431
- {
432
- this._ThemeProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowTheme', { FlowView: this });
433
- }
434
- if (!this._NoiseProvider)
435
- {
436
- this._NoiseProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowNoise');
437
- }
438
-
439
- // Initialize CSS provider (fallback if not already created)
440
- if (!this._CSSProvider)
441
- {
442
- this._CSSProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowCSS', { FlowView: this });
443
- this._CSSProvider.registerCSS();
444
- }
445
-
446
- // Initialize icon provider (fallback if not already created)
447
- if (!this._IconProvider)
448
- {
449
- this._IconProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowIcons', { FlowView: this });
450
- this._IconProvider.registerIconTemplates();
451
- }
452
-
453
- // Initialize connector shapes provider (fallback if not already created)
454
- if (!this._ConnectorShapesProvider)
455
- {
456
- this._ConnectorShapesProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowConnectorShapes', { FlowView: this });
457
- }
458
-
459
- // Initialize shared utility providers (used by services below)
460
- if (!this._SVGHelperProvider)
461
- {
462
- this._SVGHelperProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowSVGHelpers');
463
- }
464
- if (!this._GeometryProvider)
465
- {
466
- this._GeometryProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowGeometry');
467
- }
468
- if (!this._PathGenerator)
469
- {
470
- this._PathGenerator = this.fable.instantiateServiceProviderWithoutRegistration('PictServiceFlowPathGenerator', { FlowView: this });
471
- }
472
- if (!this._PanelChromeProvider)
473
- {
474
- this._PanelChromeProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowPanelChrome', { FlowView: this });
475
- }
476
-
477
- // Initialize services with references
478
- if (!this._InteractionManager)
479
- {
480
- this._InteractionManager = this.fable.instantiateServiceProviderWithoutRegistration('PictServiceFlowInteractionManager', { FlowView: this });
481
- }
482
- if (!this._ConnectionRenderer)
483
- {
484
- this._ConnectionRenderer = this.fable.instantiateServiceProviderWithoutRegistration('PictServiceFlowConnectionRenderer', { FlowView: this });
485
- }
486
- if (!this._TetherService)
487
- {
488
- this._TetherService = this.fable.instantiateServiceProviderWithoutRegistration('PictServiceFlowTether', { FlowView: this });
489
- }
490
- if (!this._LayoutService)
491
- {
492
- this._LayoutService = this.fable.instantiateServiceProviderWithoutRegistration('PictServiceFlowLayout', { FlowView: this });
493
- }
494
- if (!this._ViewportManager)
495
- {
496
- this._ViewportManager = this.fable.instantiateServiceProviderWithoutRegistration('PictServiceFlowViewportManager', { FlowView: this });
497
- }
498
- if (!this._SelectionManager)
499
- {
500
- this._SelectionManager = this.fable.instantiateServiceProviderWithoutRegistration('PictServiceFlowSelectionManager', { FlowView: this });
501
- }
502
- if (!this._PanelManager)
503
- {
504
- this._PanelManager = this.fable.instantiateServiceProviderWithoutRegistration('PictServiceFlowPanelManager', { FlowView: this });
505
- }
506
- if (!this._NodeTypeProvider)
507
- {
508
- this._NodeTypeProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowNodeTypes', { FlowView: this, AdditionalNodeTypes: this.options.NodeTypes });
509
- }
510
- if (!this._EventHandlerProvider)
511
- {
512
- this._EventHandlerProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowEventHandler', { FlowView: this });
513
- }
514
- if (!this._LayoutProvider)
515
- {
516
- this._LayoutProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowLayouts', { FlowView: this });
517
- this._LayoutProvider.loadPersistedLayouts();
518
- }
383
+ // Ensure all services are initialized (fallback if onBeforeInitialize was skipped)
384
+ this._instantiateServices();
519
385
 
520
386
  // Inject marker defs via the connector shapes provider
521
387
  // Note: insertAdjacentHTML does not work on SVG elements (wrong namespace),
@@ -590,296 +456,16 @@ class PictViewFlow extends libPictView
590
456
  this.renderFlow();
591
457
  }
592
458
 
593
- /**
594
- * Marshal data from AppData into the flow view
595
- */
596
- marshalToView()
597
- {
598
- if (this.options.FlowDataAddress)
599
- {
600
- const tmpAddressSpace =
601
- {
602
- Fable: this.fable,
603
- Pict: this.pict || this.fable,
604
- AppData: this.pict ? this.pict.AppData : this.fable.AppData,
605
- Bundle: this.Bundle,
606
- Options: this.options
607
- };
608
- let tmpData = this.fable.manifest.getValueByHash(tmpAddressSpace, this.options.FlowDataAddress);
609
- if (typeof tmpData === 'object' && tmpData !== null)
610
- {
611
- this.setFlowData(tmpData);
612
- }
613
- }
614
- }
615
-
616
- /**
617
- * Marshal data from the flow view back to AppData
618
- */
619
- marshalFromView()
620
- {
621
- if (this.options.FlowDataAddress)
622
- {
623
- const tmpAddressSpace =
624
- {
625
- Fable: this.fable,
626
- Pict: this.pict || this.fable,
627
- AppData: this.pict ? this.pict.AppData : this.fable.AppData,
628
- Bundle: this.Bundle,
629
- Options: this.options
630
- };
631
- this.fable.manifest.setValueByHash(tmpAddressSpace, this.options.FlowDataAddress, JSON.parse(JSON.stringify(this._FlowData)));
632
- }
633
- }
634
-
635
- /**
636
- * Get the complete flow data object
637
- * @returns {Object} The flow data including nodes, connections, and view state
638
- */
639
- getFlowData()
640
- {
641
- return JSON.parse(JSON.stringify(this._FlowData));
642
- }
643
-
644
- /**
645
- * Set the complete flow data object and re-render
646
- * @param {Object} pFlowData - The flow data to set
647
- */
648
- setFlowData(pFlowData)
649
- {
650
- if (typeof pFlowData !== 'object' || pFlowData === null)
651
- {
652
- this.log.warn('PictSectionFlow setFlowData received invalid data');
653
- return;
654
- }
655
-
656
- this._FlowData = {
657
- Nodes: Array.isArray(pFlowData.Nodes) ? pFlowData.Nodes : [],
658
- Connections: Array.isArray(pFlowData.Connections) ? pFlowData.Connections : [],
659
- OpenPanels: Array.isArray(pFlowData.OpenPanels) ? pFlowData.OpenPanels : [],
660
- SavedLayouts: Array.isArray(pFlowData.SavedLayouts) ? pFlowData.SavedLayouts : [],
661
- ViewState: Object.assign(
662
- { PanX: 0, PanY: 0, Zoom: 1, SelectedNodeHash: null, SelectedConnectionHash: null, SelectedTetherHash: null },
663
- pFlowData.ViewState || {}
664
- )
665
- };
666
-
667
- // Merge any browser-persisted layouts into the newly loaded data
668
- if (this._LayoutProvider)
669
- {
670
- this._LayoutProvider.loadPersistedLayouts();
671
- }
672
-
673
- if (this.initialRenderComplete)
674
- {
675
- this.renderFlow();
676
- }
677
- }
678
-
679
- /**
680
- * Add a new node to the flow
681
- * @param {string} pType - The node type hash
682
- * @param {number} pX - X position
683
- * @param {number} pY - Y position
684
- * @param {string} [pTitle] - Optional title
685
- * @param {Object} [pData] - Optional additional data
686
- * @returns {Object} The created node
687
- */
688
- addNode(pType, pX, pY, pTitle, pData)
689
- {
690
- let tmpType = pType || this.options.DefaultNodeType;
691
- let tmpNodeTypeConfig = this._NodeTypeProvider.getNodeType(tmpType);
692
-
693
- let tmpNodeHash = `node-${this.fable.getUUID()}`;
694
- let tmpNode =
695
- {
696
- Hash: tmpNodeHash,
697
- Type: tmpType,
698
- X: pX || 100,
699
- Y: pY || 100,
700
- Width: (tmpNodeTypeConfig && tmpNodeTypeConfig.DefaultWidth) || this.options.DefaultNodeWidth,
701
- Height: (tmpNodeTypeConfig && tmpNodeTypeConfig.DefaultHeight) || this.options.DefaultNodeHeight,
702
- Title: pTitle || (tmpNodeTypeConfig && tmpNodeTypeConfig.Label) || 'New Node',
703
- Ports: (tmpNodeTypeConfig && tmpNodeTypeConfig.DefaultPorts)
704
- ? JSON.parse(JSON.stringify(tmpNodeTypeConfig.DefaultPorts))
705
- : [
706
- { Hash: `port-in-${this.fable.getUUID()}`, Direction: 'input', Side: 'left', Label: 'In' },
707
- { Hash: `port-out-${this.fable.getUUID()}`, Direction: 'output', Side: 'right', Label: 'Out' }
708
- ],
709
- Data: pData || {}
710
- };
711
-
712
- // Ensure each port has a unique hash
713
- for (let i = 0; i < tmpNode.Ports.length; i++)
714
- {
715
- if (!tmpNode.Ports[i].Hash)
716
- {
717
- tmpNode.Ports[i].Hash = `port-${tmpNode.Ports[i].Direction}-${this.fable.getUUID()}`;
718
- }
719
- }
720
-
721
- this._FlowData.Nodes.push(tmpNode);
722
- this.renderFlow();
723
- this.marshalFromView();
724
-
725
- if (this._EventHandlerProvider)
726
- {
727
- this._EventHandlerProvider.fireEvent('onNodeAdded', tmpNode);
728
- this._EventHandlerProvider.fireEvent('onFlowChanged', this._FlowData);
729
- }
730
-
731
- return tmpNode;
732
- }
733
-
734
- /**
735
- * Remove a node and all its connections
736
- * @param {string} pNodeHash - The hash of the node to remove
737
- * @returns {boolean} Whether the node was removed
738
- */
739
- removeNode(pNodeHash)
740
- {
741
- let tmpNodeIndex = this._FlowData.Nodes.findIndex((pNode) => pNode.Hash === pNodeHash);
742
- if (tmpNodeIndex < 0)
743
- {
744
- this.log.warn(`PictSectionFlow removeNode: node ${pNodeHash} not found`);
745
- return false;
746
- }
747
-
748
- let tmpRemovedNode = this._FlowData.Nodes.splice(tmpNodeIndex, 1)[0];
749
-
750
- // Remove all connections involving this node
751
- this._FlowData.Connections = this._FlowData.Connections.filter((pConnection) =>
752
- {
753
- return pConnection.SourceNodeHash !== pNodeHash && pConnection.TargetNodeHash !== pNodeHash;
754
- });
755
-
756
- // Close any open panels for this node
757
- this.closePanelForNode(pNodeHash);
459
+ // ---- Data Manager Delegations ----
758
460
 
759
- // Clear selection if this node was selected
760
- if (this._FlowData.ViewState.SelectedNodeHash === pNodeHash)
761
- {
762
- this._FlowData.ViewState.SelectedNodeHash = null;
763
- }
764
-
765
- this.renderFlow();
766
- this.marshalFromView();
767
-
768
- if (this._EventHandlerProvider)
769
- {
770
- this._EventHandlerProvider.fireEvent('onNodeRemoved', tmpRemovedNode);
771
- this._EventHandlerProvider.fireEvent('onFlowChanged', this._FlowData);
772
- }
773
-
774
- return true;
775
- }
776
-
777
- /**
778
- * Add a connection between two ports
779
- * @param {string} pSourceNodeHash
780
- * @param {string} pSourcePortHash
781
- * @param {string} pTargetNodeHash
782
- * @param {string} pTargetPortHash
783
- * @param {Object} [pData] - Optional additional data
784
- * @returns {Object|false} The created connection, or false if invalid
785
- */
786
- addConnection(pSourceNodeHash, pSourcePortHash, pTargetNodeHash, pTargetPortHash, pData)
787
- {
788
- // Validate that both nodes and ports exist
789
- let tmpSourceNode = this._FlowData.Nodes.find((pNode) => pNode.Hash === pSourceNodeHash);
790
- let tmpTargetNode = this._FlowData.Nodes.find((pNode) => pNode.Hash === pTargetNodeHash);
791
-
792
- if (!tmpSourceNode || !tmpTargetNode)
793
- {
794
- this.log.warn('PictSectionFlow addConnection: source or target node not found');
795
- return false;
796
- }
797
-
798
- let tmpSourcePort = tmpSourceNode.Ports.find((pPort) => pPort.Hash === pSourcePortHash);
799
- let tmpTargetPort = tmpTargetNode.Ports.find((pPort) => pPort.Hash === pTargetPortHash);
800
-
801
- if (!tmpSourcePort || !tmpTargetPort)
802
- {
803
- this.log.warn('PictSectionFlow addConnection: source or target port not found');
804
- return false;
805
- }
806
-
807
- // Prevent self-connections
808
- if (pSourceNodeHash === pTargetNodeHash)
809
- {
810
- this.log.warn('PictSectionFlow addConnection: cannot connect a node to itself');
811
- return false;
812
- }
813
-
814
- // Check for duplicate connections
815
- let tmpDuplicate = this._FlowData.Connections.find((pConn) =>
816
- {
817
- return pConn.SourceNodeHash === pSourceNodeHash
818
- && pConn.SourcePortHash === pSourcePortHash
819
- && pConn.TargetNodeHash === pTargetNodeHash
820
- && pConn.TargetPortHash === pTargetPortHash;
821
- });
822
- if (tmpDuplicate)
823
- {
824
- this.log.warn('PictSectionFlow addConnection: duplicate connection');
825
- return false;
826
- }
827
-
828
- let tmpConnection =
829
- {
830
- Hash: `conn-${this.fable.getUUID()}`,
831
- SourceNodeHash: pSourceNodeHash,
832
- SourcePortHash: pSourcePortHash,
833
- TargetNodeHash: pTargetNodeHash,
834
- TargetPortHash: pTargetPortHash,
835
- Data: pData || {}
836
- };
837
-
838
- this._FlowData.Connections.push(tmpConnection);
839
- this.renderFlow();
840
- this.marshalFromView();
841
-
842
- if (this._EventHandlerProvider)
843
- {
844
- this._EventHandlerProvider.fireEvent('onConnectionCreated', tmpConnection);
845
- this._EventHandlerProvider.fireEvent('onFlowChanged', this._FlowData);
846
- }
847
-
848
- return tmpConnection;
849
- }
850
-
851
- /**
852
- * Remove a connection
853
- * @param {string} pConnectionHash - The hash of the connection to remove
854
- * @returns {boolean} Whether the connection was removed
855
- */
856
- removeConnection(pConnectionHash)
857
- {
858
- let tmpConnectionIndex = this._FlowData.Connections.findIndex((pConn) => pConn.Hash === pConnectionHash);
859
- if (tmpConnectionIndex < 0)
860
- {
861
- this.log.warn(`PictSectionFlow removeConnection: connection ${pConnectionHash} not found`);
862
- return false;
863
- }
864
-
865
- let tmpRemovedConnection = this._FlowData.Connections.splice(tmpConnectionIndex, 1)[0];
866
-
867
- if (this._FlowData.ViewState.SelectedConnectionHash === pConnectionHash)
868
- {
869
- this._FlowData.ViewState.SelectedConnectionHash = null;
870
- }
871
-
872
- this.renderFlow();
873
- this.marshalFromView();
874
-
875
- if (this._EventHandlerProvider)
876
- {
877
- this._EventHandlerProvider.fireEvent('onConnectionRemoved', tmpRemovedConnection);
878
- this._EventHandlerProvider.fireEvent('onFlowChanged', this._FlowData);
879
- }
880
-
881
- return true;
882
- }
461
+ marshalToView() { return this._DataManager.marshalToView(); }
462
+ marshalFromView() { return this._DataManager.marshalFromView(); }
463
+ getFlowData() { return this._DataManager.getFlowData(); }
464
+ setFlowData(pFlowData) { return this._DataManager.setFlowData(pFlowData); }
465
+ addNode(pType, pX, pY, pTitle, pData) { return this._DataManager.addNode(pType, pX, pY, pTitle, pData); }
466
+ removeNode(pNodeHash) { return this._DataManager.removeNode(pNodeHash); }
467
+ addConnection(pSourceNodeHash, pSourcePortHash, pTargetNodeHash, pTargetPortHash, pData) { return this._DataManager.addConnection(pSourceNodeHash, pSourcePortHash, pTargetNodeHash, pTargetPortHash, pData); }
468
+ removeConnection(pConnectionHash) { return this._DataManager.removeConnection(pConnectionHash); }
883
469
 
884
470
  /**
885
471
  * Select a node
@@ -1064,34 +650,7 @@ class PictViewFlow extends libPictView
1064
650
  return 'default';
1065
651
  }
1066
652
 
1067
- /**
1068
- * Re-inject SVG marker definitions (arrowheads).
1069
- * Called after a theme switch to update arrowhead colors.
1070
- */
1071
- _reinjectMarkerDefs()
1072
- {
1073
- if (!this._ConnectorShapesProvider || !this._SVGElement) return;
1074
-
1075
- let tmpViewIdentifier = this.options.ViewIdentifier;
1076
- let tmpDefs = this._SVGElement.querySelector('defs');
1077
- if (!tmpDefs) return;
1078
-
1079
- // Remove existing marker elements
1080
- let tmpExistingMarkers = tmpDefs.querySelectorAll('marker');
1081
- for (let i = 0; i < tmpExistingMarkers.length; i++)
1082
- {
1083
- tmpExistingMarkers[i].remove();
1084
- }
1085
-
1086
- // Re-generate and inject
1087
- let tmpMarkerMarkup = this._ConnectorShapesProvider.generateMarkerDefs(tmpViewIdentifier);
1088
- let tmpTempSVG = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
1089
- tmpTempSVG.innerHTML = tmpMarkerMarkup;
1090
- while (tmpTempSVG.firstChild)
1091
- {
1092
- tmpDefs.appendChild(tmpTempSVG.firstChild);
1093
- }
1094
- }
653
+ _reinjectMarkerDefs() { return this._RenderManager.reinjectMarkerDefs(); }
1095
654
 
1096
655
  /**
1097
656
  * Get a node by hash
@@ -1122,74 +681,19 @@ class PictViewFlow extends libPictView
1122
681
  return this._SelectionManager.selectTether(pPanelHash);
1123
682
  }
1124
683
 
1125
- /**
1126
- * Update a connection handle position during drag (for real-time feedback).
1127
- * @param {string} pConnectionHash
1128
- * @param {string} pHandleType - 'bezier-midpoint', 'ortho-corner1', 'ortho-corner2', 'ortho-midpoint'
1129
- * @param {number} pX
1130
- * @param {number} pY
1131
- */
1132
- updateConnectionHandle(pConnectionHash, pHandleType, pX, pY)
1133
- {
1134
- let tmpConnection = this.getConnection(pConnectionHash);
1135
- if (!tmpConnection) return;
1136
-
1137
- if (!tmpConnection.Data) tmpConnection.Data = {};
1138
- tmpConnection.Data.HandleCustomized = true;
1139
-
1140
- switch (pHandleType)
1141
- {
1142
- case 'bezier-midpoint':
1143
- tmpConnection.Data.BezierHandleX = pX;
1144
- tmpConnection.Data.BezierHandleY = pY;
1145
- break;
1146
-
1147
- case 'ortho-corner1':
1148
- tmpConnection.Data.OrthoCorner1X = pX;
1149
- tmpConnection.Data.OrthoCorner1Y = pY;
1150
- break;
1151
-
1152
- case 'ortho-corner2':
1153
- tmpConnection.Data.OrthoCorner2X = pX;
1154
- tmpConnection.Data.OrthoCorner2Y = pY;
1155
- break;
1156
-
1157
- case 'ortho-midpoint':
1158
- {
1159
- // Midpoint drag shifts the corridor offset
1160
- let tmpSourcePos = this.getPortPosition(tmpConnection.SourceNodeHash, tmpConnection.SourcePortHash);
1161
- let tmpTargetPos = this.getPortPosition(tmpConnection.TargetNodeHash, tmpConnection.TargetPortHash);
1162
- if (tmpSourcePos && tmpTargetPos)
1163
- {
1164
- let tmpGeom = this._ConnectionRenderer._computeDirectionalGeometry(tmpSourcePos, tmpTargetPos);
1165
- let tmpStartDir = tmpGeom.startDir;
1166
-
1167
- // Compute offset along the corridor axis
1168
- if (Math.abs(tmpStartDir.dx) > Math.abs(tmpStartDir.dy))
1169
- {
1170
- // Horizontal departure — corridor is vertical, shift is along X
1171
- let tmpAutoMidX = (tmpGeom.departX + tmpGeom.approachX) / 2;
1172
- tmpConnection.Data.OrthoMidOffset = pX - tmpAutoMidX;
1173
- }
1174
- else
1175
- {
1176
- // Vertical departure — corridor is horizontal, shift is along Y
1177
- let tmpAutoMidY = (tmpGeom.departY + tmpGeom.approachY) / 2;
1178
- tmpConnection.Data.OrthoMidOffset = pY - tmpAutoMidY;
1179
- }
1180
- }
1181
- break;
1182
- }
1183
- }
684
+ // ---- Connection Handle Manager Delegations ----
1184
685
 
1185
- this._renderSingleConnection(pConnectionHash);
1186
- }
686
+ updateConnectionHandle(pConnectionHash, pHandleType, pX, pY) { return this._ConnectionHandleManager.updateConnectionHandle(pConnectionHash, pHandleType, pX, pY); }
687
+ addConnectionHandle(pConnectionHash, pX, pY) { return this._ConnectionHandleManager.addConnectionHandle(pConnectionHash, pX, pY); }
688
+ removeConnectionHandle(pConnectionHash, pIndex) { return this._ConnectionHandleManager.removeConnectionHandle(pConnectionHash, pIndex); }
689
+ _resetHandlesForNode(pNodeHash) { return this._ConnectionHandleManager.resetHandlesForNode(pNodeHash); }
690
+ _resetHandlesForPanel(pPanelHash) { return this._ConnectionHandleManager.resetHandlesForPanel(pPanelHash); }
1187
691
 
1188
692
  /**
1189
693
  * Update a tether handle position during drag (for real-time feedback).
1190
694
  * Delegates state update to the TetherService.
1191
695
  * @param {string} pPanelHash
1192
- * @param {string} pHandleType - 'bezier-midpoint', 'ortho-corner1', 'ortho-corner2', 'ortho-midpoint'
696
+ * @param {string} pHandleType
1193
697
  * @param {number} pX
1194
698
  * @param {number} pY
1195
699
  */
@@ -1206,103 +710,6 @@ class PictViewFlow extends libPictView
1206
710
  this._renderSingleTether(pPanelHash);
1207
711
  }
1208
712
 
1209
- /**
1210
- * Re-render a single connection (remove and re-add) for smooth drag performance.
1211
- * @param {string} pConnectionHash
1212
- */
1213
- _renderSingleConnection(pConnectionHash)
1214
- {
1215
- if (!this._ConnectionsLayer) return;
1216
-
1217
- // Remove existing elements for this connection
1218
- let tmpExisting = this._ConnectionsLayer.querySelectorAll(`[data-connection-hash="${pConnectionHash}"]`);
1219
- for (let i = 0; i < tmpExisting.length; i++)
1220
- {
1221
- tmpExisting[i].remove();
1222
- }
1223
-
1224
- let tmpConnection = this.getConnection(pConnectionHash);
1225
- if (!tmpConnection) return;
1226
-
1227
- let tmpIsSelected = (this._FlowData.ViewState.SelectedConnectionHash === pConnectionHash);
1228
- this._ConnectionRenderer.renderConnection(tmpConnection, this._ConnectionsLayer, tmpIsSelected);
1229
- }
1230
-
1231
- /**
1232
- * Re-render a single tether (remove and re-add) for smooth drag performance.
1233
- * @param {string} pPanelHash
1234
- */
1235
- _renderSingleTether(pPanelHash)
1236
- {
1237
- if (!this._TethersLayer || !this._TetherService) return;
1238
-
1239
- // Remove existing tether elements for this panel
1240
- let tmpExisting = this._TethersLayer.querySelectorAll(`[data-panel-hash="${pPanelHash}"]`);
1241
- for (let i = 0; i < tmpExisting.length; i++)
1242
- {
1243
- tmpExisting[i].remove();
1244
- }
1245
-
1246
- let tmpPanel = this._FlowData.OpenPanels.find((pPanel) => pPanel.Hash === pPanelHash);
1247
- if (!tmpPanel) return;
1248
-
1249
- let tmpNodeData = this.getNode(tmpPanel.NodeHash);
1250
- if (!tmpNodeData) return;
1251
-
1252
- let tmpIsSelected = (this._FlowData.ViewState.SelectedTetherHash === pPanelHash);
1253
- this._TetherService.renderTether(tmpPanel, tmpNodeData, this._TethersLayer, tmpIsSelected, this.options.ViewIdentifier);
1254
- }
1255
-
1256
- /**
1257
- * Reset handle positions for all connections/tethers involving a node.
1258
- * Called when a node moves. Preserves LineMode but resets handle coordinates to auto.
1259
- * @param {string} pNodeHash
1260
- */
1261
- _resetHandlesForNode(pNodeHash)
1262
- {
1263
- // Reset connection handles
1264
- for (let i = 0; i < this._FlowData.Connections.length; i++)
1265
- {
1266
- let tmpConn = this._FlowData.Connections[i];
1267
- if (tmpConn.SourceNodeHash === pNodeHash || tmpConn.TargetNodeHash === pNodeHash)
1268
- {
1269
- if (tmpConn.Data && tmpConn.Data.HandleCustomized)
1270
- {
1271
- tmpConn.Data.HandleCustomized = false;
1272
- tmpConn.Data.BezierHandleX = null;
1273
- tmpConn.Data.BezierHandleY = null;
1274
- tmpConn.Data.OrthoCorner1X = null;
1275
- tmpConn.Data.OrthoCorner1Y = null;
1276
- tmpConn.Data.OrthoCorner2X = null;
1277
- tmpConn.Data.OrthoCorner2Y = null;
1278
- tmpConn.Data.OrthoMidOffset = 0;
1279
- }
1280
- }
1281
- }
1282
-
1283
- // Reset tether handles for panels attached to this node
1284
- if (this._TetherService)
1285
- {
1286
- this._TetherService.resetHandlesForNode(this._FlowData.OpenPanels, pNodeHash);
1287
- }
1288
- }
1289
-
1290
- /**
1291
- * Reset tether handle positions for a specific panel.
1292
- * Called when a panel is dragged.
1293
- * @param {string} pPanelHash
1294
- */
1295
- _resetHandlesForPanel(pPanelHash)
1296
- {
1297
- let tmpPanel = this._FlowData.OpenPanels.find((pPanel) => pPanel.Hash === pPanelHash);
1298
- if (!tmpPanel) return;
1299
-
1300
- if (this._TetherService)
1301
- {
1302
- this._TetherService.resetHandlePositions(tmpPanel);
1303
- }
1304
- }
1305
-
1306
713
  /**
1307
714
  * Get a port's absolute position in SVG coordinates.
1308
715
  *
@@ -1341,7 +748,10 @@ class PictViewFlow extends libPictView
1341
748
  }
1342
749
  }
1343
750
 
1344
- let tmpLocal = this._GeometryProvider.getPortLocalPosition(tmpPort.Side, tmpPortIndex, tmpPortCount, tmpNode.Width, tmpHeight, tmpTitleBarHeight);
751
+ // Build port counts map for adaptive zone sizing
752
+ let tmpPortCountsBySide = this._GeometryProvider.buildPortCountsBySide(tmpNode.Ports);
753
+
754
+ let tmpLocal = this._GeometryProvider.getPortLocalPosition(tmpPort.Side, tmpPortIndex, tmpPortCount, tmpNode.Width, tmpHeight, tmpTitleBarHeight, tmpPortCountsBySide);
1345
755
 
1346
756
  return { x: tmpNode.X + tmpLocal.x, y: tmpNode.Y + tmpLocal.y, side: tmpPort.Side || 'right' };
1347
757
  }
@@ -1357,176 +767,14 @@ class PictViewFlow extends libPictView
1357
767
  return this._ViewportManager.screenToSVGCoords(pScreenX, pScreenY);
1358
768
  }
1359
769
 
1360
- /**
1361
- * Render the complete flow diagram
1362
- */
1363
- renderFlow()
1364
- {
1365
- if (!this._NodesLayer || !this._ConnectionsLayer) return;
1366
-
1367
- // Clear existing SVG content
1368
- while (this._NodesLayer.firstChild)
1369
- {
1370
- this._NodesLayer.removeChild(this._NodesLayer.firstChild);
1371
- }
1372
- while (this._ConnectionsLayer.firstChild)
1373
- {
1374
- this._ConnectionsLayer.removeChild(this._ConnectionsLayer.firstChild);
1375
- }
1376
-
1377
- // Render connections first (behind nodes)
1378
- for (let i = 0; i < this._FlowData.Connections.length; i++)
1379
- {
1380
- let tmpConnection = this._FlowData.Connections[i];
1381
- let tmpIsSelected = (this._FlowData.ViewState.SelectedConnectionHash === tmpConnection.Hash);
1382
-
1383
- this._ConnectionRenderer.renderConnection(
1384
- tmpConnection,
1385
- this._ConnectionsLayer,
1386
- tmpIsSelected
1387
- );
1388
- }
1389
-
1390
- // Render nodes
1391
- for (let i = 0; i < this._FlowData.Nodes.length; i++)
1392
- {
1393
- let tmpNode = this._FlowData.Nodes[i];
1394
- let tmpIsSelected = (this._FlowData.ViewState.SelectedNodeHash === tmpNode.Hash);
1395
- let tmpNodeTypeConfig = this._NodeTypeProvider.getNodeType(tmpNode.Type);
1396
-
1397
- // Enrich saved port data with metadata from the node type's DefaultPorts.
1398
- // Saved flow data may not include PortType or may have stale Side values,
1399
- // so we match each port to its DefaultPort counterpart by Label and Direction,
1400
- // then copy over PortType and Side from the authoritative node type definition.
1401
- if (tmpNodeTypeConfig && tmpNodeTypeConfig.DefaultPorts && tmpNode.Ports)
1402
- {
1403
- for (let p = 0; p < tmpNode.Ports.length; p++)
1404
- {
1405
- let tmpPort = tmpNode.Ports[p];
1406
- for (let d = 0; d < tmpNodeTypeConfig.DefaultPorts.length; d++)
1407
- {
1408
- let tmpDefault = tmpNodeTypeConfig.DefaultPorts[d];
1409
- if (tmpDefault.Label === tmpPort.Label && tmpDefault.Direction === tmpPort.Direction)
1410
- {
1411
- if (tmpDefault.PortType)
1412
- {
1413
- tmpPort.PortType = tmpDefault.PortType;
1414
- }
1415
- if (tmpDefault.Side)
1416
- {
1417
- tmpPort.Side = tmpDefault.Side;
1418
- }
1419
- break;
1420
- }
1421
- }
1422
- }
1423
- }
1424
-
1425
- this._NodeView.renderNode(tmpNode, this._NodesLayer, tmpIsSelected, tmpNodeTypeConfig);
1426
- }
770
+ // ---- Render Manager Delegations ----
1427
771
 
1428
- // Render properties panels and tethers
1429
- if (this._PropertiesPanelView && this._PanelsLayer && this._TethersLayer)
1430
- {
1431
- this._PropertiesPanelView.renderPanels(this._FlowData.OpenPanels, this._PanelsLayer, this._TethersLayer, this._FlowData.ViewState.SelectedTetherHash);
1432
- }
1433
-
1434
- // Update viewport transform
1435
- this.updateViewportTransform();
1436
- }
1437
-
1438
- /**
1439
- * Update a single node's position in the SVG without full re-render (for drag performance)
1440
- * @param {string} pNodeHash
1441
- * @param {number} pX
1442
- * @param {number} pY
1443
- */
1444
- updateNodePosition(pNodeHash, pX, pY)
1445
- {
1446
- let tmpNode = this.getNode(pNodeHash);
1447
- if (!tmpNode) return;
1448
-
1449
- if (this.options.EnableGridSnap)
1450
- {
1451
- pX = this._LayoutService.snapToGrid(pX, this.options.GridSnapSize);
1452
- pY = this._LayoutService.snapToGrid(pY, this.options.GridSnapSize);
1453
- }
1454
-
1455
- tmpNode.X = pX;
1456
- tmpNode.Y = pY;
1457
-
1458
- // Reset customized handle positions for connections/tethers involving this node
1459
- this._resetHandlesForNode(pNodeHash);
1460
-
1461
- // Update the node's SVG group transform for smooth dragging
1462
- let tmpNodeGroup = this._NodesLayer.querySelector(`[data-node-hash="${pNodeHash}"]`);
1463
- if (tmpNodeGroup)
1464
- {
1465
- tmpNodeGroup.setAttribute('transform', `translate(${pX}, ${pY})`);
1466
- }
1467
-
1468
- // Re-render connections that involve this node
1469
- this._renderConnectionsForNode(pNodeHash);
1470
-
1471
- // Update tethers for any panels attached to this node
1472
- this._renderTethersForNode(pNodeHash);
1473
- }
1474
-
1475
- /**
1476
- * Re-render only connections that involve a specific node (for drag performance)
1477
- * @param {string} pNodeHash
1478
- */
1479
- _renderConnectionsForNode(pNodeHash)
1480
- {
1481
- let tmpAffectedConnections = this._FlowData.Connections.filter((pConn) =>
1482
- {
1483
- return pConn.SourceNodeHash === pNodeHash || pConn.TargetNodeHash === pNodeHash;
1484
- });
1485
-
1486
- for (let i = 0; i < tmpAffectedConnections.length; i++)
1487
- {
1488
- let tmpConn = tmpAffectedConnections[i];
1489
- let tmpIsSelected = (this._FlowData.ViewState.SelectedConnectionHash === tmpConn.Hash);
1490
-
1491
- // Remove existing connection SVG elements
1492
- let tmpExisting = this._ConnectionsLayer.querySelectorAll(`[data-connection-hash="${tmpConn.Hash}"]`);
1493
- for (let j = 0; j < tmpExisting.length; j++)
1494
- {
1495
- tmpExisting[j].remove();
1496
- }
1497
-
1498
- // Re-render this connection
1499
- this._ConnectionRenderer.renderConnection(tmpConn, this._ConnectionsLayer, tmpIsSelected);
1500
- }
1501
- }
1502
-
1503
- /**
1504
- * Re-render tethers for panels attached to a specific node (for drag performance).
1505
- * @param {string} pNodeHash
1506
- */
1507
- _renderTethersForNode(pNodeHash)
1508
- {
1509
- if (!this._TethersLayer || !this._TetherService) return;
1510
-
1511
- let tmpAffectedPanels = this._FlowData.OpenPanels.filter((pPanel) => pPanel.NodeHash === pNodeHash);
1512
- if (tmpAffectedPanels.length === 0) return;
1513
-
1514
- // Remove existing tethers for these panels and re-render via TetherService
1515
- for (let i = 0; i < tmpAffectedPanels.length; i++)
1516
- {
1517
- let tmpExisting = this._TethersLayer.querySelectorAll(`[data-panel-hash="${tmpAffectedPanels[i].Hash}"]`);
1518
- for (let j = 0; j < tmpExisting.length; j++)
1519
- {
1520
- tmpExisting[j].remove();
1521
- }
1522
-
1523
- let tmpNodeData = this.getNode(tmpAffectedPanels[i].NodeHash);
1524
- if (!tmpNodeData) continue;
1525
-
1526
- let tmpIsSelected = (this._FlowData.ViewState.SelectedTetherHash === tmpAffectedPanels[i].Hash);
1527
- this._TetherService.renderTether(tmpAffectedPanels[i], tmpNodeData, this._TethersLayer, tmpIsSelected, this.options.ViewIdentifier);
1528
- }
1529
- }
772
+ renderFlow() { return this._RenderManager.renderFlow(); }
773
+ _renderSingleConnection(pConnectionHash) { return this._RenderManager.renderSingleConnection(pConnectionHash); }
774
+ _renderSingleTether(pPanelHash) { return this._RenderManager.renderSingleTether(pPanelHash); }
775
+ updateNodePosition(pNodeHash, pX, pY) { return this._RenderManager.updateNodePosition(pNodeHash, pX, pY); }
776
+ _renderConnectionsForNode(pNodeHash) { return this._RenderManager.renderConnectionsForNode(pNodeHash); }
777
+ _renderTethersForNode(pNodeHash) { return this._RenderManager.renderTethersForNode(pNodeHash); }
1530
778
 
1531
779
  // ---- Properties Panel Management ----
1532
780