pict-section-flow 1.4.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 (164) 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 +73 -7
  5. package/source/providers/PictProvider-Flow-Geometry.js +11 -421
  6. package/source/providers/PictProvider-Flow-Icons.js +12 -0
  7. package/source/providers/PictProvider-Flow-Layouts.js +107 -0
  8. package/source/services/PictService-Flow-ConnectionRenderer.js +1 -1
  9. package/source/services/PictService-Flow-CursorManager.js +113 -0
  10. package/source/services/PictService-Flow-InteractionManager.js +439 -59
  11. package/source/services/PictService-Flow-Layout.js +21 -16
  12. package/source/services/PictService-Flow-PathGenerator.js +30 -417
  13. package/source/services/PictService-Flow-RenderManager.js +9 -1
  14. package/source/services/PictService-Flow-ViewportManager.js +102 -0
  15. package/source/views/PictView-Flow-FloatingToolbar.js +5 -1
  16. package/source/views/PictView-Flow-Node.js +29 -0
  17. package/source/views/PictView-Flow-Toolbar.js +50 -3
  18. package/source/views/PictView-Flow.js +591 -2
  19. package/.claude/launch.json +0 -11
  20. package/docs/.nojekyll +0 -0
  21. package/docs/Architecture.md +0 -163
  22. package/docs/Custom-Styling.md +0 -275
  23. package/docs/Data_Model.md +0 -149
  24. package/docs/Event_System.md +0 -156
  25. package/docs/Getting_Started.md +0 -237
  26. package/docs/Implementation_Reference.md +0 -528
  27. package/docs/Layout_Persistence.md +0 -117
  28. package/docs/README.md +0 -103
  29. package/docs/Theme_Integration.md +0 -150
  30. package/docs/_brand.json +0 -18
  31. package/docs/_cover.md +0 -17
  32. package/docs/_playground.json +0 -24
  33. package/docs/_sidebar.md +0 -57
  34. package/docs/_topbar.md +0 -8
  35. package/docs/_version.json +0 -7
  36. package/docs/api/PictFlowCard.md +0 -216
  37. package/docs/api/PictFlowCardPropertiesPanel.md +0 -235
  38. package/docs/api/addConnection.md +0 -101
  39. package/docs/api/addNode.md +0 -137
  40. package/docs/api/autoLayout.md +0 -77
  41. package/docs/api/getFlowData.md +0 -112
  42. package/docs/api/marshalToView.md +0 -95
  43. package/docs/api/openPanel.md +0 -128
  44. package/docs/api/registerHandler.md +0 -174
  45. package/docs/api/registerNodeType.md +0 -142
  46. package/docs/api/removeConnection.md +0 -57
  47. package/docs/api/removeNode.md +0 -80
  48. package/docs/api/saveLayout.md +0 -152
  49. package/docs/api/screenToSVGCoords.md +0 -68
  50. package/docs/api/selectNode.md +0 -116
  51. package/docs/api/setTheme.md +0 -168
  52. package/docs/api/setZoom.md +0 -97
  53. package/docs/api/toggleFullscreen.md +0 -68
  54. package/docs/card-help/EACH.md +0 -19
  55. package/docs/card-help/FREAD.md +0 -24
  56. package/docs/card-help/FWRITE.md +0 -24
  57. package/docs/card-help/GET.md +0 -22
  58. package/docs/card-help/ITE.md +0 -23
  59. package/docs/card-help/LOG.md +0 -23
  60. package/docs/card-help/NOTE.md +0 -17
  61. package/docs/card-help/PREV.md +0 -18
  62. package/docs/card-help/SET.md +0 -27
  63. package/docs/card-help/SPKL.md +0 -22
  64. package/docs/card-help/STAT.md +0 -23
  65. package/docs/card-help/SW.md +0 -25
  66. package/docs/diagrams/architecture-at-a-glance.excalidraw +0 -4270
  67. package/docs/diagrams/architecture-at-a-glance.mmd +0 -30
  68. package/docs/diagrams/architecture-at-a-glance.svg +0 -2
  69. package/docs/diagrams/data-flow.excalidraw +0 -1451
  70. package/docs/diagrams/data-flow.mmd +0 -17
  71. package/docs/diagrams/data-flow.svg +0 -2
  72. package/docs/diagrams/high-level-design.excalidraw +0 -5767
  73. package/docs/diagrams/high-level-design.mmd +0 -86
  74. package/docs/diagrams/high-level-design.svg +0 -2
  75. package/docs/diagrams/relationships.excalidraw +0 -3852
  76. package/docs/diagrams/relationships.mmd +0 -9
  77. package/docs/diagrams/relationships.svg +0 -2
  78. package/docs/diagrams/service-initialization-sequence.excalidraw +0 -1466
  79. package/docs/diagrams/service-initialization-sequence.mmd +0 -19
  80. package/docs/diagrams/service-initialization-sequence.svg +0 -2
  81. package/docs/diagrams/svg-layer-structure.excalidraw +0 -1060
  82. package/docs/diagrams/svg-layer-structure.mmd +0 -18
  83. package/docs/diagrams/svg-layer-structure.svg +0 -2
  84. package/docs/examples/README.md +0 -9
  85. package/docs/examples/simple_cards/README.md +0 -677
  86. package/docs/examples/simple_cards/css/flowexample.css +0 -65
  87. package/docs/examples/simple_cards/index.html +0 -32
  88. package/docs/examples/simple_cards/js/pict.min.js +0 -12
  89. package/docs/examples/simple_cards/pict-section-flow-example-simple-cards.compatible.min.js +0 -1
  90. package/docs/index.html +0 -38
  91. package/docs/playground/app.json +0 -6
  92. package/docs/playground/appdata.json +0 -85
  93. package/docs/playground/application.js +0 -23
  94. package/docs/playground/pict.json +0 -17
  95. package/docs/playground/runtime/pict-application.min.js +0 -2
  96. package/docs/playground/runtime/pict-section-flow.min.js +0 -2
  97. package/docs/playground/runtime/pict-section-modal.min.js +0 -2
  98. package/docs/playground/runtime/pict.min.js +0 -12
  99. package/docs/retold-catalog.json +0 -244
  100. package/docs/retold-keyword-index.json +0 -26028
  101. package/example_applications/simple_cards/css/flowexample.css +0 -65
  102. package/example_applications/simple_cards/html/index.html +0 -32
  103. package/example_applications/simple_cards/package.json +0 -52
  104. package/example_applications/simple_cards/source/Pict-Application-FlowExample-Configuration.json +0 -15
  105. package/example_applications/simple_cards/source/Pict-Application-FlowExample.js +0 -539
  106. package/example_applications/simple_cards/source/card-help-content.js +0 -16
  107. package/example_applications/simple_cards/source/cards/FlowCard-Comment.js +0 -38
  108. package/example_applications/simple_cards/source/cards/FlowCard-DataPreview.js +0 -44
  109. package/example_applications/simple_cards/source/cards/FlowCard-Each.js +0 -38
  110. package/example_applications/simple_cards/source/cards/FlowCard-FileRead.js +0 -56
  111. package/example_applications/simple_cards/source/cards/FlowCard-FileWrite.js +0 -50
  112. package/example_applications/simple_cards/source/cards/FlowCard-GetValue.js +0 -37
  113. package/example_applications/simple_cards/source/cards/FlowCard-IfThenElse.js +0 -49
  114. package/example_applications/simple_cards/source/cards/FlowCard-LogValues.js +0 -55
  115. package/example_applications/simple_cards/source/cards/FlowCard-SetValue.js +0 -97
  116. package/example_applications/simple_cards/source/cards/FlowCard-Sparkline.js +0 -100
  117. package/example_applications/simple_cards/source/cards/FlowCard-StatusMonitor.js +0 -46
  118. package/example_applications/simple_cards/source/cards/FlowCard-Switch.js +0 -39
  119. package/example_applications/simple_cards/source/providers/PictRouter-FlowExample-Configuration.json +0 -22
  120. package/example_applications/simple_cards/source/sample-flows.js +0 -410
  121. package/example_applications/simple_cards/source/views/PictView-FlowExample-About.js +0 -184
  122. package/example_applications/simple_cards/source/views/PictView-FlowExample-BottomBar.js +0 -77
  123. package/example_applications/simple_cards/source/views/PictView-FlowExample-Documentation.js +0 -325
  124. package/example_applications/simple_cards/source/views/PictView-FlowExample-FileWriteInfo.js +0 -59
  125. package/example_applications/simple_cards/source/views/PictView-FlowExample-Layout.js +0 -90
  126. package/example_applications/simple_cards/source/views/PictView-FlowExample-MainWorkspace.js +0 -453
  127. package/example_applications/simple_cards/source/views/PictView-FlowExample-TopBar.js +0 -95
  128. package/scripts/generate-card-help.js +0 -214
  129. package/source/providers/edges/Edge-Bezier.js +0 -41
  130. package/source/providers/edges/Edge-Orthogonal.js +0 -37
  131. package/source/providers/edges/Edge-OrthogonalSnap.js +0 -72
  132. package/source/providers/edges/Edge-Perimeter-Linear.js +0 -31
  133. package/source/providers/edges/Edge-Perimeter-Orthogonal.js +0 -39
  134. package/source/providers/edges/Edge-Perimeter.js +0 -48
  135. package/source/providers/edges/Edge-PerimeterMath.js +0 -92
  136. package/source/providers/edges/Edge-Straight.js +0 -24
  137. package/source/providers/layouts/Layout-Circular.js +0 -203
  138. package/source/providers/layouts/Layout-Coerce.js +0 -40
  139. package/source/providers/layouts/Layout-Columnar.js +0 -134
  140. package/source/providers/layouts/Layout-Custom.js +0 -27
  141. package/source/providers/layouts/Layout-ForcedFromCenter.js +0 -256
  142. package/source/providers/layouts/Layout-Grid.js +0 -134
  143. package/source/providers/layouts/Layout-Layered.js +0 -155
  144. package/source/providers/layouts/Layout-Rank.js +0 -141
  145. package/source/providers/layouts/Layout-Staggered.js +0 -131
  146. package/source/providers/layouts/Layout-Tabular.js +0 -94
  147. package/test/CardPalette_tests.js +0 -43
  148. package/test/ConnectionHandleManager_tests.js +0 -717
  149. package/test/ConnectionRenderer_tests.js +0 -591
  150. package/test/ConnectionStyle_tests.js +0 -90
  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
  163. package/test/ToolbarExtraButtons_tests.js +0 -138
  164. package/test/UndirectedConnections_tests.js +0 -70
@@ -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,
@@ -67,6 +74,10 @@ const _DefaultConfiguration =
67
74
  // When on, the selected node shows a bottom-right grip that resizes it by drag. Off by default
68
75
  // so existing diagrams are unaffected; free-form canvases (moodboards) turn it on.
69
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,
70
81
  EnableGridSnap: false,
71
82
  GridSnapSize: 20,
72
83
  // When on, several nodes can be selected at once: shift-click a node to toggle it, drag on the
@@ -78,6 +89,14 @@ const _DefaultConfiguration =
78
89
  EnableAlignmentGuides: false,
79
90
  EnableLayoutMenu: true,
80
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
+
81
100
  // Host-supplied toolbar buttons. Each entry is { Hash, Icon, Label?, Tooltip?, Active? } where Icon
82
101
  // is a flow icon-provider key (edit, check, background, ...). They render in BOTH the docked and the
83
102
  // floating toolbar (so they survive every toolbar mode) and, on click, fire onToolbarButton below.
@@ -91,6 +110,41 @@ const _DefaultConfiguration =
91
110
  MaxZoom: 5.0,
92
111
  ZoomStep: 0.1,
93
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
+
94
148
  DefaultNodeType: 'default',
95
149
  DefaultNodeWidth: 180,
96
150
  DefaultNodeHeight: 80,
@@ -107,6 +161,15 @@ const _DefaultConfiguration =
107
161
  DefaultLayoutParameters: {},
108
162
  DefaultLayoutAutoApply: false,
109
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
+
110
173
  // Edge-theme subsystem defaults — null = inherit from active layout's
111
174
  // `DefaultEdgeTheme` field, with hard fallback to 'Bezier'.
112
175
  DefaultEdgeTheme: null,
@@ -175,11 +238,88 @@ const _DefaultConfiguration =
175
238
  ]
176
239
  };
177
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
+
178
300
  class PictViewFlow extends libPictView
179
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
+
180
320
  constructor(pFable, pOptions, pServiceHash)
181
321
  {
182
- let tmpOptions = Object.assign({}, JSON.parse(JSON.stringify(_DefaultConfiguration)), pOptions);
322
+ let tmpOptions = PictViewFlow.mergeProfileOptions(pOptions);
183
323
  super(pFable, tmpOptions, pServiceHash);
184
324
 
185
325
  this.serviceType = 'PictSectionFlow';
@@ -211,6 +351,7 @@ class PictViewFlow extends libPictView
211
351
  { ServiceType: 'PictProviderFlowNodeTypes', Library: libPictProviderFlowNodeTypes, Property: '_NodeTypeProvider', ExtraOptions: () => ({ AdditionalNodeTypes: this.options.NodeTypes, IncludeDefaultNodeTypes: this.options.IncludeDefaultNodeTypes }) },
212
352
  { ServiceType: 'PictProviderFlowEventHandler', Library: libPictProviderFlowEventHandler, Property: '_EventHandlerProvider' },
213
353
  { ServiceType: 'PictProviderFlowLayouts', Library: libPictProviderFlowLayouts, Property: '_LayoutProvider', PostInit: 'loadPersistedLayouts' },
354
+ { ServiceType: 'PictProviderFlowBackground', Library: libPictProviderFlowBackground, Property: '_BackgroundProvider' },
214
355
 
215
356
  // Services
216
357
  { ServiceType: 'PictServiceFlowPathGenerator', Library: libPictServiceFlowPathGenerator, Property: '_PathGenerator' },
@@ -219,6 +360,7 @@ class PictViewFlow extends libPictView
219
360
  { ServiceType: 'PictServiceFlowRenderManager', Library: libPictServiceFlowRenderManager, Property: '_RenderManager' },
220
361
  { ServiceType: 'PictServiceFlowPortRenderer', Library: libPictServiceFlowPortRenderer, Property: '_PortRenderer' },
221
362
  { ServiceType: 'PictServiceFlowInteractionManager', Library: libPictServiceFlowInteractionManager, Property: '_InteractionManager' },
363
+ { ServiceType: 'PictServiceFlowCursorManager', Library: libPictServiceFlowCursorManager, Property: '_CursorManager' },
222
364
  { ServiceType: 'PictServiceFlowConnectionRenderer', Library: libPictServiceFlowConnectionRenderer, Property: '_ConnectionRenderer' },
223
365
  { ServiceType: 'PictServiceFlowTether', Library: libPictServiceFlowTether, Property: '_TetherService' },
224
366
  { ServiceType: 'PictServiceFlowLayout', Library: libPictServiceFlowLayout, Property: '_LayoutService' },
@@ -285,6 +427,7 @@ class PictViewFlow extends libPictView
285
427
  this._PortRenderer = null;
286
428
 
287
429
  this._InteractionManager = null;
430
+ this._CursorManager = null;
288
431
  this._ConnectionRenderer = null;
289
432
  this._TetherService = null;
290
433
  this._LayoutService = null;
@@ -304,11 +447,26 @@ class PictViewFlow extends libPictView
304
447
  this._PanelChromeProvider = null;
305
448
  this._NodeTypeProvider = null;
306
449
  this._LayoutProvider = null;
450
+ this._BackgroundProvider = null;
307
451
  this._EventHandlerProvider = null;
308
452
  this._NodeView = null;
309
453
  this._ToolbarView = null;
310
454
  this._PropertiesPanelView = null;
311
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
+
312
470
  this.initialRenderComplete = false;
313
471
  }
314
472
 
@@ -324,6 +482,377 @@ class PictViewFlow extends libPictView
324
482
  }
325
483
  }
326
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
+
327
856
  _instantiateServices()
328
857
  {
329
858
  for (let i = 0; i < this._ServiceRegistry.length; i++)
@@ -502,12 +1031,24 @@ class PictViewFlow extends libPictView
502
1031
  }
503
1032
  this._SVGElement = tmpSVGElements[0];
504
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
+
505
1043
  let tmpViewportElements = this.pict.ContentAssignment.getElement(`#Flow-Viewport-${tmpViewIdentifier}`);
506
1044
  if (tmpViewportElements.length > 0)
507
1045
  {
508
1046
  this._ViewportElement = tmpViewportElements[0];
509
1047
  }
510
1048
 
1049
+ // Draw the content frame, if configured (needs the viewport <g>).
1050
+ this._applyFrame();
1051
+
511
1052
  let tmpNodesElements = this.pict.ContentAssignment.getElement(`#Flow-Nodes-${tmpViewIdentifier}`);
512
1053
  if (tmpNodesElements.length > 0)
513
1054
  {
@@ -596,6 +1137,8 @@ class PictViewFlow extends libPictView
596
1137
  this._NodeView = this.fable.instantiateServiceProviderWithoutRegistration('PictViewFlowNode',
597
1138
  Object.assign({}, libPictViewFlowNode.default_configuration, tmpNodeViewOptions));
598
1139
  this._NodeView._FlowView = this;
1140
+ // The default renderable renderer. Non-card renderers register alongside it.
1141
+ this._RenderableRenderers['card'] = this._NodeView;
599
1142
 
600
1143
  // Setup the properties panel renderer
601
1144
  this._PropertiesPanelView = this.fable.instantiateServiceProviderWithoutRegistration('PictViewFlowPropertiesPanel',
@@ -622,8 +1165,45 @@ class PictViewFlow extends libPictView
622
1165
 
623
1166
  // Render the initial flow
624
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
+ }
625
1191
  }
626
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
+
627
1207
  // ---- Data Manager Delegations ----
628
1208
 
629
1209
  marshalToView() { return this._DataManager.marshalToView(); }
@@ -1587,4 +2167,13 @@ class PictViewFlow extends libPictView
1587
2167
 
1588
2168
  module.exports = PictViewFlow;
1589
2169
 
1590
- 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;