pict-section-flow 0.0.16 → 0.0.18

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 (84) 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/_cover.md +1 -1
  9. package/docs/_sidebar.md +6 -6
  10. package/docs/_version.json +7 -0
  11. package/docs/api/PictFlowCard.md +6 -6
  12. package/docs/api/PictFlowCardPropertiesPanel.md +2 -2
  13. package/docs/api/addConnection.md +4 -4
  14. package/docs/api/addNode.md +6 -6
  15. package/docs/api/autoLayout.md +2 -2
  16. package/docs/api/getFlowData.md +5 -5
  17. package/docs/api/marshalToView.md +3 -3
  18. package/docs/api/openPanel.md +2 -2
  19. package/docs/api/registerHandler.md +3 -3
  20. package/docs/api/registerNodeType.md +3 -3
  21. package/docs/api/removeConnection.md +5 -5
  22. package/docs/api/removeNode.md +6 -6
  23. package/docs/api/saveLayout.md +2 -2
  24. package/docs/api/screenToSVGCoords.md +2 -2
  25. package/docs/api/selectNode.md +3 -3
  26. package/docs/api/setTheme.md +2 -2
  27. package/docs/api/setZoom.md +3 -3
  28. package/docs/api/toggleFullscreen.md +2 -2
  29. package/docs/card-help/EACH.md +3 -3
  30. package/docs/card-help/FREAD.md +5 -5
  31. package/docs/card-help/FWRITE.md +5 -5
  32. package/docs/card-help/GET.md +2 -2
  33. package/docs/card-help/ITE.md +3 -3
  34. package/docs/card-help/LOG.md +4 -4
  35. package/docs/card-help/NOTE.md +1 -1
  36. package/docs/card-help/PREV.md +2 -2
  37. package/docs/card-help/SET.md +5 -5
  38. package/docs/card-help/SPKL.md +2 -2
  39. package/docs/card-help/STAT.md +3 -3
  40. package/docs/card-help/SW.md +4 -4
  41. package/docs/css/docuserve.css +277 -23
  42. package/docs/index.html +2 -2
  43. package/docs/retold-catalog.json +1 -1
  44. package/docs/retold-keyword-index.json +1 -1
  45. package/example_applications/simple_cards/css/flowexample.css +2 -2
  46. package/example_applications/simple_cards/source/card-help-content.js +12 -12
  47. package/example_applications/simple_cards/source/cards/FlowCard-DataPreview.js +1 -1
  48. package/example_applications/simple_cards/source/sample-flows.js +410 -0
  49. package/example_applications/simple_cards/source/views/PictView-FlowExample-About.js +5 -5
  50. package/example_applications/simple_cards/source/views/PictView-FlowExample-Documentation.js +5 -5
  51. package/example_applications/simple_cards/source/views/PictView-FlowExample-FileWriteInfo.js +4 -4
  52. package/example_applications/simple_cards/source/views/PictView-FlowExample-MainWorkspace.js +141 -8
  53. package/example_applications/simple_cards/source/views/PictView-FlowExample-TopBar.js +2 -2
  54. package/package.json +3 -2
  55. package/source/Pict-Section-Flow.js +26 -0
  56. package/source/providers/PictProvider-Flow-CSS.js +244 -14
  57. package/source/providers/PictProvider-Flow-Theme.js +7 -7
  58. package/source/providers/edges/Edge-Bezier.js +41 -0
  59. package/source/providers/edges/Edge-Orthogonal.js +37 -0
  60. package/source/providers/edges/Edge-OrthogonalSnap.js +72 -0
  61. package/source/providers/edges/Edge-Perimeter-Linear.js +31 -0
  62. package/source/providers/edges/Edge-Perimeter-Orthogonal.js +39 -0
  63. package/source/providers/edges/Edge-Perimeter.js +48 -0
  64. package/source/providers/edges/Edge-PerimeterMath.js +92 -0
  65. package/source/providers/edges/Edge-Straight.js +24 -0
  66. package/source/providers/layouts/Layout-Circular.js +203 -0
  67. package/source/providers/layouts/Layout-Coerce.js +40 -0
  68. package/source/providers/layouts/Layout-Columnar.js +134 -0
  69. package/source/providers/layouts/Layout-Custom.js +27 -0
  70. package/source/providers/layouts/Layout-ForcedFromCenter.js +256 -0
  71. package/source/providers/layouts/Layout-Grid.js +134 -0
  72. package/source/providers/layouts/Layout-Layered.js +209 -0
  73. package/source/providers/layouts/Layout-Tabular.js +94 -0
  74. package/source/services/PictService-Flow-ConnectionRenderer.js +532 -28
  75. package/source/services/PictService-Flow-DataManager.js +12 -1
  76. package/source/services/PictService-Flow-Layout.js +305 -121
  77. package/source/services/PictService-Flow-PortRenderer.js +122 -26
  78. package/source/services/PictService-Flow-RenderManager.js +41 -11
  79. package/source/views/PictView-Flow-FloatingToolbar.js +3 -3
  80. package/source/views/PictView-Flow-Node.js +28 -0
  81. package/source/views/PictView-Flow-Toolbar.js +715 -10
  82. package/source/views/PictView-Flow.js +272 -5
  83. package/test/Layout_tests.js +1400 -0
  84. package/test/PortRenderer_tests.js +11 -2
@@ -1,5 +1,6 @@
1
1
  const libPictView = require('pict-view');
2
2
  const libPictSectionFlow = require('pict-section-flow');
3
+ const libSampleFlows = require('../sample-flows.js');
3
4
 
4
5
  // FlowCard definitions
5
6
  const libFlowCardIfThenElse = require('../cards/FlowCard-IfThenElse.js');
@@ -36,7 +37,7 @@ const _ViewConfiguration =
36
37
  flex-shrink: 0;
37
38
  margin: 0 0 0.75em 0;
38
39
  padding-bottom: 0.75em;
39
- border-bottom: 1px solid #eee;
40
+ border-bottom: 1px solid var(--theme-color-border-light, #eee);
40
41
  display: flex;
41
42
  align-items: flex-start;
42
43
  justify-content: space-between;
@@ -59,7 +60,7 @@ const _ViewConfiguration =
59
60
  height: 36px;
60
61
  border-radius: 50%;
61
62
  border: 2px solid #3498db;
62
- background: #fff;
63
+ background: var(--theme-color-background-panel, #fff);
63
64
  color: #3498db;
64
65
  font-size: 1.2em;
65
66
  font-weight: 700;
@@ -71,16 +72,57 @@ const _ViewConfiguration =
71
72
  }
72
73
  .flowexample-help-toggle:hover {
73
74
  background: #3498db;
74
- color: #fff;
75
+ color: var(--theme-color-background-panel, #fff);
75
76
  }
76
77
  .flowexample-help-toggle.active {
77
78
  background: #3498db;
78
- color: #fff;
79
+ color: var(--theme-color-background-panel, #fff);
79
80
  }
80
81
  #FlowExample-Flow-Container {
81
82
  flex: 1;
82
83
  min-height: 0;
83
84
  }
85
+ .flowexample-sample-bar {
86
+ flex-shrink: 0;
87
+ margin: 0 0 0.75em 0;
88
+ padding: 0.6em 0.75em;
89
+ background: var(--theme-color-background-panel, #fff);
90
+ border: 1px solid var(--theme-color-border-light, #dee2e6);
91
+ border-radius: 6px;
92
+ display: flex;
93
+ align-items: center;
94
+ gap: 0.6em;
95
+ flex-wrap: wrap;
96
+ }
97
+ .flowexample-sample-bar label {
98
+ font-weight: 600;
99
+ color: #2c3e50;
100
+ font-size: 0.9em;
101
+ }
102
+ .flowexample-sample-bar select {
103
+ padding: 0.35em 0.55em;
104
+ border: 1px solid #ced4da;
105
+ border-radius: 4px;
106
+ background: #fff;
107
+ font-size: 0.95em;
108
+ min-width: 220px;
109
+ }
110
+ .flowexample-sample-description {
111
+ flex: 1;
112
+ min-width: 280px;
113
+ color: #5a6470;
114
+ font-size: 0.85em;
115
+ line-height: 1.4;
116
+ }
117
+ .flowexample-sample-recommended {
118
+ padding: 0.2em 0.55em;
119
+ background: #eaf6ee;
120
+ color: #1f7a3f;
121
+ border-radius: 4px;
122
+ font-size: 0.8em;
123
+ font-weight: 600;
124
+ white-space: nowrap;
125
+ }
84
126
  .flowexample-help-panel {
85
127
  flex-shrink: 0;
86
128
  display: none;
@@ -105,8 +147,8 @@ const _ViewConfiguration =
105
147
  gap: 1em;
106
148
  }
107
149
  .flowexample-hint {
108
- background: #fff;
109
- border: 1px solid #e0e0e0;
150
+ background: var(--theme-color-background-panel, #fff);
151
+ border: 1px solid var(--theme-color-border-default, #e0e0e0);
110
152
  border-radius: 6px;
111
153
  padding: 1em 1.25em;
112
154
  }
@@ -117,7 +159,7 @@ const _ViewConfiguration =
117
159
  }
118
160
  .flowexample-hint p {
119
161
  margin: 0;
120
- color: #666;
162
+ color: var(--theme-color-text-secondary, #666);
121
163
  font-size: 0.85em;
122
164
  line-height: 1.5;
123
165
  }
@@ -126,7 +168,7 @@ const _ViewConfiguration =
126
168
  padding: 0.1em 0.3em;
127
169
  border-radius: 3px;
128
170
  font-size: 0.9em;
129
- color: #e74c3c;
171
+ color: var(--theme-color-status-error, #e74c3c);
130
172
  }
131
173
  `,
132
174
 
@@ -180,6 +222,12 @@ const _ViewConfiguration =
180
222
  </div>
181
223
  </div>
182
224
  </div>
225
+ <div class="flowexample-sample-bar">
226
+ <label for="FlowExample-SampleSelect">Sample graph:</label>
227
+ <select id="FlowExample-SampleSelect"></select>
228
+ <span class="flowexample-sample-recommended" id="FlowExample-SampleRecommended"></span>
229
+ <span class="flowexample-sample-description" id="FlowExample-SampleDescription">Pick a sample, then open the <strong>Algorithm</strong> popup in the toolbar to compare layouts.</span>
230
+ </div>
183
231
  <div id="FlowExample-Flow-Container"></div>
184
232
  </div>
185
233
  `
@@ -311,8 +359,93 @@ class FlowExampleMainWorkspaceView extends libPictView
311
359
  });
312
360
  }
313
361
 
362
+ // Populate the sample-graph selector and wire its change handler.
363
+ this._populateSampleSelector();
364
+
314
365
  return super.onAfterRender(pRenderable, pRenderDestinationAddress, pRecord, pContent);
315
366
  }
367
+
368
+ /**
369
+ * Populate the sample-graph dropdown above the flow diagram and wire
370
+ * the change handler. The first option ("Hello World") is the rich
371
+ * default flow that lives in AppData.FlowExample.SampleFlow; the rest
372
+ * come from sample-flows.js and showcase a different layout strength.
373
+ */
374
+ _populateSampleSelector()
375
+ {
376
+ let tmpSelect = document.getElementById('FlowExample-SampleSelect');
377
+ let tmpDesc = document.getElementById('FlowExample-SampleDescription');
378
+ let tmpReco = document.getElementById('FlowExample-SampleRecommended');
379
+ if (!tmpSelect || !tmpDesc || !tmpReco) return;
380
+
381
+ // Clear pre-existing options
382
+ while (tmpSelect.firstChild) tmpSelect.removeChild(tmpSelect.firstChild);
383
+
384
+ let tmpHelloOpt = document.createElement('option');
385
+ tmpHelloOpt.value = '__hello-world__';
386
+ tmpHelloOpt.textContent = 'Hello World — multi-feature reference';
387
+ tmpSelect.appendChild(tmpHelloOpt);
388
+
389
+ let tmpKeys = libSampleFlows.getSampleNames();
390
+ for (let i = 0; i < tmpKeys.length; i++)
391
+ {
392
+ let tmpSample = libSampleFlows.getSample(tmpKeys[i]);
393
+ let tmpOpt = document.createElement('option');
394
+ tmpOpt.value = tmpKeys[i];
395
+ tmpOpt.textContent = tmpSample.Name;
396
+ tmpSelect.appendChild(tmpOpt);
397
+ }
398
+
399
+ // Initial description (Hello World)
400
+ tmpDesc.innerHTML = 'Pick a sample, then open the <strong>Algorithm</strong> popup in the toolbar to compare layouts.';
401
+ tmpReco.style.display = 'none';
402
+
403
+ let tmpView = this;
404
+ tmpSelect.addEventListener('change', function ()
405
+ {
406
+ let tmpKey = tmpSelect.value;
407
+ tmpView._loadSample(tmpKey, tmpDesc, tmpReco);
408
+ });
409
+ }
410
+
411
+ /**
412
+ * Load a sample flow into the FlowView. `__hello-world__` reloads the
413
+ * original AppData-backed flow; everything else comes from sample-flows.
414
+ *
415
+ * @param {string} pKey
416
+ * @param {HTMLElement} pDescEl
417
+ * @param {HTMLElement} pRecoEl
418
+ */
419
+ _loadSample(pKey, pDescEl, pRecoEl)
420
+ {
421
+ if (!this._FlowView) return;
422
+
423
+ if (pKey === '__hello-world__')
424
+ {
425
+ this._FlowView.setFlowData(this.pict.AppData.FlowExample.SampleFlow);
426
+ pDescEl.innerHTML = 'The full reference flow with all card types, properties panels, and an error branch. Originally designed by hand — set <code>LayoutAlgorithm</code> to <em>Layered</em> to see how the auto-layout compares.';
427
+ pRecoEl.style.display = 'none';
428
+ return;
429
+ }
430
+
431
+ let tmpSample = libSampleFlows.getSample(pKey);
432
+ if (!tmpSample) return;
433
+
434
+ // setFlowData expects a fresh _FlowData-shaped object — deep clone so
435
+ // re-loading the same sample doesn't share mutated node references
436
+ // with prior loads.
437
+ this._FlowView.setFlowData(JSON.parse(JSON.stringify(tmpSample.Flow)));
438
+ pDescEl.textContent = tmpSample.Description;
439
+ if (tmpSample.Recommended)
440
+ {
441
+ pRecoEl.style.display = '';
442
+ pRecoEl.textContent = `Try: ${tmpSample.Recommended}`;
443
+ }
444
+ else
445
+ {
446
+ pRecoEl.style.display = 'none';
447
+ }
448
+ }
316
449
  }
317
450
 
318
451
  module.exports = FlowExampleMainWorkspaceView;
@@ -32,7 +32,7 @@ const _ViewConfiguration =
32
32
  cursor: pointer;
33
33
  }
34
34
  .flowexample-topbar-brand:hover {
35
- color: #fff;
35
+ color: var(--theme-color-background-panel, #fff);
36
36
  }
37
37
  .flowexample-topbar-nav {
38
38
  display: flex;
@@ -50,7 +50,7 @@ const _ViewConfiguration =
50
50
  }
51
51
  .flowexample-topbar-nav a:hover {
52
52
  background-color: #34495e;
53
- color: #fff;
53
+ color: var(--theme-color-background-panel, #fff);
54
54
  }
55
55
  `,
56
56
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pict-section-flow",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
4
4
  "description": "Pict Section Flow Diagram",
5
5
  "main": "source/Pict-Section-Flow.js",
6
6
  "scripts": {
@@ -22,7 +22,8 @@
22
22
  "chai": "^6.2.2",
23
23
  "mocha": "^11.7.5",
24
24
  "pict": "^1.0.359",
25
+ "pict-docuserve": "^0.1.5",
25
26
  "pict-router": "^1.0.9",
26
- "quackage": "^1.0.65"
27
+ "quackage": "^1.1.0"
27
28
  }
28
29
  }
@@ -35,6 +35,32 @@ module.exports.PictProviderFlowCSS = require('./providers/PictProvider-Flow-CSS.
35
35
  module.exports.PictProviderFlowIcons = require('./providers/PictProvider-Flow-Icons.js');
36
36
  module.exports.PictProviderFlowConnectorShapes = require('./providers/PictProvider-Flow-ConnectorShapes.js');
37
37
 
38
+ // Layout algorithm descriptors (consumers can register custom algorithms via
39
+ // _LayoutService.registerAlgorithm({ Name, Apply, DefaultParameters, ParameterSchema }))
40
+ module.exports.LayoutAlgorithms =
41
+ {
42
+ Custom: require('./providers/layouts/Layout-Custom.js'),
43
+ Layered: require('./providers/layouts/Layout-Layered.js'),
44
+ ForcedFromCenter: require('./providers/layouts/Layout-ForcedFromCenter.js'),
45
+ Grid: require('./providers/layouts/Layout-Grid.js'),
46
+ Circular: require('./providers/layouts/Layout-Circular.js'),
47
+ Tabular: require('./providers/layouts/Layout-Tabular.js'),
48
+ Columnar: require('./providers/layouts/Layout-Columnar.js')
49
+ };
50
+
51
+ // Edge-theme descriptors (consumers can register custom edge themes via
52
+ // _LayoutService.registerEdgeTheme({ Name, GeneratePath, AdjustLayout?, ResolveAttachment?, ... }))
53
+ module.exports.EdgeThemes =
54
+ {
55
+ Bezier: require('./providers/edges/Edge-Bezier.js'),
56
+ Orthogonal: require('./providers/edges/Edge-Orthogonal.js'),
57
+ Straight: require('./providers/edges/Edge-Straight.js'),
58
+ OrthogonalSnap: require('./providers/edges/Edge-OrthogonalSnap.js'),
59
+ Perimeter: require('./providers/edges/Edge-Perimeter.js'),
60
+ PerimeterLinear: require('./providers/edges/Edge-Perimeter-Linear.js'),
61
+ PerimeterOrthogonal: require('./providers/edges/Edge-Perimeter-Orthogonal.js')
62
+ };
63
+
38
64
  // FlowCard base class
39
65
  module.exports.PictFlowCard = require('./PictFlowCard.js');
40
66
 
@@ -47,7 +47,7 @@ class PictProviderFlowCSS extends libFableServiceProviderBase
47
47
  --pf-text-placeholder: #95a5a6;
48
48
 
49
49
  /* Node */
50
- --pf-node-body-fill: #ffffff;
50
+ --pf-node-body-fill: var(--theme-color-background-panel, #ffffff);
51
51
  --pf-node-body-stroke: #d0d4d8;
52
52
  --pf-node-body-stroke-hover: #b0b8c0;
53
53
  --pf-node-body-stroke-width: 1;
@@ -56,7 +56,7 @@ class PictProviderFlowCSS extends libFableServiceProviderBase
56
56
  --pf-node-shadow-hover: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.15));
57
57
  --pf-node-shadow-selected: drop-shadow(0 2px 8px rgba(52, 152, 219, 0.25));
58
58
  --pf-node-shadow-dragging: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.20));
59
- --pf-node-title-fill: #ffffff;
59
+ --pf-node-title-fill: var(--theme-color-background-panel, #ffffff);
60
60
  --pf-node-title-size: 11.5px;
61
61
  --pf-node-title-weight: 600;
62
62
  --pf-node-title-bar-color: #2c3e50;
@@ -65,35 +65,35 @@ class PictProviderFlowCSS extends libFableServiceProviderBase
65
65
 
66
66
  /* Node Variants */
67
67
  --pf-node-start-fill: #eafaf1;
68
- --pf-node-start-stroke: #27ae60;
68
+ --pf-node-start-stroke: var(--theme-color-status-success, #27ae60);
69
69
  --pf-node-end-fill: #e8f8f5;
70
70
  --pf-node-end-stroke: #1abc9c;
71
71
  --pf-node-halt-fill: #fdedec;
72
- --pf-node-halt-stroke: #e74c3c;
72
+ --pf-node-halt-stroke: var(--theme-color-status-error, #e74c3c);
73
73
  --pf-node-decision-fill: #fff9e6;
74
- --pf-node-decision-stroke: #f39c12;
74
+ --pf-node-decision-stroke: var(--theme-color-status-warning, #f39c12);
75
75
 
76
76
  /* Ports */
77
77
  --pf-port-input-fill: #3498db;
78
- --pf-port-output-fill: #2ecc71;
79
- --pf-port-stroke: #ffffff;
78
+ --pf-port-output-fill: var(--theme-color-status-success, #2ecc71);
79
+ --pf-port-stroke: var(--theme-color-background-panel, #ffffff);
80
80
  --pf-port-stroke-width: 2;
81
81
  --pf-port-label-bg: rgba(255, 253, 240, 0.5);
82
82
  --pf-port-label-text: #2c3e50;
83
83
 
84
84
  /* Port Type Colors */
85
85
  --pf-port-event-in-fill: #3498db;
86
- --pf-port-event-out-fill: #2ecc71;
86
+ --pf-port-event-out-fill: var(--theme-color-status-success, #2ecc71);
87
87
  --pf-port-setting-fill: #e67e22;
88
88
  --pf-port-value-fill: #f1c40f;
89
- --pf-port-error-fill: #e74c3c;
89
+ --pf-port-error-fill: var(--theme-color-status-error, #e74c3c);
90
90
 
91
91
  /* Connection Type Colors (match source port) */
92
92
  --pf-connection-event-in-stroke: #3498db;
93
- --pf-connection-event-out-stroke: #2ecc71;
93
+ --pf-connection-event-out-stroke: var(--theme-color-status-success, #2ecc71);
94
94
  --pf-connection-setting-stroke: #e67e22;
95
95
  --pf-connection-value-stroke: #f1c40f;
96
- --pf-connection-error-stroke: #e74c3c;
96
+ --pf-connection-error-stroke: var(--theme-color-status-error, #e74c3c);
97
97
 
98
98
  /* Connections */
99
99
  --pf-connection-stroke: #95a5a6;
@@ -141,7 +141,7 @@ class PictProviderFlowCSS extends libFableServiceProviderBase
141
141
 
142
142
  /* Toolbar */
143
143
  --pf-toolbar-bg: #ffffff;
144
- --pf-toolbar-border: #e0e0e0;
144
+ --pf-toolbar-border: var(--theme-color-border-default, #e0e0e0);
145
145
 
146
146
  /* Palette Cards */
147
147
  --pf-card-border: #d5d8dc;
@@ -150,7 +150,7 @@ class PictProviderFlowCSS extends libFableServiceProviderBase
150
150
 
151
151
  /* Canvas */
152
152
  --pf-canvas-bg: #fafafa;
153
- --pf-grid-stroke: #e8e8e8;
153
+ --pf-grid-stroke: var(--theme-color-border-light, #e8e8e8);
154
154
 
155
155
  position: relative;
156
156
  width: 100%;
@@ -396,6 +396,187 @@ class PictProviderFlowCSS extends libFableServiceProviderBase
396
396
  .pict-flow-node-port-labels-hover:hover .pict-flow-port-label-bg {
397
397
  opacity: 1;
398
398
  }
399
+
400
+ /* Port-hint beziers — drawn from the badge to the actual dot
401
+ when an edge theme has rerouted the connection. Hidden by
402
+ default; PortRenderer / NodeView toggle data-active on hover. */
403
+ .pict-flow-port-hint {
404
+ fill: none;
405
+ stroke: var(--pf-connection-stroke-hover, #3498db);
406
+ stroke-width: 1.75;
407
+ stroke-dasharray: 4 3;
408
+ stroke-linecap: round;
409
+ opacity: 0;
410
+ pointer-events: none;
411
+ transition: opacity 0.18s ease;
412
+ }
413
+ .pict-flow-port-hint[data-active="true"] {
414
+ opacity: 0.7;
415
+ }
416
+
417
+ /* ── Layout-algorithm popup: tightened layout + form styling ── */
418
+ .pict-flow-popup-layout-algorithm-row {
419
+ display: flex;
420
+ flex-direction: column;
421
+ gap: 4px;
422
+ padding: 6px 10px 4px;
423
+ }
424
+ .pict-flow-popup-layout-algorithm-row > .pict-flow-popup-settings-label {
425
+ font-size: 10px;
426
+ font-weight: 700;
427
+ text-transform: uppercase;
428
+ letter-spacing: 0.05em;
429
+ opacity: 0.7;
430
+ margin: 0;
431
+ }
432
+ .pict-flow-popup-layout-algorithm-controls {
433
+ display: flex;
434
+ align-items: stretch;
435
+ gap: 4px;
436
+ }
437
+ .pict-flow-popup-layout-algorithm-select {
438
+ flex: 1 1 auto;
439
+ min-width: 0;
440
+ }
441
+ .pict-flow-popup-collapse-toggle {
442
+ flex: 0 0 auto;
443
+ width: 28px;
444
+ padding: 0;
445
+ border: 1px solid var(--pf-button-border, #ccc);
446
+ border-radius: 3px;
447
+ background: var(--pf-toolbar-bg, #fff);
448
+ color: var(--pf-text-secondary, #5a6470);
449
+ cursor: pointer;
450
+ display: inline-flex;
451
+ align-items: center;
452
+ justify-content: center;
453
+ transition: background-color 0.15s, color 0.15s, border-color 0.15s;
454
+ }
455
+ .pict-flow-popup-collapse-toggle:hover {
456
+ background-color: var(--pf-button-hover-bg, #eef);
457
+ color: var(--pf-text-primary, #2c3e50);
458
+ }
459
+ /* "Open" state: pressed-in look so the user sees that the form
460
+ below is being driven by this gear. */
461
+ .pict-flow-popup-collapse-toggle[aria-expanded="true"] {
462
+ background-color: var(--pf-button-active-bg, #d6e4f0);
463
+ color: var(--pf-text-primary, #2c3e50);
464
+ border-color: var(--pf-button-hover-border, #3498db);
465
+ }
466
+ .pict-flow-popup-collapse-toggle svg {
467
+ display: block;
468
+ }
469
+ /* Subordinate description text — sits under a control (algorithm
470
+ dropdown, edge-theme dropdown, etc.) and explains it. Indented
471
+ from the section's left edge with a faint left rule so the
472
+ visual hierarchy is unambiguous: SECTION LABEL > control >
473
+ description. */
474
+ .pict-flow-popup-control-description {
475
+ font-size: 11px;
476
+ line-height: 1.4;
477
+ color: var(--pf-text-secondary, #5a6470);
478
+ padding: 4px 12px 8px 28px;
479
+ margin-left: 14px;
480
+ border-left: 2px solid var(--pf-divider-light, #e6e6e6);
481
+ }
482
+
483
+ /* The form host. Acts as the collapsible container around the
484
+ pict-section-form metacontroller's emitted markup. */
485
+ .pict-flow-popup-layout-form-host {
486
+ margin: 0 10px 8px;
487
+ padding: 8px 10px;
488
+ background: var(--pf-toolbar-bg, #fafafa);
489
+ border: 1px solid var(--pf-button-border, #e0e0e0);
490
+ border-radius: 6px;
491
+ overflow: hidden;
492
+ max-height: 600px;
493
+ transition: max-height 0.22s ease, padding 0.22s ease,
494
+ margin 0.22s ease, border-color 0.22s ease, opacity 0.18s ease;
495
+ opacity: 1;
496
+ }
497
+ .pict-flow-popup-layout-form-host[data-collapsed="true"] {
498
+ max-height: 0;
499
+ padding-top: 0;
500
+ padding-bottom: 0;
501
+ margin-top: 0;
502
+ margin-bottom: 0;
503
+ border-color: transparent;
504
+ opacity: 0;
505
+ }
506
+ .pict-flow-popup-layout-form-host > div { width: 100%; }
507
+
508
+ /* Form contents — make the auto-rendered sections look professional.
509
+ The section headings stay visible; the redundant per-section
510
+ "Group: Defaults" h3 is suppressed. */
511
+ .pict-flow-popup-layout-form .pict-form-view {
512
+ display: block;
513
+ }
514
+ .pict-flow-popup-layout-form .pict-form-section {
515
+ margin: 0;
516
+ padding: 0;
517
+ }
518
+ .pict-flow-popup-layout-form .pict-form-section h2 {
519
+ font-size: 10px;
520
+ font-weight: 700;
521
+ text-transform: uppercase;
522
+ letter-spacing: 0.05em;
523
+ opacity: 0.6;
524
+ margin: 8px 0 4px;
525
+ padding: 0;
526
+ border-bottom: 1px solid var(--pf-button-border, #e6e6e6);
527
+ padding-bottom: 3px;
528
+ }
529
+ .pict-flow-popup-layout-form .pict-form-section:first-child h2,
530
+ .pict-flow-popup-layout-form .pict-form-section h2:first-child {
531
+ margin-top: 0;
532
+ }
533
+ /* Group headings ("Group: Defaults") are noise — every algorithm
534
+ has exactly one group right now. Hide them. */
535
+ .pict-flow-popup-layout-form .pict-form-section h3 {
536
+ display: none;
537
+ }
538
+ /* Each row of inputs lives in a flat <div> as alternating
539
+ <span>label</span><input> pairs. Lay them out in a balanced grid. */
540
+ .pict-flow-popup-layout-form .pict-form-section > div > div {
541
+ display: flex;
542
+ flex-wrap: wrap;
543
+ gap: 4px 14px;
544
+ margin: 4px 0;
545
+ align-items: center;
546
+ }
547
+ .pict-flow-popup-layout-form .pict-form-section span {
548
+ font-size: 12px;
549
+ color: var(--pf-text-primary, #2c3e50);
550
+ line-height: 1.4;
551
+ margin: 0;
552
+ }
553
+ .pict-flow-popup-layout-form input[type="number"],
554
+ .pict-flow-popup-layout-form input[type="text"],
555
+ .pict-flow-popup-layout-form select {
556
+ width: 88px;
557
+ padding: 2px 6px;
558
+ border: 1px solid var(--pf-button-border, #ccc);
559
+ border-radius: 3px;
560
+ font-size: 12px;
561
+ line-height: 1.3;
562
+ background: #fff;
563
+ color: var(--pf-text-primary, #2c3e50);
564
+ margin-left: -8px; /* tighten gap between label and its input */
565
+ }
566
+ .pict-flow-popup-layout-form select {
567
+ width: auto;
568
+ min-width: 110px;
569
+ }
570
+ .pict-flow-popup-layout-form input[type="number"]:focus,
571
+ .pict-flow-popup-layout-form input[type="text"]:focus,
572
+ .pict-flow-popup-layout-form select:focus {
573
+ outline: none;
574
+ border-color: var(--pf-button-hover-border, #3498db);
575
+ box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.15);
576
+ }
577
+ .pict-flow-popup-layout-form input[type="checkbox"] {
578
+ margin: 0 4px 0 0;
579
+ }
399
580
  `;
400
581
  }
401
582
 
@@ -787,6 +968,26 @@ class PictProviderFlowCSS extends libFableServiceProviderBase
787
968
  cursor: pointer;
788
969
  flex: 0 0 28px;
789
970
  }
971
+
972
+ /* Suppress native number-input spinner buttons on every input
973
+ inside a properties panel. Panels live inside <foreignObject>;
974
+ browsers (Chromium / WebKit) render the up/down spinner chrome
975
+ as native overlays that don't always respect SVG transforms or
976
+ the parent tab pane's display:none — leaving stale spinner
977
+ artifacts hanging around when the user switches tabs or pans
978
+ the diagram. The user can still type numbers; they just don't
979
+ get clickable spinners. (If we ever want spinners back, build
980
+ them as real DOM elements next to the input, not native chrome.) */
981
+ .pict-flow-panel input[type="number"] {
982
+ -moz-appearance: textfield;
983
+ appearance: textfield;
984
+ }
985
+ .pict-flow-panel input[type="number"]::-webkit-outer-spin-button,
986
+ .pict-flow-panel input[type="number"]::-webkit-inner-spin-button {
987
+ -webkit-appearance: none;
988
+ appearance: none;
989
+ margin: 0;
990
+ }
790
991
  `;
791
992
  }
792
993
 
@@ -927,6 +1128,9 @@ class PictProviderFlowCSS extends libFableServiceProviderBase
927
1128
  user-select: none;
928
1129
  -webkit-user-select: none;
929
1130
  }
1131
+ .pict-flow-toolbar-btn:focus {
1132
+ outline: none;
1133
+ }
930
1134
  .pict-flow-toolbar-btn:hover {
931
1135
  background-color: var(--pf-button-hover-bg);
932
1136
  border-color: var(--pf-button-hover-border);
@@ -951,6 +1155,29 @@ class PictProviderFlowCSS extends libFableServiceProviderBase
951
1155
  align-items: center;
952
1156
  margin-left: 0.15em;
953
1157
  }
1158
+ /* Split button — visible as one connected unit; the two halves
1159
+ route to different actions. Used for "Auto" so clicking the
1160
+ icon/text applies the current layout while the chevron half
1161
+ opens the algorithm popup with a generous hit area. */
1162
+ .pict-flow-toolbar-btn-split {
1163
+ display: inline-flex;
1164
+ align-items: stretch;
1165
+ }
1166
+ .pict-flow-toolbar-btn-split-main {
1167
+ border-top-right-radius: 0;
1168
+ border-bottom-right-radius: 0;
1169
+ border-right: 1px solid var(--pf-button-border);
1170
+ margin-right: -1px; /* collapse the seam between the two buttons */
1171
+ }
1172
+ .pict-flow-toolbar-btn-split-chevron {
1173
+ border-top-left-radius: 0;
1174
+ border-bottom-left-radius: 0;
1175
+ padding-left: 0.55em;
1176
+ padding-right: 0.55em;
1177
+ }
1178
+ .pict-flow-toolbar-btn-split-chevron .pict-flow-toolbar-btn-chevron {
1179
+ margin: 0;
1180
+ }
954
1181
  .pict-flow-toolbar-right {
955
1182
  margin-left: auto;
956
1183
  border-right: none;
@@ -1072,7 +1299,7 @@ class PictProviderFlowCSS extends libFableServiceProviderBase
1072
1299
  border-radius: 6px;
1073
1300
  box-shadow: 0 4px 16px rgba(0,0,0,0.12);
1074
1301
  min-width: 240px;
1075
- max-height: 340px;
1302
+ max-height: 80vh;
1076
1303
  overflow-y: auto;
1077
1304
  padding: 0.35em 0;
1078
1305
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
@@ -1387,6 +1614,9 @@ class PictProviderFlowCSS extends libFableServiceProviderBase
1387
1614
  justify-content: center;
1388
1615
  transition: background-color 0.15s;
1389
1616
  }
1617
+ .pict-flow-floating-btn:focus {
1618
+ outline: none;
1619
+ }
1390
1620
  .pict-flow-floating-btn:hover {
1391
1621
  background-color: var(--pf-button-hover-bg);
1392
1622
  }
@@ -219,7 +219,7 @@ class PictProviderFlowTheme extends libFableServiceProviderBase
219
219
  .pict-flow-toolbar-btn {
220
220
  background-color: rgba(255,255,255,0.05);
221
221
  border-color: rgba(255,255,255,0.2);
222
- color: #ffffff;
222
+ color: var(--theme-color-background-panel, #ffffff);
223
223
  }
224
224
  .pict-flow-toolbar-btn:hover {
225
225
  background-color: rgba(255,255,255,0.1);
@@ -458,16 +458,16 @@ class PictProviderFlowTheme extends libFableServiceProviderBase
458
458
  .pict-flow-toolbar {
459
459
  background-color: #c0c0c0;
460
460
  border-bottom: 2px solid #808080;
461
- border-top: 1px solid #ffffff;
461
+ border-top: 1px solid var(--theme-color-background-panel, #ffffff);
462
462
  }
463
463
  .pict-flow-toolbar-btn {
464
464
  background-color: #c0c0c0;
465
465
  border: 2px outset #c0c0c0;
466
466
  border-radius: 0;
467
- color: #000000;
467
+ color: var(--theme-color-text-primary, #000000);
468
468
  }
469
469
  .pict-flow-toolbar-btn:hover {
470
- background-color: #d0d0d0;
470
+ background-color: var(--theme-color-border-default, #d0d0d0);
471
471
  }
472
472
  .pict-flow-toolbar-btn:active {
473
473
  border-style: inset;
@@ -544,10 +544,10 @@ class PictProviderFlowTheme extends libFableServiceProviderBase
544
544
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
545
545
  }
546
546
  /* Node-type bracket colors — each type gets its own bracket color */
547
- .pict-flow-node-start .pict-flow-node-bracket { stroke: #27ae60; }
547
+ .pict-flow-node-start .pict-flow-node-bracket { stroke: var(--theme-color-status-success, #27ae60); }
548
548
  .pict-flow-node-end .pict-flow-node-bracket { stroke: #1abc9c; }
549
- .pict-flow-node-halt .pict-flow-node-bracket { stroke: #e74c3c; }
550
- .pict-flow-node-decision .pict-flow-node-bracket { stroke: #f39c12; }
549
+ .pict-flow-node-halt .pict-flow-node-bracket { stroke: var(--theme-color-status-error, #e74c3c); }
550
+ .pict-flow-node-decision .pict-flow-node-bracket { stroke: var(--theme-color-status-warning, #f39c12); }
551
551
  .pict-flow-node-default .pict-flow-node-bracket { stroke: #3498db; }
552
552
  .pict-flow-node-action .pict-flow-node-bracket { stroke: #2c3e50; }
553
553
  /* Override variant rules: no fills/strokes on body rects in whiteboard */