pict-section-flow 0.0.17 → 0.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +18 -18
  2. package/docs/Architecture.md +1 -1
  3. package/docs/Data_Model.md +2 -2
  4. package/docs/Getting_Started.md +5 -5
  5. package/docs/Implementation_Reference.md +6 -6
  6. package/docs/Layout_Persistence.md +3 -3
  7. package/docs/README.md +12 -12
  8. package/docs/Theme_Integration.md +150 -0
  9. package/docs/_cover.md +1 -1
  10. package/docs/_sidebar.md +7 -6
  11. package/docs/_version.json +7 -0
  12. package/docs/api/PictFlowCard.md +6 -6
  13. package/docs/api/PictFlowCardPropertiesPanel.md +2 -2
  14. package/docs/api/addConnection.md +4 -4
  15. package/docs/api/addNode.md +6 -6
  16. package/docs/api/autoLayout.md +2 -2
  17. package/docs/api/getFlowData.md +5 -5
  18. package/docs/api/marshalToView.md +3 -3
  19. package/docs/api/openPanel.md +2 -2
  20. package/docs/api/registerHandler.md +3 -3
  21. package/docs/api/registerNodeType.md +3 -3
  22. package/docs/api/removeConnection.md +5 -5
  23. package/docs/api/removeNode.md +6 -6
  24. package/docs/api/saveLayout.md +2 -2
  25. package/docs/api/screenToSVGCoords.md +2 -2
  26. package/docs/api/selectNode.md +3 -3
  27. package/docs/api/setTheme.md +2 -2
  28. package/docs/api/setZoom.md +3 -3
  29. package/docs/api/toggleFullscreen.md +2 -2
  30. package/docs/card-help/EACH.md +3 -3
  31. package/docs/card-help/FREAD.md +5 -5
  32. package/docs/card-help/FWRITE.md +5 -5
  33. package/docs/card-help/GET.md +2 -2
  34. package/docs/card-help/ITE.md +3 -3
  35. package/docs/card-help/LOG.md +4 -4
  36. package/docs/card-help/NOTE.md +1 -1
  37. package/docs/card-help/PREV.md +2 -2
  38. package/docs/card-help/SET.md +5 -5
  39. package/docs/card-help/SPKL.md +2 -2
  40. package/docs/card-help/STAT.md +3 -3
  41. package/docs/card-help/SW.md +4 -4
  42. package/docs/css/docuserve.css +277 -23
  43. package/docs/index.html +2 -2
  44. package/docs/retold-catalog.json +1 -1
  45. package/docs/retold-keyword-index.json +1 -1
  46. package/example_applications/simple_cards/css/flowexample.css +2 -2
  47. package/example_applications/simple_cards/source/card-help-content.js +12 -12
  48. package/example_applications/simple_cards/source/cards/FlowCard-DataPreview.js +1 -1
  49. package/example_applications/simple_cards/source/sample-flows.js +410 -0
  50. package/example_applications/simple_cards/source/views/PictView-FlowExample-About.js +5 -5
  51. package/example_applications/simple_cards/source/views/PictView-FlowExample-Documentation.js +5 -5
  52. package/example_applications/simple_cards/source/views/PictView-FlowExample-FileWriteInfo.js +4 -4
  53. package/example_applications/simple_cards/source/views/PictView-FlowExample-MainWorkspace.js +141 -8
  54. package/example_applications/simple_cards/source/views/PictView-FlowExample-TopBar.js +2 -2
  55. package/package.json +8 -7
  56. package/source/Pict-Section-Flow.js +26 -0
  57. package/source/providers/PictProvider-Flow-CSS.js +435 -61
  58. package/source/providers/PictProvider-Flow-ConnectorShapes.js +9 -5
  59. package/source/providers/PictProvider-Flow-NodeTypes.js +10 -0
  60. package/source/providers/PictProvider-Flow-PanelChrome.js +7 -17
  61. package/source/providers/PictProvider-Flow-Theme.js +7 -7
  62. package/source/providers/edges/Edge-Bezier.js +41 -0
  63. package/source/providers/edges/Edge-Orthogonal.js +37 -0
  64. package/source/providers/edges/Edge-OrthogonalSnap.js +72 -0
  65. package/source/providers/edges/Edge-Perimeter-Linear.js +31 -0
  66. package/source/providers/edges/Edge-Perimeter-Orthogonal.js +39 -0
  67. package/source/providers/edges/Edge-Perimeter.js +48 -0
  68. package/source/providers/edges/Edge-PerimeterMath.js +92 -0
  69. package/source/providers/edges/Edge-Straight.js +24 -0
  70. package/source/providers/layouts/Layout-Circular.js +203 -0
  71. package/source/providers/layouts/Layout-Coerce.js +40 -0
  72. package/source/providers/layouts/Layout-Columnar.js +134 -0
  73. package/source/providers/layouts/Layout-Custom.js +27 -0
  74. package/source/providers/layouts/Layout-ForcedFromCenter.js +256 -0
  75. package/source/providers/layouts/Layout-Grid.js +134 -0
  76. package/source/providers/layouts/Layout-Layered.js +209 -0
  77. package/source/providers/layouts/Layout-Tabular.js +94 -0
  78. package/source/services/PictService-Flow-ConnectionRenderer.js +532 -28
  79. package/source/services/PictService-Flow-DataManager.js +12 -1
  80. package/source/services/PictService-Flow-InteractionManager.js +39 -42
  81. package/source/services/PictService-Flow-Layout.js +305 -121
  82. package/source/services/PictService-Flow-PortRenderer.js +108 -26
  83. package/source/services/PictService-Flow-RenderManager.js +41 -11
  84. package/source/views/PictView-Flow-FloatingToolbar.js +69 -61
  85. package/source/views/PictView-Flow-Node.js +47 -6
  86. package/source/views/PictView-Flow-PropertiesPanel.js +46 -53
  87. package/source/views/PictView-Flow-Toolbar.js +1065 -485
  88. package/source/views/PictView-Flow.js +455 -7
  89. package/test/Layout_tests.js +1400 -0
  90. package/test/PortRenderer_tests.js +11 -2
@@ -59,6 +59,7 @@ const _DefaultConfiguration =
59
59
  EnableConnectionCreation: true,
60
60
  EnableGridSnap: false,
61
61
  GridSnapSize: 20,
62
+ EnableLayoutMenu: true,
62
63
 
63
64
  MinZoom: 0.1,
64
65
  MaxZoom: 5.0,
@@ -68,16 +69,29 @@ const _DefaultConfiguration =
68
69
  DefaultNodeWidth: 180,
69
70
  DefaultNodeHeight: 80,
70
71
 
72
+ // Layout-algorithm subsystem defaults
73
+ DefaultLayoutAlgorithm: 'Custom',
74
+ DefaultLayoutParameters: {},
75
+ DefaultLayoutAutoApply: false,
76
+
77
+ // Edge-theme subsystem defaults — null = inherit from active layout's
78
+ // `DefaultEdgeTheme` field, with hard fallback to 'Bezier'.
79
+ DefaultEdgeTheme: null,
80
+ DefaultEdgeThemeParameters: {},
81
+
71
82
  CSS: false,
72
83
 
73
84
  Templates:
74
85
  [
75
86
  {
76
87
  Hash: 'Flow-PanelChrome-Template',
77
- 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>`
88
+ 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~}" onpointerdown="event.stopPropagation()" onwheel="event.stopPropagation()"><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~}" onpointerdown="event.stopPropagation()" onwheel="event.stopPropagation()"><div class="pict-flow-panel-tab active" data-tab-target="properties" data-panel-hash="{~D:Record.Hash~}" onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._handlePanelTabClick(this, event)">Properties</div><div class="pict-flow-panel-tab" data-tab-target="help" data-panel-hash="{~D:Record.Hash~}" style="display:none;" onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._handlePanelTabClick(this, event)">Help</div><div class="pict-flow-panel-tab" data-tab-target="appearance" data-panel-hash="{~D:Record.Hash~}" onclick="_Pict.views['{~D:Record.FlowViewIdentifier~}']._handlePanelTabClick(this, event)">Appearance</div></div></div>`
78
89
  },
79
90
  {
80
91
  Hash: 'Flow-Container-Template',
92
+ // Inline pointer/wheel/contextmenu handlers route to the
93
+ // InteractionManager via the FlowView. Keyboard events stay
94
+ // document-level (no element-level inline equivalent).
81
95
  Template: /*html*/`
82
96
  <div class="pict-flow-container" id="Flow-Wrapper-{~D:Record.ViewIdentifier~}">
83
97
  <div id="Flow-Toolbar-{~D:Record.ViewIdentifier~}"></div>
@@ -85,7 +99,13 @@ const _DefaultConfiguration =
85
99
  <div class="pict-flow-svg-container" id="Flow-SVG-Container-{~D:Record.ViewIdentifier~}">
86
100
  <svg class="pict-flow-svg"
87
101
  id="Flow-SVG-{~D:Record.ViewIdentifier~}"
88
- xmlns="http://www.w3.org/2000/svg">
102
+ xmlns="http://www.w3.org/2000/svg"
103
+ onpointerdown="_Pict.views['{~D:Record.ViewIdentifier~}']._handleSVGPointerDown(event)"
104
+ onpointermove="_Pict.views['{~D:Record.ViewIdentifier~}']._handleSVGPointerMove(event)"
105
+ onpointerup="_Pict.views['{~D:Record.ViewIdentifier~}']._handleSVGPointerUp(event)"
106
+ onpointerleave="_Pict.views['{~D:Record.ViewIdentifier~}']._handleSVGPointerUp(event)"
107
+ onwheel="_Pict.views['{~D:Record.ViewIdentifier~}']._handleSVGWheel(event)"
108
+ oncontextmenu="_Pict.views['{~D:Record.ViewIdentifier~}']._handleSVGContextMenu(event)">
89
109
  <defs>
90
110
  <pattern id="flow-grid-{~D:Record.ViewIdentifier~}"
91
111
  width="20" height="20" patternUnits="userSpaceOnUse">
@@ -99,6 +119,8 @@ const _DefaultConfiguration =
99
119
  <g class="pict-flow-viewport" id="Flow-Viewport-{~D:Record.ViewIdentifier~}">
100
120
  <g class="pict-flow-connections-layer" id="Flow-Connections-{~D:Record.ViewIdentifier~}"></g>
101
121
  <g class="pict-flow-nodes-layer" id="Flow-Nodes-{~D:Record.ViewIdentifier~}"></g>
122
+ <g class="pict-flow-endpoints-layer" id="Flow-Endpoints-{~D:Record.ViewIdentifier~}"></g>
123
+ <g class="pict-flow-port-hints-layer" id="Flow-PortHints-{~D:Record.ViewIdentifier~}"></g>
102
124
  <g class="pict-flow-tethers-layer" id="Flow-Tethers-{~D:Record.ViewIdentifier~}"></g>
103
125
  <g class="pict-flow-panels-layer" id="Flow-Panels-{~D:Record.ViewIdentifier~}"></g>
104
126
  </g>
@@ -195,13 +217,24 @@ class PictViewFlow extends libPictView
195
217
  SelectedNodeHash: null,
196
218
  SelectedConnectionHash: null,
197
219
  SelectedTetherHash: null
198
- }
220
+ },
221
+ LayoutAlgorithm: this.options.DefaultLayoutAlgorithm || 'Custom',
222
+ LayoutParameters: JSON.parse(JSON.stringify(this.options.DefaultLayoutParameters || {})),
223
+ LayoutAutoApply: !!this.options.DefaultLayoutAutoApply,
224
+ EdgeTheme: this.options.DefaultEdgeTheme || null,
225
+ EdgeThemeParameters: JSON.parse(JSON.stringify(this.options.DefaultEdgeThemeParameters || {}))
199
226
  };
200
227
 
228
+ // Re-entrancy guard for the auto-apply event handler
229
+ this._AutoApplyInProgress = false;
230
+ this._AutoApplyHandlerHashes = [];
231
+
201
232
  this._SVGElement = null;
202
233
  this._ViewportElement = null;
203
234
  this._NodesLayer = null;
204
235
  this._ConnectionsLayer = null;
236
+ this._EndpointsLayer = null;
237
+ this._PortHintsLayer = null;
205
238
  this._TethersLayer = null;
206
239
  this._PanelsLayer = null;
207
240
 
@@ -327,9 +360,65 @@ class PictViewFlow extends libPictView
327
360
  // Instantiate all remaining services (skips Theme + Noise since already set)
328
361
  this._instantiateServices();
329
362
 
363
+ // Subscribe to the host application's pict-provider-theme so the flow
364
+ // editor's marker arrowhead colors and shape overrides update when the
365
+ // host swaps light/dark or palette themes. CSS variables (--theme-*)
366
+ // re-resolve automatically — only the SVG <marker> defs (which inline
367
+ // fill colors at build time) and ConnectorShapesProvider state need a
368
+ // manual refresh.
369
+ this._subscribeToHostTheme();
370
+
330
371
  return super.onBeforeInitialize();
331
372
  }
332
373
 
374
+ /**
375
+ * If the host application has registered pict-provider-theme, subscribe
376
+ * to its onApply hook so we can refresh anything that doesn't pick up
377
+ * the new --theme-* CSS variables automatically (SVG marker fills,
378
+ * connector shape overrides). No-op when no host theme provider is
379
+ * available — the flow editor still functions with its built-in themes.
380
+ */
381
+ _subscribeToHostTheme()
382
+ {
383
+ let tmpHostTheme = this._resolveHostThemeProvider();
384
+ if (!tmpHostTheme || typeof tmpHostTheme.onApply !== 'function') return;
385
+
386
+ this._HostThemeUnsubscribe = tmpHostTheme.onApply(() =>
387
+ {
388
+ if (this._CSSProvider) this._CSSProvider.registerCSS();
389
+ this._reinjectMarkerDefs();
390
+ if (this.initialRenderComplete) this.renderFlow();
391
+ });
392
+ }
393
+
394
+ /**
395
+ * Locate the host's pict-provider-theme instance. It is normally
396
+ * registered under a stable service hash by the host app; we check
397
+ * common locations so consumers don't have to wire it manually.
398
+ * @returns {Object|null}
399
+ */
400
+ _resolveHostThemeProvider()
401
+ {
402
+ let tmpPict = this.pict;
403
+ if (!tmpPict) return null;
404
+ if (tmpPict.providers)
405
+ {
406
+ let tmpKeys = Object.keys(tmpPict.providers);
407
+ for (let i = 0; i < tmpKeys.length; i++)
408
+ {
409
+ let tmpProvider = tmpPict.providers[tmpKeys[i]];
410
+ if (tmpProvider
411
+ && typeof tmpProvider.onApply === 'function'
412
+ && typeof tmpProvider.applyTheme === 'function'
413
+ && typeof tmpProvider.listThemes === 'function')
414
+ {
415
+ return tmpProvider;
416
+ }
417
+ }
418
+ }
419
+ return null;
420
+ }
421
+
333
422
  onAfterRender(pRenderable, pRenderDestinationAddress, pRecord, pContent)
334
423
  {
335
424
  if (!this.initialRenderComplete)
@@ -371,6 +460,18 @@ class PictViewFlow extends libPictView
371
460
  this._ConnectionsLayer = tmpConnectionsElements[0];
372
461
  }
373
462
 
463
+ let tmpEndpointsElements = this.pict.ContentAssignment.getElement(`#Flow-Endpoints-${tmpViewIdentifier}`);
464
+ if (tmpEndpointsElements.length > 0)
465
+ {
466
+ this._EndpointsLayer = tmpEndpointsElements[0];
467
+ }
468
+
469
+ let tmpPortHintsElements = this.pict.ContentAssignment.getElement(`#Flow-PortHints-${tmpViewIdentifier}`);
470
+ if (tmpPortHintsElements.length > 0)
471
+ {
472
+ this._PortHintsLayer = tmpPortHintsElements[0];
473
+ }
474
+
374
475
  let tmpTethersElements = this.pict.ContentAssignment.getElement(`#Flow-Tethers-${tmpViewIdentifier}`);
375
476
  if (tmpTethersElements.length > 0)
376
477
  {
@@ -451,6 +552,9 @@ class PictViewFlow extends libPictView
451
552
  // Bind interaction events
452
553
  this._InteractionManager.initialize(this._SVGElement, this._ViewportElement);
453
554
 
555
+ // Wire the auto-apply handler to structural-change events
556
+ this._subscribeAutoApplyHandlers();
557
+
454
558
  // Load initial flow data if an address is configured
455
559
  if (this.options.FlowDataAddress)
456
560
  {
@@ -535,13 +639,104 @@ class PictViewFlow extends libPictView
535
639
  }
536
640
 
537
641
  /**
538
- * Apply auto-layout to all nodes
642
+ * Apply auto-layout to all nodes.
643
+ *
644
+ * Backwards-compatible signature: when called with no arguments,
645
+ * dispatches to the Layered algorithm with its default parameters
646
+ * (matching the pre-subsystem behavior).
647
+ *
648
+ * If `pAlgorithmName` is provided it is used directly; otherwise the
649
+ * configured `_FlowData.LayoutAlgorithm` is used (unless that value
650
+ * is 'Custom' or unset, in which case it falls back to 'Layered').
651
+ *
652
+ * @param {string} [pAlgorithmName]
653
+ * @param {Object} [pParameters]
539
654
  */
540
- autoLayout()
655
+ autoLayout(pAlgorithmName, pParameters)
541
656
  {
542
- if (this._LayoutService)
657
+ if (!this._LayoutService) return;
658
+
659
+ let tmpName;
660
+ let tmpParams;
661
+ if (typeof pAlgorithmName === 'string' && pAlgorithmName !== '')
543
662
  {
544
- this._LayoutService.autoLayout(this._FlowData.Nodes, this._FlowData.Connections);
663
+ tmpName = pAlgorithmName;
664
+ tmpParams = pParameters || {};
665
+ }
666
+ else
667
+ {
668
+ let tmpConfigured = this._FlowData.LayoutAlgorithm;
669
+ tmpName = (typeof tmpConfigured === 'string' && tmpConfigured !== '' && tmpConfigured !== 'Custom') ? tmpConfigured : 'Layered';
670
+ tmpParams = (tmpName === this._FlowData.LayoutAlgorithm) ? (this._FlowData.LayoutParameters || {}) : {};
671
+ }
672
+
673
+ this._LayoutService.autoLayout(this._FlowData.Nodes, this._FlowData.Connections, tmpName, tmpParams);
674
+ this.renderFlow();
675
+ this.marshalFromView();
676
+
677
+ if (this._EventHandlerProvider)
678
+ {
679
+ this._EventHandlerProvider.fireEvent('onFlowChanged', this._FlowData);
680
+ }
681
+ }
682
+
683
+ /**
684
+ * Apply the currently configured layout algorithm to the flow, then
685
+ * call the active edge theme's `AdjustLayout` callback (if any) to
686
+ * let the theme adjust node positions for cleaner edge rendering
687
+ * (e.g. snap to grid, optimize for crossing minimization).
688
+ *
689
+ * Reads `_FlowData.LayoutAlgorithm` / `LayoutParameters` /
690
+ * `EdgeTheme` / `EdgeThemeParameters`. No-op when algorithm is
691
+ * 'Custom' — but the edge theme's adjustment still runs (so themes
692
+ * like OrthogonalSnap can snap hand-placed nodes too).
693
+ */
694
+ applyCurrentLayout()
695
+ {
696
+ if (!this._LayoutService) return;
697
+ let tmpAlgorithm = this._FlowData.LayoutAlgorithm || 'Custom';
698
+
699
+ this._AutoApplyInProgress = true;
700
+ try
701
+ {
702
+ if (tmpAlgorithm !== 'Custom')
703
+ {
704
+ this._LayoutService.applyLayout(
705
+ this._FlowData.Nodes,
706
+ this._FlowData.Connections,
707
+ tmpAlgorithm,
708
+ this._FlowData.LayoutParameters || {}
709
+ );
710
+ }
711
+
712
+ // Edge-theme post-processing (placement adjustment).
713
+ let tmpEdgeTheme = this._LayoutService.resolveActiveEdgeTheme({});
714
+ if (tmpEdgeTheme && typeof tmpEdgeTheme.AdjustLayout === 'function')
715
+ {
716
+ try
717
+ {
718
+ let tmpEdgeParams = this._LayoutService.getMergedEdgeThemeParameters(
719
+ tmpEdgeTheme.Name,
720
+ this._FlowData.EdgeThemeParameters || {}
721
+ );
722
+ tmpEdgeTheme.AdjustLayout(this._FlowData.Nodes, this._FlowData.Connections, tmpEdgeParams);
723
+ }
724
+ catch (pError)
725
+ {
726
+ this.log.warn(`PictSectionFlow edge theme '${tmpEdgeTheme.Name}' AdjustLayout threw: ${pError.message}`);
727
+ }
728
+ }
729
+
730
+ let tmpHadAdjust = !!(tmpEdgeTheme && typeof tmpEdgeTheme.AdjustLayout === 'function');
731
+ let tmpDidLayout = (tmpAlgorithm !== 'Custom');
732
+
733
+ if (!tmpDidLayout && !tmpHadAdjust)
734
+ {
735
+ // Custom + theme-without-AdjustLayout → no positions changed.
736
+ // Don't render or fire onFlowChanged.
737
+ return;
738
+ }
739
+
545
740
  this.renderFlow();
546
741
  this.marshalFromView();
547
742
 
@@ -550,6 +745,143 @@ class PictViewFlow extends libPictView
550
745
  this._EventHandlerProvider.fireEvent('onFlowChanged', this._FlowData);
551
746
  }
552
747
  }
748
+ finally
749
+ {
750
+ this._AutoApplyInProgress = false;
751
+ }
752
+ }
753
+
754
+ /**
755
+ * Set the active edge theme (and optionally its parameters). Re-runs
756
+ * the current layout so any `AdjustLayout` callback takes effect.
757
+ *
758
+ * @param {string|null} pThemeName - null clears the override (theme
759
+ * then resolves via the active layout's `DefaultEdgeTheme`).
760
+ * @param {Object} [pParameters]
761
+ */
762
+ setEdgeTheme(pThemeName, pParameters)
763
+ {
764
+ if (!this._LayoutService) return;
765
+ if (pThemeName != null && typeof pThemeName !== 'string') return;
766
+
767
+ if (pThemeName)
768
+ {
769
+ let tmpTheme = this._LayoutService.getEdgeTheme(pThemeName);
770
+ if (!tmpTheme)
771
+ {
772
+ this.log.warn(`PictSectionFlow setEdgeTheme: unknown theme '${pThemeName}'`);
773
+ return;
774
+ }
775
+ this._FlowData.EdgeTheme = pThemeName;
776
+ this._FlowData.EdgeThemeParameters = Object.assign({}, tmpTheme.DefaultParameters || {}, pParameters || {});
777
+ }
778
+ else
779
+ {
780
+ this._FlowData.EdgeTheme = null;
781
+ this._FlowData.EdgeThemeParameters = pParameters ? Object.assign({}, pParameters) : {};
782
+ }
783
+
784
+ this.applyCurrentLayout();
785
+ }
786
+
787
+ /**
788
+ * Read the currently configured edge theme settings.
789
+ * Returns the *resolved* active theme (factoring in fallback to the
790
+ * active layout's `DefaultEdgeTheme`) plus the explicit override
791
+ * (if any) for round-tripping into UI.
792
+ *
793
+ * @returns {{ Theme: string, Override: string|null, Parameters: Object }}
794
+ */
795
+ getEdgeTheme()
796
+ {
797
+ let tmpResolved = this._LayoutService ? this._LayoutService.resolveActiveEdgeTheme({}) : null;
798
+ return {
799
+ Theme: tmpResolved ? tmpResolved.Name : null,
800
+ Override: this._FlowData.EdgeTheme || null,
801
+ Parameters: JSON.parse(JSON.stringify(this._FlowData.EdgeThemeParameters || {}))
802
+ };
803
+ }
804
+
805
+ /**
806
+ * Set the configured layout algorithm and (optionally) its parameters
807
+ * and auto-apply flag. Persists to `_FlowData` and applies the layout
808
+ * immediately when the algorithm is non-Custom.
809
+ *
810
+ * @param {string} pAlgorithmName
811
+ * @param {Object} [pParameters] - merged over the algorithm's defaults
812
+ * @param {boolean} [pAutoApply] - if provided, also sets `LayoutAutoApply`
813
+ */
814
+ setLayoutAlgorithm(pAlgorithmName, pParameters, pAutoApply)
815
+ {
816
+ if (!this._LayoutService) return;
817
+ if (typeof pAlgorithmName !== 'string' || pAlgorithmName === '') return;
818
+
819
+ let tmpAlgo = this._LayoutService.getAlgorithm(pAlgorithmName);
820
+ if (!tmpAlgo)
821
+ {
822
+ this.log.warn(`PictSectionFlow setLayoutAlgorithm: unknown algorithm '${pAlgorithmName}'`);
823
+ return;
824
+ }
825
+
826
+ this._FlowData.LayoutAlgorithm = pAlgorithmName;
827
+ this._FlowData.LayoutParameters = Object.assign({}, tmpAlgo.DefaultParameters || {}, pParameters || {});
828
+ if (typeof pAutoApply === 'boolean')
829
+ {
830
+ this._FlowData.LayoutAutoApply = pAutoApply;
831
+ }
832
+
833
+ // Always run applyCurrentLayout so the new layout's `DefaultEdgeTheme`
834
+ // takes effect on connection rendering — even when switching to
835
+ // 'Custom' (which doesn't reposition nodes but does re-render edges).
836
+ this.applyCurrentLayout();
837
+ this.renderFlow();
838
+ }
839
+
840
+ /**
841
+ * Toggle whether the configured layout re-applies on structural changes.
842
+ * @param {boolean} pAutoApply
843
+ */
844
+ setLayoutAutoApply(pAutoApply)
845
+ {
846
+ this._FlowData.LayoutAutoApply = !!pAutoApply;
847
+ this.marshalFromView();
848
+ }
849
+
850
+ /**
851
+ * Read the currently configured layout algorithm settings.
852
+ * @returns {{ Algorithm: string, Parameters: Object, AutoApply: boolean }}
853
+ */
854
+ getLayoutAlgorithm()
855
+ {
856
+ return {
857
+ Algorithm: this._FlowData.LayoutAlgorithm || 'Custom',
858
+ Parameters: JSON.parse(JSON.stringify(this._FlowData.LayoutParameters || {})),
859
+ AutoApply: !!this._FlowData.LayoutAutoApply
860
+ };
861
+ }
862
+
863
+ /**
864
+ * Subscribe the auto-apply handler to structural-change events. Idempotent.
865
+ * Only structural events trigger re-layout — `onNodeMoved` is intentionally
866
+ * NOT subscribed so user drags are never clobbered until the next add/remove.
867
+ */
868
+ _subscribeAutoApplyHandlers()
869
+ {
870
+ if (!this._EventHandlerProvider) return;
871
+ if (this._AutoApplyHandlerHashes.length > 0) return;
872
+
873
+ let tmpEvents = ['onNodeAdded', 'onNodeRemoved', 'onConnectionCreated', 'onConnectionRemoved'];
874
+ for (let i = 0; i < tmpEvents.length; i++)
875
+ {
876
+ let tmpHash = this._EventHandlerProvider.registerHandler(tmpEvents[i], () =>
877
+ {
878
+ if (this._AutoApplyInProgress) return;
879
+ if (!this._FlowData.LayoutAutoApply) return;
880
+ if (!this._FlowData.LayoutAlgorithm || this._FlowData.LayoutAlgorithm === 'Custom') return;
881
+ this.applyCurrentLayout();
882
+ });
883
+ if (tmpHash) this._AutoApplyHandlerHashes.push({ Event: tmpEvents[i], Hash: tmpHash });
884
+ }
553
885
  }
554
886
 
555
887
  /**
@@ -881,6 +1213,122 @@ class PictViewFlow extends libPictView
881
1213
  {
882
1214
  return this._PanelManager.updatePanelPosition(pPanelHash, pX, pY);
883
1215
  }
1216
+
1217
+ // ── Inline-handler bridges (called from template onclick/oninput attrs)
1218
+
1219
+ /**
1220
+ * Handle a click on a panel tab button. Stops propagation so the
1221
+ * underlying SVG doesn't see it, then delegates to the properties
1222
+ * panel view for the actual visibility toggle.
1223
+ *
1224
+ * @param {Element} pTabElement
1225
+ * @param {Event} pEvent
1226
+ */
1227
+ _handlePanelTabClick(pTabElement, pEvent)
1228
+ {
1229
+ if (pEvent && typeof pEvent.stopPropagation === 'function')
1230
+ {
1231
+ pEvent.stopPropagation();
1232
+ }
1233
+ if (this._PropertiesPanelView)
1234
+ {
1235
+ this._PropertiesPanelView.switchPanelTab(pTabElement);
1236
+ }
1237
+ }
1238
+
1239
+ /**
1240
+ * Apply a node property change driven from the appearance editor's
1241
+ * inline `oninput` handler. Stops propagation, then delegates to the
1242
+ * properties panel view.
1243
+ *
1244
+ * @param {string} pNodeHash
1245
+ * @param {string} pPropPath
1246
+ * @param {string} pValue
1247
+ * @param {string} pInputType - 'text' | 'number' | 'color'
1248
+ * @param {Event} [pEvent]
1249
+ */
1250
+ _applyNodePropChange(pNodeHash, pPropPath, pValue, pInputType, pEvent)
1251
+ {
1252
+ if (pEvent && typeof pEvent.stopPropagation === 'function')
1253
+ {
1254
+ pEvent.stopPropagation();
1255
+ }
1256
+ if (this._PropertiesPanelView)
1257
+ {
1258
+ this._PropertiesPanelView._applyNodePropChange(pNodeHash, pPropPath, pValue, pInputType);
1259
+ }
1260
+ }
1261
+
1262
+ /**
1263
+ * Inline-handler bridge — light up port-hint paths matching the given
1264
+ * port (and optionally node) hash. Called from inline `onmouseenter`
1265
+ * on port badges; see PictService-Flow-PortRenderer._wirePortHintHover.
1266
+ *
1267
+ * @param {string|null} pPortHash
1268
+ * @param {string|null} pNodeHash
1269
+ */
1270
+ _activatePortHints(pPortHash, pNodeHash)
1271
+ {
1272
+ this._togglePortHints(pPortHash, pNodeHash, true);
1273
+ }
1274
+
1275
+ /**
1276
+ * Inline-handler bridge — clear port-hint highlights.
1277
+ *
1278
+ * @param {string|null} pPortHash
1279
+ * @param {string|null} pNodeHash
1280
+ */
1281
+ _deactivatePortHints(pPortHash, pNodeHash)
1282
+ {
1283
+ this._togglePortHints(pPortHash, pNodeHash, false);
1284
+ }
1285
+
1286
+ _togglePortHints(pPortHash, pNodeHash, pActive)
1287
+ {
1288
+ let tmpScope = this._SVGElement || document;
1289
+ let tmpSelector = pPortHash
1290
+ ? '.pict-flow-port-hint[data-port-hash="' + pPortHash + '"]'
1291
+ : '.pict-flow-port-hint[data-node-hash="' + pNodeHash + '"]';
1292
+ let tmpHints = tmpScope.querySelectorAll(tmpSelector);
1293
+ for (let i = 0; i < tmpHints.length; i++)
1294
+ {
1295
+ if (pActive)
1296
+ {
1297
+ tmpHints[i].setAttribute('data-active', 'true');
1298
+ }
1299
+ else
1300
+ {
1301
+ tmpHints[i].removeAttribute('data-active');
1302
+ }
1303
+ }
1304
+ }
1305
+
1306
+ // ── SVG inline pointer/wheel bridges ─────────────────────────────────
1307
+
1308
+ _handleSVGPointerDown(pEvent)
1309
+ {
1310
+ if (this._InteractionManager) this._InteractionManager._onPointerDown(pEvent);
1311
+ }
1312
+
1313
+ _handleSVGPointerMove(pEvent)
1314
+ {
1315
+ if (this._InteractionManager) this._InteractionManager._onPointerMove(pEvent);
1316
+ }
1317
+
1318
+ _handleSVGPointerUp(pEvent)
1319
+ {
1320
+ if (this._InteractionManager) this._InteractionManager._onPointerUp(pEvent);
1321
+ }
1322
+
1323
+ _handleSVGWheel(pEvent)
1324
+ {
1325
+ if (this._InteractionManager) this._InteractionManager._onWheel(pEvent);
1326
+ }
1327
+
1328
+ _handleSVGContextMenu(pEvent)
1329
+ {
1330
+ if (this._InteractionManager) this._InteractionManager.handleContextMenu(pEvent);
1331
+ }
884
1332
  }
885
1333
 
886
1334
  module.exports = PictViewFlow;