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
@@ -12,6 +12,7 @@ const libPictServiceFlowDataManager = require('../services/PictService-Flow-Data
12
12
  const libPictServiceFlowConnectionHandleManager = require('../services/PictService-Flow-ConnectionHandleManager.js');
13
13
  const libPictServiceFlowRenderManager = require('../services/PictService-Flow-RenderManager.js');
14
14
  const libPictServiceFlowPortRenderer = require('../services/PictService-Flow-PortRenderer.js');
15
+ const libPictServiceFlowCursorManager = require('../services/PictService-Flow-CursorManager.js');
15
16
 
16
17
  const libPictProviderFlowNodeTypes = require('../providers/PictProvider-Flow-NodeTypes.js');
17
18
  const libPictProviderFlowEventHandler = require('../providers/PictProvider-Flow-EventHandler.js');
@@ -26,6 +27,7 @@ const libPictProviderFlowTheme = require('../providers/PictProvider-Flow-Theme.j
26
27
  const libPictProviderFlowRenderer = require('../providers/PictProvider-Flow-Renderer.js');
27
28
  const libPictProviderFlowStylePresets = require('../providers/PictProvider-Flow-StylePresets.js');
28
29
  const libPictProviderFlowNoise = require('../providers/PictProvider-Flow-Noise.js');
30
+ const libPictProviderFlowBackground = require('../providers/PictProvider-Flow-Background.js');
29
31
 
30
32
  const libPictViewFlowNode = require('./PictView-Flow-Node.js');
31
33
  const libPictViewFlowToolbar = require('./PictView-Flow-Toolbar.js');
@@ -51,6 +53,11 @@ const _DefaultConfiguration =
51
53
 
52
54
  TargetElementAddress: '#Flow-SVG-Container',
53
55
 
56
+ // Config profile: expands into a coherent bundle of the options below before
57
+ // your explicit options apply. null/'graph' = the historical behavior; also
58
+ // 'whiteboard', 'moodboard', 'presentation', 'erd'.
59
+ Profile: null,
60
+
54
61
  EnableToolbar: true,
55
62
  EnableAddNode: true,
56
63
  EnableCardPalette: true,
@@ -59,9 +66,18 @@ const _DefaultConfiguration =
59
66
  EnableZooming: true,
60
67
  EnableNodeDragging: true,
61
68
  EnableConnectionCreation: true,
69
+ // When on, a connection can be drawn between ANY two ports (any port can start a drag, any port can
70
+ // receive it), rather than only output -> input. Off by default so directed graphs (workflows) keep
71
+ // their source/target semantics; free-form canvases (a moodboard, whose links are undirected) turn it
72
+ // on so a card's ports connect in any direction.
73
+ EnableUndirectedConnections: false,
62
74
  // When on, the selected node shows a bottom-right grip that resizes it by drag. Off by default
63
75
  // so existing diagrams are unaffected; free-form canvases (moodboards) turn it on.
64
76
  EnableNodeResizing: false,
77
+ // When on, the selected node shows a rotate grip on an arm above it; drag it to set the node's
78
+ // Rotation (degrees), Shift to snap to 15. Off by default; free-form canvases turn it on. The
79
+ // properties panel can still set Rotation directly, so both ways work.
80
+ EnableNodeRotation: false,
65
81
  EnableGridSnap: false,
66
82
  GridSnapSize: 20,
67
83
  // When on, several nodes can be selected at once: shift-click a node to toggle it, drag on the
@@ -73,10 +89,62 @@ const _DefaultConfiguration =
73
89
  EnableAlignmentGuides: false,
74
90
  EnableLayoutMenu: true,
75
91
 
92
+ // Read-only / presentation mode. When true, editing interactions are inert
93
+ // (no drag, resize, connect, delete, panel edit); panning, zoom, selection,
94
+ // and node activation still work, and an onNodeActivate event fires on a node
95
+ // click so a host can show a read-only inspector. Toggle at runtime with
96
+ // setReadOnly(). The container also gets a 'pict-flow-readonly' class so chrome
97
+ // (ports, delete affordance, resize grips) can be hidden via CSS.
98
+ ReadOnly: false,
99
+
100
+ // Host-supplied toolbar buttons. Each entry is { Hash, Icon, Label?, Tooltip?, Active? } where Icon
101
+ // is a flow icon-provider key (edit, check, background, ...). They render in BOTH the docked and the
102
+ // floating toolbar (so they survive every toolbar mode) and, on click, fire onToolbarButton below.
103
+ // Empty by default, so existing consumers are unaffected.
104
+ ToolbarExtraButtons: [],
105
+ // Fired when a ToolbarExtraButtons button is clicked: onToolbarButton(pHash, pElement). The element
106
+ // lets the host anchor a popover next to the button. Off (false) by default.
107
+ onToolbarButton: false,
108
+
76
109
  MinZoom: 0.1,
77
110
  MaxZoom: 5.0,
78
111
  ZoomStep: 0.1,
79
112
 
113
+ // Mouse-wheel behavior. 'zoom' (default) keeps the historical wheel-zoom; set
114
+ // 'pan' so the wheel scrolls the canvas (ctrl/cmd+wheel still zooms), or
115
+ // 'none' to ignore the wheel. WheelZoomRequiresModifier makes a plain wheel
116
+ // pan and only ctrl/cmd+wheel zoom while in 'zoom' mode (tames the
117
+ // "scrolling zooms too fast" feel). Sensitivities scale the per-tick amount.
118
+ WheelMode: 'zoom',
119
+ WheelZoomRequiresModifier: false,
120
+ WheelZoomSensitivity: 1,
121
+ WheelPanSensitivity: 1,
122
+
123
+ // Canvas background. false (default) keeps the built-in grid from the
124
+ // container template. Set an object to control it natively:
125
+ // { Style: 'grid'|'dots'|'solid'|'image'|'none', Color?, Image?, GridSize?, DotSize? }
126
+ // This replaces hand-painting the SVG from a consumer.
127
+ Background: false,
128
+
129
+ // Content frame: a dashed "page" rectangle in content space marking the
130
+ // intended bounds (origin + width + preferred end). false (default) draws
131
+ // nothing. Set { Enabled?, X, Y, Width, Height, FitOnLoad? }; fitToFrame()
132
+ // fits it to the viewport (handy for read-only / presentation). Drag-to-resize
133
+ // handles are a later addition.
134
+ Frame: false,
135
+
136
+ // Presentation fit. 'contain' (default) keeps the legacy fitToFrame/FitOnLoad
137
+ // behavior. 'width' fits the frame's WIDTH to the container (anchored top-left
138
+ // + FitTopMargin) and keeps it fit as the container resizes — the jumbotron /
139
+ // background / fullscreen presentation. Content outside the frame bleeds.
140
+ FitMode: 'contain',
141
+ FitTopMargin: 0,
142
+
143
+ // When true (and not read-only), the content frame gets drag handles: four edge
144
+ // handles to set top/width/height and a move handle to reposition the view-area box.
145
+ EnableFrameEditing: false,
146
+ MinimumFrameSize: 40,
147
+
80
148
  DefaultNodeType: 'default',
81
149
  DefaultNodeWidth: 180,
82
150
  DefaultNodeHeight: 80,
@@ -93,6 +161,15 @@ const _DefaultConfiguration =
93
161
  DefaultLayoutParameters: {},
94
162
  DefaultLayoutAutoApply: false,
95
163
 
164
+ // Saved layouts (named position snapshots). LayoutStorage lets a host wire a
165
+ // server / IndexedDB backend by config instead of overriding the layout
166
+ // provider's methods: { read(cb), write(layouts, cb), delete(cb) } with
167
+ // Node-style cb(err, result). false (default) uses localStorage. When
168
+ // ApplyDefaultLayoutOnLoad is on, the layout marked default is applied once
169
+ // the graph first renders.
170
+ LayoutStorage: false,
171
+ ApplyDefaultLayoutOnLoad: false,
172
+
96
173
  // Edge-theme subsystem defaults — null = inherit from active layout's
97
174
  // `DefaultEdgeTheme` field, with hard fallback to 'Bezier'.
98
175
  DefaultEdgeTheme: null,
@@ -161,11 +238,88 @@ const _DefaultConfiguration =
161
238
  ]
162
239
  };
163
240
 
241
+ // Config profiles. Each is a partial option bundle that expands under the
242
+ // defaults and beneath the host's explicit options, so a single Profile knob
243
+ // gives a coherent setup that the host can still override field by field.
244
+ const _PROFILES =
245
+ {
246
+ // Directed graph editor (the historical default). Empty so the defaults stand.
247
+ graph: {},
248
+
249
+ // Free-form whiteboard: undirected links, resize, multi-select + alignment
250
+ // guides, wheel pans (ctrl/cmd zooms), dotted background, no layout menu.
251
+ whiteboard:
252
+ {
253
+ EnableUndirectedConnections: true,
254
+ EnableNodeResizing: true,
255
+ EnableNodeRotation: true,
256
+ EnableMultiSelect: true,
257
+ EnableAlignmentGuides: true,
258
+ EnableLayoutMenu: false,
259
+ WheelMode: 'pan',
260
+ WheelZoomRequiresModifier: true,
261
+ Background: { Style: 'dots' }
262
+ },
263
+
264
+ // Moodboard: free-form content cards, undirected links, no default node types
265
+ // or add-node button, edge-to-edge cards (no title bar), wheel pans.
266
+ moodboard:
267
+ {
268
+ EnableUndirectedConnections: true,
269
+ EnableNodeResizing: true,
270
+ EnableNodeRotation: true,
271
+ EnableAlignmentGuides: true,
272
+ EnableAddNode: false,
273
+ EnableLayoutMenu: false,
274
+ IncludeDefaultNodeTypes: false,
275
+ NodeTitleBarHeight: 0,
276
+ WheelMode: 'pan',
277
+ WheelZoomRequiresModifier: true,
278
+ // Flat, light canvas (no grid) by default; a host can recolor it via setBackground.
279
+ Background: { Style: 'solid', Color: '#f4f6f9' }
280
+ },
281
+
282
+ // Read-only presentation surface: no editing, no toolbar; plain wheel pans,
283
+ // ctrl/cmd wheel zooms.
284
+ presentation:
285
+ {
286
+ ReadOnly: true,
287
+ EnableToolbar: false,
288
+ WheelMode: 'pan',
289
+ WheelZoomRequiresModifier: true
290
+ },
291
+
292
+ // Entity-relationship diagrams (fleshed out in a later phase). Orthogonal
293
+ // edges read best for ERDs.
294
+ erd:
295
+ {
296
+ DefaultEdgeTheme: 'Orthogonal'
297
+ }
298
+ };
299
+
164
300
  class PictViewFlow extends libPictView
165
301
  {
302
+ /**
303
+ * Merge a Profile's option bundle between the defaults and the host's
304
+ * explicit options: defaults < profile < pOptions. Pure (no side effects),
305
+ * so it is unit testable without constructing the view.
306
+ * @param {Object} pOptions - the host-supplied options (may name a Profile)
307
+ * @returns {Object} the fully merged option set
308
+ */
309
+ static mergeProfileOptions(pOptions)
310
+ {
311
+ let tmpProfileName = (pOptions && pOptions.Profile) ? pOptions.Profile : (_DefaultConfiguration.Profile || 'graph');
312
+ let tmpProfile = _PROFILES[tmpProfileName] || {};
313
+ return Object.assign(
314
+ {},
315
+ JSON.parse(JSON.stringify(_DefaultConfiguration)),
316
+ JSON.parse(JSON.stringify(tmpProfile)),
317
+ pOptions || {});
318
+ }
319
+
166
320
  constructor(pFable, pOptions, pServiceHash)
167
321
  {
168
- let tmpOptions = Object.assign({}, JSON.parse(JSON.stringify(_DefaultConfiguration)), pOptions);
322
+ let tmpOptions = PictViewFlow.mergeProfileOptions(pOptions);
169
323
  super(pFable, tmpOptions, pServiceHash);
170
324
 
171
325
  this.serviceType = 'PictSectionFlow';
@@ -197,6 +351,7 @@ class PictViewFlow extends libPictView
197
351
  { ServiceType: 'PictProviderFlowNodeTypes', Library: libPictProviderFlowNodeTypes, Property: '_NodeTypeProvider', ExtraOptions: () => ({ AdditionalNodeTypes: this.options.NodeTypes, IncludeDefaultNodeTypes: this.options.IncludeDefaultNodeTypes }) },
198
352
  { ServiceType: 'PictProviderFlowEventHandler', Library: libPictProviderFlowEventHandler, Property: '_EventHandlerProvider' },
199
353
  { ServiceType: 'PictProviderFlowLayouts', Library: libPictProviderFlowLayouts, Property: '_LayoutProvider', PostInit: 'loadPersistedLayouts' },
354
+ { ServiceType: 'PictProviderFlowBackground', Library: libPictProviderFlowBackground, Property: '_BackgroundProvider' },
200
355
 
201
356
  // Services
202
357
  { ServiceType: 'PictServiceFlowPathGenerator', Library: libPictServiceFlowPathGenerator, Property: '_PathGenerator' },
@@ -205,6 +360,7 @@ class PictViewFlow extends libPictView
205
360
  { ServiceType: 'PictServiceFlowRenderManager', Library: libPictServiceFlowRenderManager, Property: '_RenderManager' },
206
361
  { ServiceType: 'PictServiceFlowPortRenderer', Library: libPictServiceFlowPortRenderer, Property: '_PortRenderer' },
207
362
  { ServiceType: 'PictServiceFlowInteractionManager', Library: libPictServiceFlowInteractionManager, Property: '_InteractionManager' },
363
+ { ServiceType: 'PictServiceFlowCursorManager', Library: libPictServiceFlowCursorManager, Property: '_CursorManager' },
208
364
  { ServiceType: 'PictServiceFlowConnectionRenderer', Library: libPictServiceFlowConnectionRenderer, Property: '_ConnectionRenderer' },
209
365
  { ServiceType: 'PictServiceFlowTether', Library: libPictServiceFlowTether, Property: '_TetherService' },
210
366
  { ServiceType: 'PictServiceFlowLayout', Library: libPictServiceFlowLayout, Property: '_LayoutService' },
@@ -271,6 +427,7 @@ class PictViewFlow extends libPictView
271
427
  this._PortRenderer = null;
272
428
 
273
429
  this._InteractionManager = null;
430
+ this._CursorManager = null;
274
431
  this._ConnectionRenderer = null;
275
432
  this._TetherService = null;
276
433
  this._LayoutService = null;
@@ -290,11 +447,26 @@ class PictViewFlow extends libPictView
290
447
  this._PanelChromeProvider = null;
291
448
  this._NodeTypeProvider = null;
292
449
  this._LayoutProvider = null;
450
+ this._BackgroundProvider = null;
293
451
  this._EventHandlerProvider = null;
294
452
  this._NodeView = null;
295
453
  this._ToolbarView = null;
296
454
  this._PropertiesPanelView = null;
297
455
 
456
+ // Registry of renderable renderers keyed by RenderableType. The default
457
+ // 'card' renderer (the node view) is registered once it exists; consumers
458
+ // register additional renderers (shape, sticky, text, image, category,
459
+ // connector) to draw renderables that are not cards.
460
+ this._RenderableRenderers = {};
461
+
462
+ // Read-only / presentation flag (runtime; defaults to the option).
463
+ this._ReadOnly = !!this.options.ReadOnly;
464
+
465
+ // In read-only, canvas pan + wheel-zoom are OFF by default (the wheel scrolls the page and the
466
+ // canvas reads as static content). A host flips it on with a "hand" toggle via
467
+ // setReadOnlyNavigation. No effect in edit mode.
468
+ this._ReadOnlyNavigation = false;
469
+
298
470
  this.initialRenderComplete = false;
299
471
  }
300
472
 
@@ -310,6 +482,377 @@ class PictViewFlow extends libPictView
310
482
  }
311
483
  }
312
484
 
485
+ /**
486
+ * Register a renderable renderer under a key. The default 'card' renderer is
487
+ * the node view; consumers register additional renderers (shape, sticky,
488
+ * text, image, category, connector) to draw non-card renderables. A renderer
489
+ * must expose renderNode(node, layerElement, isSelected, typeConfig).
490
+ * @param {string} pKey
491
+ * @param {Object} pRenderer
492
+ * @returns {boolean}
493
+ */
494
+ registerRenderableRenderer(pKey, pRenderer)
495
+ {
496
+ if (!pKey || !pRenderer)
497
+ {
498
+ return false;
499
+ }
500
+ this._RenderableRenderers[pKey] = pRenderer;
501
+ return true;
502
+ }
503
+
504
+ /**
505
+ * Resolve the renderable renderer for a node. A node type may name its
506
+ * renderer via the RenderableType field (default 'card'); unknown keys fall
507
+ * back to the card renderer so existing diagrams are unaffected.
508
+ * @param {Object} pNode
509
+ * @param {Object} pNodeTypeConfig
510
+ * @returns {Object} a renderer exposing renderNode(...)
511
+ */
512
+ resolveRenderableRenderer(pNode, pNodeTypeConfig)
513
+ {
514
+ // A node may name its renderer directly via pNode.RenderableType (wins), else
515
+ // the node type config's RenderableType, else the default 'card'. The node-level
516
+ // override lets a free-form canvas mint shape/sticky/text renderables without
517
+ // registering a node type for each. Unknown keys fall back to card so existing
518
+ // diagrams are unaffected.
519
+ let tmpKey = 'card';
520
+ if (pNode && typeof pNode.RenderableType === 'string' && pNode.RenderableType)
521
+ {
522
+ tmpKey = pNode.RenderableType;
523
+ }
524
+ else if (pNodeTypeConfig && pNodeTypeConfig.RenderableType)
525
+ {
526
+ tmpKey = pNodeTypeConfig.RenderableType;
527
+ }
528
+ return this._RenderableRenderers[tmpKey] || this._RenderableRenderers['card'] || this._NodeView;
529
+ }
530
+
531
+ /**
532
+ * Apply the configured canvas background to the live SVG. No-op when no
533
+ * Background is configured (the template grid stands). Safe to call on every
534
+ * render; idempotent.
535
+ */
536
+ _applyBackground()
537
+ {
538
+ if (this._BackgroundProvider)
539
+ {
540
+ return this._BackgroundProvider.apply(this);
541
+ }
542
+ return false;
543
+ }
544
+
545
+ /**
546
+ * Set the canvas background and repaint it. Pass an object
547
+ * { Style, Color?, Image?, GridSize?, DotSize? } or false to clear it back to
548
+ * the template grid. Stored on ViewState so it travels with the flow data.
549
+ * @param {Object|false} pBackground
550
+ */
551
+ setBackground(pBackground)
552
+ {
553
+ this._FlowData.ViewState.Background = pBackground || null;
554
+ return this._applyBackground();
555
+ }
556
+
557
+ /**
558
+ * Whether the flow is in read-only / presentation mode.
559
+ * @returns {boolean}
560
+ */
561
+ isReadOnly()
562
+ {
563
+ return !!this._ReadOnly;
564
+ }
565
+
566
+ /**
567
+ * Toggle read-only / presentation mode at runtime. Editing interactions go
568
+ * inert; the container gets the 'pict-flow-readonly' class so chrome can be
569
+ * hidden via CSS. Repaints so port/grip visibility updates.
570
+ * @param {boolean} pReadOnly
571
+ * @returns {boolean} the new read-only state
572
+ */
573
+ setReadOnly(pReadOnly)
574
+ {
575
+ this._ReadOnly = !!pReadOnly;
576
+ // Navigation is a read-only concept; leaving read-only clears it.
577
+ if (!this._ReadOnly) { this._ReadOnlyNavigation = false; }
578
+ this._applyReadOnlyClass();
579
+ if (this._CursorManager) { this._CursorManager.update(); }
580
+ if (this.initialRenderComplete)
581
+ {
582
+ this.renderFlow();
583
+ }
584
+ return this._ReadOnly;
585
+ }
586
+
587
+ /**
588
+ * Whether read-only canvas navigation (pan + wheel-zoom) is enabled. When off
589
+ * (the default in read-only), the wheel scrolls the page and the canvas does
590
+ * not pan, so it reads as static content.
591
+ * @returns {boolean}
592
+ */
593
+ isReadOnlyNavigation()
594
+ {
595
+ return !!this._ReadOnlyNavigation;
596
+ }
597
+
598
+ /**
599
+ * Turn read-only canvas navigation (pan + wheel-zoom) on or off. This is the
600
+ * "hand" toggle for a presentation surface; it has no effect in edit mode.
601
+ * @param {boolean} pOn
602
+ * @returns {boolean} the new navigation state
603
+ */
604
+ setReadOnlyNavigation(pOn)
605
+ {
606
+ this._ReadOnlyNavigation = !!pOn;
607
+ this._applyReadOnlyClass();
608
+ if (this._CursorManager) { this._CursorManager.update(); }
609
+ if (this._EventHandlerProvider)
610
+ {
611
+ this._EventHandlerProvider.fireEvent('onReadOnlyNavigationChanged', this._ReadOnlyNavigation);
612
+ }
613
+ return this._ReadOnlyNavigation;
614
+ }
615
+
616
+ /**
617
+ * Reflect the read-only flag as a class on the flow container so CSS can hide
618
+ * editing chrome (ports, delete affordance, resize grips). Internal.
619
+ */
620
+ _applyReadOnlyClass()
621
+ {
622
+ let tmpWrapper = this.pict.ContentAssignment.getElement(`#Flow-Wrapper-${this.options.ViewIdentifier}`);
623
+ if (tmpWrapper && tmpWrapper.length > 0 && tmpWrapper[0].classList)
624
+ {
625
+ if (this._ReadOnly)
626
+ {
627
+ tmpWrapper[0].classList.add('pict-flow-readonly');
628
+ }
629
+ else
630
+ {
631
+ tmpWrapper[0].classList.remove('pict-flow-readonly');
632
+ }
633
+ // 'navigating' = read-only with pan/zoom enabled; drives the grab cursor.
634
+ if (this._ReadOnly && this._ReadOnlyNavigation)
635
+ {
636
+ tmpWrapper[0].classList.add('pict-flow-navigating');
637
+ }
638
+ else
639
+ {
640
+ tmpWrapper[0].classList.remove('pict-flow-navigating');
641
+ }
642
+ }
643
+ }
644
+
645
+ /**
646
+ * Resolve the effective content frame: ViewState wins, then the option.
647
+ * @returns {Object|null}
648
+ */
649
+ _resolveFrame()
650
+ {
651
+ let tmpFrame = (this._FlowData && this._FlowData.ViewState && this._FlowData.ViewState.Frame)
652
+ ? this._FlowData.ViewState.Frame
653
+ : this.options.Frame;
654
+ return (tmpFrame && typeof tmpFrame === 'object') ? tmpFrame : null;
655
+ }
656
+
657
+ /**
658
+ * Set (or clear, with false) the content frame and repaint it. Stored on
659
+ * ViewState so it travels with the flow data.
660
+ * @param {Object|false} pFrame
661
+ */
662
+ setFrame(pFrame)
663
+ {
664
+ this._FlowData.ViewState.Frame = (pFrame && typeof pFrame === 'object') ? pFrame : null;
665
+ this._applyFrame();
666
+ return this._FlowData.ViewState.Frame;
667
+ }
668
+
669
+ /**
670
+ * Read the effective content frame (ViewState wins, then the option), or null
671
+ * when none is set. The public read counterpart to setFrame.
672
+ * @returns {Object|null}
673
+ */
674
+ getFrame()
675
+ {
676
+ return this._resolveFrame();
677
+ }
678
+
679
+ /**
680
+ * Toggle the content frame's drag handles at runtime (e.g. an editor turning on
681
+ * "set the view area"). Repaints the frame so the handles appear / disappear.
682
+ * @param {boolean} pEnabled
683
+ * @returns {boolean}
684
+ */
685
+ setFrameEditing(pEnabled)
686
+ {
687
+ this.options.EnableFrameEditing = !!pEnabled;
688
+ this._applyFrame();
689
+ return this.options.EnableFrameEditing;
690
+ }
691
+
692
+ /**
693
+ * Draw, update, or remove the content-frame rectangle inside the viewport
694
+ * group (behind the content layers). No-op until the viewport exists.
695
+ */
696
+ _applyFrame()
697
+ {
698
+ if (!this._ViewportElement) return;
699
+
700
+ let tmpId = `Flow-Frame-${this.options.ViewIdentifier}`;
701
+ let tmpExisting = this._ViewportElement.querySelector(`#${tmpId}`);
702
+ let tmpFrame = this._resolveFrame();
703
+
704
+ if (!tmpFrame || tmpFrame.Enabled === false || !tmpFrame.Width || !tmpFrame.Height)
705
+ {
706
+ if (tmpExisting) tmpExisting.remove();
707
+ return;
708
+ }
709
+
710
+ let tmpRect = tmpExisting;
711
+ if (!tmpRect)
712
+ {
713
+ tmpRect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
714
+ tmpRect.setAttribute('id', tmpId);
715
+ tmpRect.setAttribute('class', 'pict-flow-frame');
716
+ // First child of the viewport group: behind connections and nodes.
717
+ this._ViewportElement.insertBefore(tmpRect, this._ViewportElement.firstChild);
718
+ }
719
+ tmpRect.setAttribute('x', tmpFrame.X || 0);
720
+ tmpRect.setAttribute('y', tmpFrame.Y || 0);
721
+ tmpRect.setAttribute('width', tmpFrame.Width);
722
+ tmpRect.setAttribute('height', tmpFrame.Height);
723
+
724
+ this._applyFrameHandles(tmpFrame);
725
+ }
726
+
727
+ /**
728
+ * Draw, refresh, or remove the content frame's drag handles. Shown only when
729
+ * EnableFrameEditing is on and the view is editable: four edge handles (set top /
730
+ * width / height) and a move handle at the top-left. Rendered on top of the content
731
+ * (appended last in the viewport) so they are grabbable; their data-element-type routes
732
+ * pointer-down to the InteractionManager's frame edit paths.
733
+ * @param {Object} pFrame - the resolved frame ({X,Y,Width,Height})
734
+ */
735
+ _applyFrameHandles(pFrame)
736
+ {
737
+ if (!this._ViewportElement) return;
738
+
739
+ let tmpId = `Flow-FrameHandles-${this.options.ViewIdentifier}`;
740
+ let tmpExisting = this._ViewportElement.querySelector(`#${tmpId}`);
741
+ if (tmpExisting) tmpExisting.remove();
742
+
743
+ let tmpEditable = this.options.EnableFrameEditing
744
+ && !(typeof this.isReadOnly === 'function' && this.isReadOnly())
745
+ && pFrame && pFrame.Width && pFrame.Height;
746
+ if (!tmpEditable) return;
747
+
748
+ let tmpX = pFrame.X || 0;
749
+ let tmpY = pFrame.Y || 0;
750
+ let tmpW = pFrame.Width;
751
+ let tmpH = pFrame.Height;
752
+ let tmpSize = 12;
753
+
754
+ let tmpGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
755
+ tmpGroup.setAttribute('id', tmpId);
756
+ tmpGroup.setAttribute('class', 'pict-flow-frame-handles');
757
+
758
+ let tmpHandles =
759
+ [
760
+ { Edge: 'n', Type: 'frame-resize', CX: tmpX + tmpW / 2, CY: tmpY },
761
+ { Edge: 's', Type: 'frame-resize', CX: tmpX + tmpW / 2, CY: tmpY + tmpH },
762
+ { Edge: 'e', Type: 'frame-resize', CX: tmpX + tmpW, CY: tmpY + tmpH / 2 },
763
+ { Edge: 'w', Type: 'frame-resize', CX: tmpX, CY: tmpY + tmpH / 2 },
764
+ { Edge: 'move', Type: 'frame-move', CX: tmpX, CY: tmpY }
765
+ ];
766
+ for (let i = 0; i < tmpHandles.length; i++)
767
+ {
768
+ let tmpHandle = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
769
+ tmpHandle.setAttribute('class', 'pict-flow-frame-handle pict-flow-frame-handle-' + tmpHandles[i].Edge);
770
+ tmpHandle.setAttribute('x', String(tmpHandles[i].CX - tmpSize / 2));
771
+ tmpHandle.setAttribute('y', String(tmpHandles[i].CY - tmpSize / 2));
772
+ tmpHandle.setAttribute('width', String(tmpSize));
773
+ tmpHandle.setAttribute('height', String(tmpSize));
774
+ tmpHandle.setAttribute('rx', '2');
775
+ tmpHandle.setAttribute('data-element-type', tmpHandles[i].Type);
776
+ if (tmpHandles[i].Edge !== 'move')
777
+ {
778
+ tmpHandle.setAttribute('data-frame-edge', tmpHandles[i].Edge);
779
+ }
780
+ tmpGroup.appendChild(tmpHandle);
781
+ }
782
+ this._ViewportElement.appendChild(tmpGroup);
783
+ }
784
+
785
+ /**
786
+ * Fit the viewport to the content frame (zoom + pan so it fills the view).
787
+ * @returns {boolean}
788
+ */
789
+ fitToFrame()
790
+ {
791
+ return this._ViewportManager ? this._ViewportManager.fitToFrame(this._resolveFrame()) : false;
792
+ }
793
+
794
+ /**
795
+ * Fit the viewport so the content frame's WIDTH fills the view (anchored top-left + an
796
+ * optional top margin), letting content bleed past and vertical overflow scroll. This is
797
+ * the presentation fit a jumbotron / background board uses; fitToFrame() contains instead.
798
+ * @param {Object} [pOptions] - { TopMargin }
799
+ * @returns {boolean}
800
+ */
801
+ fitToWidth(pOptions)
802
+ {
803
+ if (!this._ViewportManager) return false;
804
+ let tmpOptions = pOptions || {};
805
+ if (typeof tmpOptions.TopMargin !== 'number')
806
+ {
807
+ tmpOptions = { TopMargin: this.options.FitTopMargin || 0 };
808
+ }
809
+ return this._ViewportManager.fitToFrameWidth(this._resolveFrame(), tmpOptions);
810
+ }
811
+
812
+ /**
813
+ * When FitMode is 'width', fit the frame to the container width now and keep it fit as the
814
+ * container resizes (a ResizeObserver, the documented exception to the no-listener rule).
815
+ * No-op without a frame or when FitMode is not 'width'.
816
+ */
817
+ _setupFitObserver()
818
+ {
819
+ if (this.options.FitMode !== 'width' || !this._resolveFrame())
820
+ {
821
+ // Not width-fit (e.g. switched back to 'contain'): drop any observer we
822
+ // installed so a stale ResizeObserver does not keep re-fitting the canvas.
823
+ this._teardownFitObserver();
824
+ return;
825
+ }
826
+ this.fitToWidth();
827
+ if ((typeof ResizeObserver !== 'undefined') && this._SVGElement)
828
+ {
829
+ // (Re)observe the CURRENT SVG element. A re-render (e.g. a host toggling presentation mode)
830
+ // can replace the SVG, which would leave a previously-installed observer watching a stale,
831
+ // detached node — so re-point it whenever the observed element has changed.
832
+ if (this._FitObserver && this._FitObservedElement === this._SVGElement) { return; }
833
+ this._teardownFitObserver();
834
+ let tmpSelf = this;
835
+ this._FitObserver = new ResizeObserver(function () { tmpSelf.fitToWidth(); });
836
+ this._FitObserver.observe(this._SVGElement);
837
+ this._FitObservedElement = this._SVGElement;
838
+ }
839
+ }
840
+
841
+ /**
842
+ * Disconnect the width-fit ResizeObserver, if one is installed. Called when a
843
+ * consumer leaves 'width' FitMode (e.g. a moodboard switching back to canvas)
844
+ * so the observer stops firing. Safe to call when no observer exists.
845
+ */
846
+ _teardownFitObserver()
847
+ {
848
+ if (this._FitObserver)
849
+ {
850
+ this._FitObserver.disconnect();
851
+ this._FitObserver = null;
852
+ }
853
+ this._FitObservedElement = null;
854
+ }
855
+
313
856
  _instantiateServices()
314
857
  {
315
858
  for (let i = 0; i < this._ServiceRegistry.length; i++)
@@ -488,12 +1031,24 @@ class PictViewFlow extends libPictView
488
1031
  }
489
1032
  this._SVGElement = tmpSVGElements[0];
490
1033
 
1034
+ // Paint a configured background (no-op when none is set; template grid stands).
1035
+ this._applyBackground();
1036
+
1037
+ // Reflect read-only mode on the container (drives chrome-hiding CSS).
1038
+ this._applyReadOnlyClass();
1039
+
1040
+ // Set the initial canvas cursor from the (idle) state + current mode.
1041
+ if (this._CursorManager) { this._CursorManager.update(); }
1042
+
491
1043
  let tmpViewportElements = this.pict.ContentAssignment.getElement(`#Flow-Viewport-${tmpViewIdentifier}`);
492
1044
  if (tmpViewportElements.length > 0)
493
1045
  {
494
1046
  this._ViewportElement = tmpViewportElements[0];
495
1047
  }
496
1048
 
1049
+ // Draw the content frame, if configured (needs the viewport <g>).
1050
+ this._applyFrame();
1051
+
497
1052
  let tmpNodesElements = this.pict.ContentAssignment.getElement(`#Flow-Nodes-${tmpViewIdentifier}`);
498
1053
  if (tmpNodesElements.length > 0)
499
1054
  {
@@ -562,7 +1117,8 @@ class PictViewFlow extends libPictView
562
1117
  DefaultDestinationAddress: `#Flow-Toolbar-${tmpViewIdentifier}`,
563
1118
  FlowViewIdentifier: tmpViewIdentifier,
564
1119
  EnableAddNode: this.options.EnableAddNode,
565
- EnableCardPalette: this.options.EnableCardPalette
1120
+ EnableCardPalette: this.options.EnableCardPalette,
1121
+ ToolbarExtraButtons: this.options.ToolbarExtraButtons
566
1122
  }
567
1123
  ));
568
1124
  // Use the toolbar's render method after it's set up
@@ -581,6 +1137,8 @@ class PictViewFlow extends libPictView
581
1137
  this._NodeView = this.fable.instantiateServiceProviderWithoutRegistration('PictViewFlowNode',
582
1138
  Object.assign({}, libPictViewFlowNode.default_configuration, tmpNodeViewOptions));
583
1139
  this._NodeView._FlowView = this;
1140
+ // The default renderable renderer. Non-card renderers register alongside it.
1141
+ this._RenderableRenderers['card'] = this._NodeView;
584
1142
 
585
1143
  // Setup the properties panel renderer
586
1144
  this._PropertiesPanelView = this.fable.instantiateServiceProviderWithoutRegistration('PictViewFlowPropertiesPanel',
@@ -607,8 +1165,45 @@ class PictViewFlow extends libPictView
607
1165
 
608
1166
  // Render the initial flow
609
1167
  this.renderFlow();
1168
+
1169
+ // Optionally snap to the default saved layout once the graph is drawn. A host with an async
1170
+ // LayoutStorage that resolves later should call applyDefaultLayout() from its load callback.
1171
+ if (this.options.ApplyDefaultLayoutOnLoad && this._LayoutProvider && this._LayoutProvider.getDefaultLayout())
1172
+ {
1173
+ this._LayoutProvider.applyDefaultLayout();
1174
+ }
1175
+
1176
+ // Presentation fit. FitMode 'width' fits the frame to the container width now and on
1177
+ // resize; otherwise honor a frame's legacy FitOnLoad (contain). Both need the SVG sized,
1178
+ // so this runs after the initial render.
1179
+ if (this.options.FitMode === 'width')
1180
+ {
1181
+ this._setupFitObserver();
1182
+ }
1183
+ else
1184
+ {
1185
+ let tmpFrame = this._resolveFrame();
1186
+ if (tmpFrame && tmpFrame.FitOnLoad)
1187
+ {
1188
+ this.fitToFrame();
1189
+ }
1190
+ }
610
1191
  }
611
1192
 
1193
+ // ---- Saved-layout delegations ----
1194
+ // A clean view-level API over the layout provider for saving, restoring, and
1195
+ // choosing a default arrangement of the same graph. The toolbar uses the
1196
+ // provider directly; these are additive.
1197
+
1198
+ saveLayout(pName) { return this._LayoutProvider ? this._LayoutProvider.saveLayout(pName) : null; }
1199
+ restoreLayout(pHash) { return this._LayoutProvider ? this._LayoutProvider.restoreLayout(pHash) : false; }
1200
+ applyLayout(pHash) { return this.restoreLayout(pHash); }
1201
+ deleteLayout(pHash) { return this._LayoutProvider ? this._LayoutProvider.deleteLayout(pHash) : false; }
1202
+ getLayouts() { return this._LayoutProvider ? this._LayoutProvider.getLayouts() : []; }
1203
+ setDefaultLayout(pHash) { return this._LayoutProvider ? this._LayoutProvider.setDefaultLayout(pHash) : false; }
1204
+ getDefaultLayout() { return this._LayoutProvider ? this._LayoutProvider.getDefaultLayout() : null; }
1205
+ applyDefaultLayout() { return this._LayoutProvider ? this._LayoutProvider.applyDefaultLayout() : false; }
1206
+
612
1207
  // ---- Data Manager Delegations ----
613
1208
 
614
1209
  marshalToView() { return this._DataManager.marshalToView(); }
@@ -1418,6 +2013,27 @@ class PictViewFlow extends libPictView
1418
2013
  let tmpSource = this.getPortPosition(tmpConnection.SourceNodeHash, tmpConnection.SourcePortHash);
1419
2014
  let tmpTarget = this.getPortPosition(tmpConnection.TargetNodeHash, tmpConnection.TargetPortHash);
1420
2015
  if (!tmpSource || !tmpTarget) return null;
2016
+ // A connection renders as a curve, so the straight-line average of its endpoints sits OFF the
2017
+ // line (the panel tether would point into empty space). Prefer the true midpoint of the rendered
2018
+ // path -- getPointAtLength at half its length, which is genuinely on the line. Fall back to the
2019
+ // endpoint average when the path element or SVG geometry is unavailable (e.g. server-side render).
2020
+ if (this._SVGElement && typeof this._SVGElement.querySelector === 'function')
2021
+ {
2022
+ let tmpPathElement = this._SVGElement.querySelector('.pict-flow-connection[data-connection-hash="' + pConnectionHash + '"]');
2023
+ if (tmpPathElement && typeof tmpPathElement.getTotalLength === 'function' && typeof tmpPathElement.getPointAtLength === 'function')
2024
+ {
2025
+ try
2026
+ {
2027
+ let tmpLength = tmpPathElement.getTotalLength();
2028
+ if (tmpLength > 0)
2029
+ {
2030
+ let tmpPoint = tmpPathElement.getPointAtLength(tmpLength / 2);
2031
+ return { x: tmpPoint.x, y: tmpPoint.y };
2032
+ }
2033
+ }
2034
+ catch (pError) { /* fall through to the straight-line midpoint */ }
2035
+ }
2036
+ }
1421
2037
  return { x: (tmpSource.x + tmpTarget.x) / 2, y: (tmpSource.y + tmpTarget.y) / 2 };
1422
2038
  }
1423
2039
 
@@ -1551,4 +2167,13 @@ class PictViewFlow extends libPictView
1551
2167
 
1552
2168
  module.exports = PictViewFlow;
1553
2169
 
1554
- module.exports.default_configuration = _DefaultConfiguration;
2170
+ // default_configuration is intentionally NOT exported. Pict's addView merges a
2171
+ // view's default_configuration UNDER the host's options, which would make those
2172
+ // now-explicit defaults override Profile values. Defaults are applied inside the
2173
+ // constructor via mergeProfileOptions (defaults < profile < host) so Profile
2174
+ // works as a real config knob. Inspect resolved defaults with
2175
+ // PictViewFlow.mergeProfileOptions({}).
2176
+
2177
+ // Config profiles (graph/whiteboard/moodboard/presentation/erd), exposed so
2178
+ // consumers can inspect or extend them.
2179
+ module.exports.Profiles = _PROFILES;