pict-section-flow 0.0.2 → 0.0.3

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 (38) hide show
  1. package/.claude/launch.json +11 -0
  2. package/docs/README.md +51 -0
  3. package/example_applications/simple_cards/source/Pict-Application-FlowExample.js +105 -0
  4. package/example_applications/simple_cards/source/cards/FlowCard-Comment.js +36 -0
  5. package/example_applications/simple_cards/source/cards/FlowCard-DataPreview.js +42 -0
  6. package/example_applications/simple_cards/source/cards/FlowCard-Each.js +1 -1
  7. package/example_applications/simple_cards/source/cards/FlowCard-FileRead.js +1 -1
  8. package/example_applications/simple_cards/source/cards/FlowCard-FileWrite.js +1 -1
  9. package/example_applications/simple_cards/source/cards/FlowCard-GetValue.js +1 -1
  10. package/example_applications/simple_cards/source/cards/FlowCard-IfThenElse.js +1 -1
  11. package/example_applications/simple_cards/source/cards/FlowCard-LogValues.js +1 -1
  12. package/example_applications/simple_cards/source/cards/FlowCard-SetValue.js +1 -1
  13. package/example_applications/simple_cards/source/cards/FlowCard-Sparkline.js +98 -0
  14. package/example_applications/simple_cards/source/cards/FlowCard-StatusMonitor.js +44 -0
  15. package/example_applications/simple_cards/source/cards/FlowCard-Switch.js +1 -1
  16. package/example_applications/simple_cards/source/views/PictView-FlowExample-MainWorkspace.js +9 -1
  17. package/package.json +2 -2
  18. package/source/Pict-Section-Flow.js +8 -1
  19. package/source/PictFlowCard.js +49 -1
  20. package/source/providers/PictProvider-Flow-CSS.js +1440 -0
  21. package/source/providers/PictProvider-Flow-ConnectorShapes.js +413 -0
  22. package/source/providers/PictProvider-Flow-Geometry.js +43 -0
  23. package/source/providers/PictProvider-Flow-Icons.js +335 -0
  24. package/source/providers/PictProvider-Flow-Layouts.js +214 -2
  25. package/source/providers/PictProvider-Flow-NodeTypes.js +30 -7
  26. package/source/providers/PictProvider-Flow-Noise.js +241 -0
  27. package/source/providers/PictProvider-Flow-PanelChrome.js +19 -0
  28. package/source/providers/PictProvider-Flow-Theme.js +755 -0
  29. package/source/services/PictService-Flow-ConnectionRenderer.js +95 -32
  30. package/source/services/PictService-Flow-PanelManager.js +188 -0
  31. package/source/services/PictService-Flow-SelectionManager.js +109 -0
  32. package/source/services/PictService-Flow-Tether.js +52 -25
  33. package/source/services/PictService-Flow-ViewportManager.js +176 -0
  34. package/source/views/PictView-Flow-FloatingToolbar.js +352 -0
  35. package/source/views/PictView-Flow-Node.js +654 -169
  36. package/source/views/PictView-Flow-PropertiesPanel.js +176 -1
  37. package/source/views/PictView-Flow-Toolbar.js +846 -379
  38. package/source/views/PictView-Flow.js +279 -671
@@ -0,0 +1,1440 @@
1
+ const libFableServiceProviderBase = require('fable-serviceproviderbase');
2
+
3
+ /**
4
+ * PictProvider-Flow-CSS
5
+ *
6
+ * Centralized CSS provider for the flow diagram.
7
+ * All flow-related CSS is organized into domain-specific getter methods,
8
+ * providing a single source of truth and enabling future theming.
9
+ */
10
+
11
+ const _ProviderConfiguration =
12
+ {
13
+ ProviderIdentifier: 'PictProviderFlowCSS'
14
+ };
15
+
16
+ class PictProviderFlowCSS extends libFableServiceProviderBase
17
+ {
18
+ constructor(pFable, pOptions, pServiceHash)
19
+ {
20
+ let tmpOptions = Object.assign({}, _ProviderConfiguration, pOptions);
21
+ super(pFable, tmpOptions, pServiceHash);
22
+
23
+ this.serviceType = 'PictProviderFlowCSS';
24
+
25
+ this._FlowView = (pOptions && pOptions.FlowView) ? pOptions.FlowView : null;
26
+ }
27
+
28
+ // ── Container ──────────────────────────────────────────────────────────
29
+ /**
30
+ * CSS for the flow container, SVG container, panning/connecting cursors, and grid pattern.
31
+ * @returns {string}
32
+ */
33
+ getContainerCSS()
34
+ {
35
+ return /*css*/`
36
+ .pict-flow-container {
37
+ /* ── Design Tokens ─────────────────────────────────────
38
+ Override these custom properties to theme the flow diagram.
39
+ Node-type classes (.pict-flow-node-{type}) can scope-override
40
+ any variable for per-type variation. */
41
+
42
+ /* Node */
43
+ --pf-node-body-fill: #ffffff;
44
+ --pf-node-body-stroke: #d0d4d8;
45
+ --pf-node-body-stroke-width: 1;
46
+ --pf-node-body-radius: 8px;
47
+ --pf-node-shadow: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.10));
48
+ --pf-node-shadow-hover: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.15));
49
+ --pf-node-shadow-selected: drop-shadow(0 2px 8px rgba(52, 152, 219, 0.25));
50
+ --pf-node-shadow-dragging: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.20));
51
+ --pf-node-title-fill: #ffffff;
52
+ --pf-node-title-size: 11.5px;
53
+ --pf-node-title-weight: 600;
54
+ --pf-node-title-bar-color: #2c3e50;
55
+ --pf-node-type-label-fill: #a0a8b0;
56
+ --pf-node-selected-stroke: #3498db;
57
+
58
+ /* Ports */
59
+ --pf-port-input-fill: #3498db;
60
+ --pf-port-output-fill: #2ecc71;
61
+ --pf-port-stroke: #ffffff;
62
+ --pf-port-stroke-width: 2;
63
+
64
+ /* Panels */
65
+ --pf-panel-bg: #ffffff;
66
+ --pf-panel-border: #d0d4d8;
67
+ --pf-panel-radius: 8px;
68
+ --pf-panel-shadow: 0 4px 12px rgba(0,0,0,0.10), 0 1px 3px rgba(0,0,0,0.06);
69
+ --pf-panel-titlebar-bg: #f7f8fa;
70
+ --pf-panel-titlebar-border: #e8eaed;
71
+ --pf-panel-title-color: #2c3e50;
72
+
73
+ /* Connections */
74
+ --pf-connection-stroke: #95a5a6;
75
+ --pf-connection-selected-stroke: #3498db;
76
+
77
+ /* Canvas */
78
+ --pf-canvas-bg: #fafafa;
79
+ --pf-grid-stroke: #e8e8e8;
80
+
81
+ position: relative;
82
+ width: 100%;
83
+ height: 100%;
84
+ min-height: 400px;
85
+ overflow: hidden;
86
+ background-color: var(--pf-canvas-bg);
87
+ border: 1px solid #e0e0e0;
88
+ border-radius: 4px;
89
+ display: flex;
90
+ flex-direction: column;
91
+ }
92
+ .pict-flow-svg-container {
93
+ flex: 1;
94
+ min-height: 0;
95
+ position: relative;
96
+ }
97
+ .pict-flow-svg {
98
+ width: 100%;
99
+ height: 100%;
100
+ cursor: grab;
101
+ user-select: none;
102
+ -webkit-user-select: none;
103
+ }
104
+ .pict-flow-svg.panning {
105
+ cursor: grabbing;
106
+ }
107
+ .pict-flow-svg.connecting {
108
+ cursor: crosshair;
109
+ }
110
+ .pict-flow-grid-pattern line {
111
+ stroke: var(--pf-grid-stroke);
112
+ stroke-width: 0.5;
113
+ }
114
+ `;
115
+ }
116
+
117
+ // ── Nodes ──────────────────────────────────────────────────────────────
118
+ /**
119
+ * CSS for base node styling: body, hover/selected/dragging states, title bar, title text, type label.
120
+ * @returns {string}
121
+ */
122
+ getNodeCSS()
123
+ {
124
+ return /*css*/`
125
+ .pict-flow-node {
126
+ cursor: pointer;
127
+ filter: var(--pf-node-shadow);
128
+ transition: filter 0.2s;
129
+ }
130
+ .pict-flow-node:hover {
131
+ filter: var(--pf-node-shadow-hover);
132
+ }
133
+ .pict-flow-node:hover .pict-flow-node-body {
134
+ stroke: #b0b8c0;
135
+ stroke-width: 1.5;
136
+ }
137
+ .pict-flow-node.selected {
138
+ filter: var(--pf-node-shadow-selected);
139
+ }
140
+ .pict-flow-node.selected .pict-flow-node-body {
141
+ stroke: var(--pf-node-selected-stroke);
142
+ stroke-width: 2;
143
+ }
144
+ .pict-flow-node.dragging {
145
+ opacity: 0.9;
146
+ cursor: grabbing;
147
+ filter: var(--pf-node-shadow-dragging);
148
+ }
149
+ .pict-flow-node-body {
150
+ fill: var(--pf-node-body-fill);
151
+ stroke: var(--pf-node-body-stroke);
152
+ stroke-width: var(--pf-node-body-stroke-width);
153
+ rx: var(--pf-node-body-radius);
154
+ ry: var(--pf-node-body-radius);
155
+ transition: stroke 0.2s, stroke-width 0.2s;
156
+ }
157
+ .pict-flow-node-title-bar {
158
+ fill: var(--pf-node-title-bar-color);
159
+ rx: var(--pf-node-body-radius);
160
+ ry: var(--pf-node-body-radius);
161
+ }
162
+ .pict-flow-node-title-bar-bottom {
163
+ fill: var(--pf-node-title-bar-color);
164
+ }
165
+ .pict-flow-node-title {
166
+ fill: var(--pf-node-title-fill);
167
+ font-size: var(--pf-node-title-size);
168
+ font-weight: var(--pf-node-title-weight);
169
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
170
+ letter-spacing: 0.2px;
171
+ pointer-events: none;
172
+ }
173
+ .pict-flow-node-type-label {
174
+ fill: var(--pf-node-type-label-fill);
175
+ font-size: 9.5px;
176
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
177
+ text-transform: uppercase;
178
+ letter-spacing: 0.3px;
179
+ pointer-events: none;
180
+ opacity: 0;
181
+ transition: opacity 0.2s;
182
+ }
183
+ .pict-flow-node:hover .pict-flow-node-type-label {
184
+ opacity: 1;
185
+ }
186
+ .pict-flow-node-card-code {
187
+ opacity: 0;
188
+ transition: opacity 0.2s;
189
+ }
190
+ .pict-flow-node:hover .pict-flow-node-card-code {
191
+ opacity: 1;
192
+ }
193
+ /* Title-bar icon: invert SVG paths to white for dark title bars */
194
+ .pict-flow-node-title-icon {
195
+ filter: brightness(0) invert(1);
196
+ }
197
+ `;
198
+ }
199
+
200
+ // ── Body Content ──────────────────────────────────────────────────────
201
+ /**
202
+ * CSS for custom body content in nodes: SVG group, foreignObject, HTML container, canvas.
203
+ * @returns {string}
204
+ */
205
+ getBodyContentCSS()
206
+ {
207
+ return /*css*/`
208
+ .pict-flow-node-body-content {
209
+ pointer-events: none;
210
+ }
211
+ .pict-flow-node-body-content-fo {
212
+ overflow: hidden;
213
+ }
214
+ .pict-flow-node-body-content-html {
215
+ overflow: hidden;
216
+ width: 100%;
217
+ height: 100%;
218
+ box-sizing: border-box;
219
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
220
+ font-size: 11px;
221
+ color: #2c3e50;
222
+ pointer-events: auto;
223
+ }
224
+ .pict-flow-node-body-content-canvas {
225
+ display: block;
226
+ pointer-events: auto;
227
+ }
228
+ `;
229
+ }
230
+
231
+ // ── Node Variants ──────────────────────────────────────────────────────
232
+ /**
233
+ * CSS overrides for specific node types: start, end, halt, decision.
234
+ * @returns {string}
235
+ */
236
+ getNodeVariantCSS()
237
+ {
238
+ return /*css*/`
239
+ .pict-flow-node-decision .pict-flow-node-body {
240
+ fill: #fff9e6;
241
+ stroke: #f39c12;
242
+ stroke-width: 1.5;
243
+ }
244
+ .pict-flow-node-start .pict-flow-node-body {
245
+ fill: #eafaf1;
246
+ stroke: #27ae60;
247
+ stroke-width: 1.5;
248
+ }
249
+ .pict-flow-node-end .pict-flow-node-body {
250
+ fill: #e8f8f5;
251
+ stroke: #1abc9c;
252
+ stroke-width: 1.5;
253
+ }
254
+ .pict-flow-node-halt .pict-flow-node-body {
255
+ fill: #fdedec;
256
+ stroke: #e74c3c;
257
+ stroke-width: 1.5;
258
+ }
259
+ `;
260
+ }
261
+
262
+ // ── Ports ──────────────────────────────────────────────────────────────
263
+ /**
264
+ * CSS for port circles: input/output coloring, hover states, labels.
265
+ * @returns {string}
266
+ */
267
+ getPortCSS()
268
+ {
269
+ return /*css*/`
270
+ .pict-flow-port {
271
+ cursor: crosshair;
272
+ transition: r 0.15s, filter 0.15s;
273
+ filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.12));
274
+ }
275
+ .pict-flow-port.input {
276
+ fill: var(--pf-port-input-fill);
277
+ stroke: var(--pf-port-stroke);
278
+ stroke-width: var(--pf-port-stroke-width);
279
+ }
280
+ .pict-flow-port.output {
281
+ fill: var(--pf-port-output-fill);
282
+ stroke: var(--pf-port-stroke);
283
+ stroke-width: var(--pf-port-stroke-width);
284
+ }
285
+ .pict-flow-port:hover {
286
+ r: 7;
287
+ filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.20));
288
+ }
289
+ .pict-flow-port-label {
290
+ fill: #7f8c8d;
291
+ font-size: 9px;
292
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
293
+ pointer-events: none;
294
+ }
295
+ /* Port labels on hover: hidden by default, revealed on node hover */
296
+ .pict-flow-node-port-labels-hover .pict-flow-port-label {
297
+ opacity: 0;
298
+ transition: opacity 0.2s;
299
+ }
300
+ .pict-flow-node-port-labels-hover:hover .pict-flow-port-label {
301
+ opacity: 1;
302
+ }
303
+ `;
304
+ }
305
+
306
+ // ── Connections ────────────────────────────────────────────────────────
307
+ /**
308
+ * CSS for connection paths: base, hover/selected states, hitarea, drag-connection.
309
+ * @returns {string}
310
+ */
311
+ getConnectionCSS()
312
+ {
313
+ return /*css*/`
314
+ .pict-flow-connection {
315
+ fill: none;
316
+ stroke: var(--pf-connection-stroke);
317
+ stroke-width: 2;
318
+ cursor: pointer;
319
+ transition: stroke 0.15s;
320
+ }
321
+ .pict-flow-connection:hover {
322
+ stroke: #7f8c8d;
323
+ stroke-width: 3;
324
+ }
325
+ .pict-flow-connection.selected {
326
+ stroke: var(--pf-connection-selected-stroke);
327
+ stroke-width: 3;
328
+ }
329
+ .pict-flow-connection-hitarea {
330
+ fill: none;
331
+ stroke: transparent;
332
+ stroke-width: 12;
333
+ cursor: pointer;
334
+ }
335
+ .pict-flow-drag-connection {
336
+ fill: none;
337
+ stroke: #3498db;
338
+ stroke-width: 2;
339
+ stroke-dasharray: 6 3;
340
+ pointer-events: none;
341
+ }
342
+ `;
343
+ }
344
+
345
+ // ── Connection Handles ─────────────────────────────────────────────────
346
+ /**
347
+ * CSS for connection waypoint handles and midpoint handles.
348
+ * @returns {string}
349
+ */
350
+ getHandleCSS()
351
+ {
352
+ return /*css*/`
353
+ .pict-flow-connection-handle {
354
+ fill: #ffffff;
355
+ stroke: #3498db;
356
+ stroke-width: 2;
357
+ cursor: grab;
358
+ transition: r 0.15s;
359
+ filter: drop-shadow(0 1px 2px rgba(0,0,0,0.2));
360
+ }
361
+ .pict-flow-connection-handle:hover {
362
+ r: 8;
363
+ stroke-width: 2.5;
364
+ }
365
+ .pict-flow-connection-handle-midpoint {
366
+ fill: #ffffff;
367
+ stroke: #e67e22;
368
+ stroke-width: 2;
369
+ cursor: grab;
370
+ transition: r 0.15s;
371
+ filter: drop-shadow(0 1px 2px rgba(0,0,0,0.2));
372
+ }
373
+ .pict-flow-connection-handle-midpoint:hover {
374
+ r: 8;
375
+ stroke-width: 2.5;
376
+ }
377
+ `;
378
+ }
379
+
380
+ // ── Tethers ────────────────────────────────────────────────────────────
381
+ /**
382
+ * CSS for tether lines, hitareas, handles, and midpoint handles.
383
+ * @returns {string}
384
+ */
385
+ getTetherCSS()
386
+ {
387
+ return /*css*/`
388
+ .pict-flow-tether-line {
389
+ fill: none;
390
+ stroke: #95a5a6;
391
+ stroke-width: 1.5;
392
+ stroke-dasharray: 6 4;
393
+ pointer-events: visibleStroke;
394
+ cursor: pointer;
395
+ }
396
+ .pict-flow-tether-line.selected {
397
+ stroke: #3498db;
398
+ stroke-width: 2;
399
+ }
400
+ .pict-flow-tether-hitarea {
401
+ fill: none;
402
+ stroke: transparent;
403
+ stroke-width: 10;
404
+ cursor: pointer;
405
+ }
406
+ .pict-flow-tether-handle {
407
+ fill: #ffffff;
408
+ stroke: #3498db;
409
+ stroke-width: 2;
410
+ cursor: grab;
411
+ transition: r 0.15s;
412
+ filter: drop-shadow(0 1px 2px rgba(0,0,0,0.2));
413
+ }
414
+ .pict-flow-tether-handle:hover {
415
+ r: 8;
416
+ stroke-width: 2.5;
417
+ }
418
+ .pict-flow-tether-handle-midpoint {
419
+ fill: #ffffff;
420
+ stroke: #e67e22;
421
+ stroke-width: 2;
422
+ cursor: grab;
423
+ transition: r 0.15s;
424
+ filter: drop-shadow(0 1px 2px rgba(0,0,0,0.2));
425
+ }
426
+ .pict-flow-tether-handle-midpoint:hover {
427
+ r: 8;
428
+ stroke-width: 2.5;
429
+ }
430
+ `;
431
+ }
432
+
433
+ // ── Panels ─────────────────────────────────────────────────────────────
434
+ /**
435
+ * CSS for property panels: foreign object, panel container, titlebar, close button, body, indicator.
436
+ * @returns {string}
437
+ */
438
+ getPanelCSS()
439
+ {
440
+ return /*css*/`
441
+ .pict-flow-node-panel-indicator {
442
+ fill: var(--pf-node-selected-stroke);
443
+ stroke: none;
444
+ opacity: 0.6;
445
+ cursor: pointer;
446
+ transition: opacity 0.15s;
447
+ }
448
+ .pict-flow-node-panel-indicator:hover {
449
+ opacity: 1.0;
450
+ }
451
+ .pict-flow-panel-foreign-object {
452
+ overflow: visible;
453
+ }
454
+ .pict-flow-panel {
455
+ background: var(--pf-panel-bg);
456
+ border: 1px solid var(--pf-panel-border);
457
+ border-radius: var(--pf-panel-radius);
458
+ box-shadow: var(--pf-panel-shadow);
459
+ display: flex;
460
+ flex-direction: column;
461
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
462
+ font-size: 13px;
463
+ overflow: hidden;
464
+ width: 100%;
465
+ height: 100%;
466
+ box-sizing: border-box;
467
+ }
468
+ .pict-flow-panel-titlebar {
469
+ display: flex;
470
+ align-items: center;
471
+ justify-content: space-between;
472
+ padding: 8px 12px;
473
+ background: var(--pf-panel-titlebar-bg);
474
+ border-bottom: 1px solid var(--pf-panel-titlebar-border);
475
+ cursor: grab;
476
+ user-select: none;
477
+ -webkit-user-select: none;
478
+ flex-shrink: 0;
479
+ }
480
+ .pict-flow-panel-titlebar.dragging {
481
+ cursor: grabbing;
482
+ }
483
+ .pict-flow-panel-title-text {
484
+ font-weight: 600;
485
+ font-size: 12px;
486
+ color: var(--pf-panel-title-color);
487
+ white-space: nowrap;
488
+ overflow: hidden;
489
+ text-overflow: ellipsis;
490
+ letter-spacing: 0.1px;
491
+ }
492
+ .pict-flow-panel-close-btn {
493
+ cursor: pointer;
494
+ color: #b0b8c0;
495
+ font-size: 14px;
496
+ line-height: 1;
497
+ padding: 4px;
498
+ border: none;
499
+ background: none;
500
+ border-radius: 4px;
501
+ transition: background-color 0.15s, color 0.15s;
502
+ }
503
+ .pict-flow-panel-close-btn:hover {
504
+ color: #e74c3c;
505
+ background-color: rgba(231, 76, 60, 0.08);
506
+ }
507
+ .pict-flow-panel-body {
508
+ flex: 1;
509
+ overflow: auto;
510
+ padding: 10px 12px;
511
+ }
512
+ `;
513
+ }
514
+
515
+ // ── Info Panels ────────────────────────────────────────────────────────
516
+ /**
517
+ * CSS for the info/hover panel and all sub-elements: header, description, badges, sections, ports.
518
+ * @returns {string}
519
+ */
520
+ getInfoPanelCSS()
521
+ {
522
+ return /*css*/`
523
+ .pict-flow-info-panel {
524
+ padding: 2px 0;
525
+ font-size: 12px;
526
+ line-height: 1.5;
527
+ color: #2c3e50;
528
+ }
529
+ .pict-flow-info-panel-header {
530
+ font-size: 13px;
531
+ font-weight: 600;
532
+ margin-bottom: 6px;
533
+ color: #1a252f;
534
+ }
535
+ .pict-flow-info-panel-header.with-icon {
536
+ font-size: 14px;
537
+ display: flex;
538
+ align-items: center;
539
+ gap: 6px;
540
+ }
541
+ .pict-flow-info-panel-description {
542
+ font-size: 11px;
543
+ color: #7f8c8d;
544
+ margin-bottom: 10px;
545
+ line-height: 1.45;
546
+ }
547
+ .pict-flow-info-panel-badges {
548
+ margin-bottom: 10px;
549
+ display: flex;
550
+ flex-wrap: wrap;
551
+ gap: 4px;
552
+ }
553
+ .pict-flow-info-panel-badge {
554
+ display: inline-block;
555
+ padding: 2px 8px;
556
+ border-radius: 4px;
557
+ font-size: 10px;
558
+ }
559
+ .pict-flow-info-panel-badge.category {
560
+ background: #f0f2f4;
561
+ color: #6b7b8d;
562
+ }
563
+ .pict-flow-info-panel-badge.code {
564
+ background: #eaf2f8;
565
+ color: #2980b9;
566
+ font-family: "SF Mono", "Fira Code", monospace;
567
+ }
568
+ .pict-flow-info-panel-section {
569
+ margin-bottom: 8px;
570
+ }
571
+ .pict-flow-info-panel-section-title {
572
+ font-size: 10px;
573
+ font-weight: 600;
574
+ text-transform: uppercase;
575
+ letter-spacing: 0.5px;
576
+ color: #8e99a4;
577
+ margin-bottom: 4px;
578
+ padding-bottom: 2px;
579
+ border-bottom: 1px solid #f0f2f4;
580
+ }
581
+ .pict-flow-info-panel-port {
582
+ padding: 3px 8px;
583
+ background: #f8f9fa;
584
+ margin-bottom: 3px;
585
+ font-size: 11px;
586
+ border-radius: 3px;
587
+ }
588
+ .pict-flow-info-panel-port.input {
589
+ border-left: 3px solid var(--pf-port-input-fill);
590
+ }
591
+ .pict-flow-info-panel-port.output {
592
+ border-left: 3px solid var(--pf-port-output-fill);
593
+ }
594
+ .pict-flow-info-panel-port-constraint {
595
+ color: #8e99a4;
596
+ font-size: 10px;
597
+ }
598
+ `;
599
+ }
600
+
601
+ // ── Node Properties Editor ────────────────────────────────────────────
602
+ /**
603
+ * CSS for the collapsible node properties editor at the bottom of panels.
604
+ * @returns {string}
605
+ */
606
+ getNodePropsEditorCSS()
607
+ {
608
+ return /*css*/`
609
+ .pict-flow-panel-node-props {
610
+ border-top: 1px solid var(--pf-panel-titlebar-border);
611
+ flex-shrink: 0;
612
+ }
613
+ .pict-flow-panel-node-props-header {
614
+ display: flex;
615
+ align-items: center;
616
+ gap: 6px;
617
+ padding: 6px 12px;
618
+ cursor: pointer;
619
+ user-select: none;
620
+ -webkit-user-select: none;
621
+ background: var(--pf-panel-titlebar-bg);
622
+ transition: background-color 0.15s;
623
+ }
624
+ .pict-flow-panel-node-props-header:hover {
625
+ background: #eef0f2;
626
+ }
627
+ .pict-flow-panel-node-props-chevron {
628
+ font-size: 8px;
629
+ color: #95a5a6;
630
+ transition: transform 0.2s;
631
+ display: inline-block;
632
+ }
633
+ .pict-flow-panel-node-props-chevron.expanded {
634
+ transform: rotate(90deg);
635
+ }
636
+ .pict-flow-panel-node-props-title {
637
+ font-size: 10px;
638
+ font-weight: 600;
639
+ text-transform: uppercase;
640
+ letter-spacing: 0.5px;
641
+ color: #8e99a4;
642
+ }
643
+ .pict-flow-panel-node-props-body {
644
+ padding: 8px 12px;
645
+ max-height: 240px;
646
+ overflow-y: auto;
647
+ }
648
+ .pict-flow-node-props-fields {
649
+ display: flex;
650
+ flex-direction: column;
651
+ gap: 6px;
652
+ }
653
+ .pict-flow-node-props-field {
654
+ display: flex;
655
+ align-items: center;
656
+ gap: 8px;
657
+ }
658
+ .pict-flow-node-props-label {
659
+ font-size: 11px;
660
+ color: #7f8c8d;
661
+ min-width: 72px;
662
+ flex-shrink: 0;
663
+ }
664
+ .pict-flow-node-props-input {
665
+ flex: 1;
666
+ padding: 3px 6px;
667
+ border: 1px solid #d5d8dc;
668
+ border-radius: 3px;
669
+ font-size: 11px;
670
+ outline: none;
671
+ box-sizing: border-box;
672
+ min-width: 0;
673
+ }
674
+ .pict-flow-node-props-input:focus {
675
+ border-color: #3498db;
676
+ }
677
+ .pict-flow-node-props-color {
678
+ width: 28px;
679
+ height: 24px;
680
+ padding: 1px;
681
+ cursor: pointer;
682
+ flex: 0 0 28px;
683
+ }
684
+ `;
685
+ }
686
+
687
+ // ── Fullscreen ─────────────────────────────────────────────────────────
688
+ /**
689
+ * CSS for fullscreen mode.
690
+ * @returns {string}
691
+ */
692
+ getFullscreenCSS()
693
+ {
694
+ return /*css*/`
695
+ .pict-flow-fullscreen {
696
+ position: fixed;
697
+ top: 0;
698
+ left: 0;
699
+ width: 100vw;
700
+ height: 100vh;
701
+ z-index: 9999;
702
+ border-radius: 0;
703
+ border: none;
704
+ min-height: 100vh;
705
+ }
706
+ .pict-flow-fullscreen .pict-flow-svg {
707
+ min-height: calc(100vh - 50px);
708
+ }
709
+ `;
710
+ }
711
+
712
+ // ── Toolbar ────────────────────────────────────────────────────────────
713
+ /**
714
+ * CSS for the toolbar: buttons, groups, labels, selects.
715
+ * @returns {string}
716
+ */
717
+ getToolbarCSS()
718
+ {
719
+ return /*css*/`
720
+ .pict-flow-toolbar {
721
+ display: flex;
722
+ align-items: center;
723
+ gap: 0.5em;
724
+ padding: 0.5em 0.75em;
725
+ background-color: #ffffff;
726
+ border-bottom: 1px solid #e0e0e0;
727
+ flex-wrap: wrap;
728
+ }
729
+ .pict-flow-toolbar-group {
730
+ display: flex;
731
+ align-items: center;
732
+ gap: 0.25em;
733
+ padding-right: 0.75em;
734
+ border-right: 1px solid #e0e0e0;
735
+ }
736
+ .pict-flow-toolbar-group:last-child {
737
+ border-right: none;
738
+ padding-right: 0;
739
+ }
740
+ .pict-flow-toolbar-btn {
741
+ display: inline-flex;
742
+ align-items: center;
743
+ justify-content: center;
744
+ gap: 0.35em;
745
+ padding: 0.35em 0.65em;
746
+ border: 1px solid #bdc3c7;
747
+ border-radius: 4px;
748
+ background-color: #fff;
749
+ color: #2c3e50;
750
+ font-size: 0.85em;
751
+ cursor: pointer;
752
+ transition: background-color 0.15s, border-color 0.15s;
753
+ user-select: none;
754
+ -webkit-user-select: none;
755
+ }
756
+ .pict-flow-toolbar-btn:hover {
757
+ background-color: #ecf0f1;
758
+ border-color: #95a5a6;
759
+ }
760
+ .pict-flow-toolbar-btn:active {
761
+ background-color: #d5dbdb;
762
+ }
763
+ .pict-flow-toolbar-btn.danger {
764
+ color: #e74c3c;
765
+ border-color: #e74c3c;
766
+ }
767
+ .pict-flow-toolbar-btn.danger:hover {
768
+ background-color: #fdedec;
769
+ }
770
+ .pict-flow-toolbar-btn-icon {
771
+ display: inline-flex;
772
+ align-items: center;
773
+ justify-content: center;
774
+ line-height: 1;
775
+ }
776
+ .pict-flow-toolbar-btn-icon svg {
777
+ display: block;
778
+ }
779
+ .pict-flow-toolbar-btn-text {
780
+ white-space: nowrap;
781
+ }
782
+ .pict-flow-toolbar-btn-chevron {
783
+ display: inline-flex;
784
+ align-items: center;
785
+ margin-left: 0.15em;
786
+ }
787
+ .pict-flow-toolbar-right {
788
+ margin-left: auto;
789
+ border-right: none;
790
+ padding-right: 0;
791
+ }
792
+ .pict-flow-toolbar-label {
793
+ font-size: 0.8em;
794
+ color: #7f8c8d;
795
+ margin-right: 0.25em;
796
+ }
797
+ .pict-flow-toolbar-select {
798
+ padding: 0.3em 0.5em;
799
+ border: 1px solid #bdc3c7;
800
+ border-radius: 4px;
801
+ font-size: 0.85em;
802
+ background-color: #fff;
803
+ color: #2c3e50;
804
+ }
805
+ `;
806
+ }
807
+
808
+ // ── Card Palette ───────────────────────────────────────────────────────
809
+ /**
810
+ * CSS for the card palette: toggle, body, categories, cards, swatches.
811
+ * @returns {string}
812
+ */
813
+ getPaletteCSS()
814
+ {
815
+ return /*css*/`
816
+ .pict-flow-palette-category {
817
+ margin-bottom: 0.5em;
818
+ }
819
+ .pict-flow-palette-category:last-child {
820
+ margin-bottom: 0;
821
+ }
822
+ .pict-flow-palette-category-label {
823
+ font-size: 0.7em;
824
+ font-weight: 700;
825
+ text-transform: uppercase;
826
+ letter-spacing: 0.05em;
827
+ color: #95a5a6;
828
+ margin-bottom: 0.35em;
829
+ padding-bottom: 0.2em;
830
+ border-bottom: 1px solid #ecf0f1;
831
+ }
832
+ .pict-flow-palette-cards {
833
+ display: flex;
834
+ flex-wrap: wrap;
835
+ gap: 0.35em;
836
+ }
837
+ .pict-flow-palette-card {
838
+ display: inline-flex;
839
+ align-items: center;
840
+ gap: 0.35em;
841
+ padding: 0.35em 0.6em;
842
+ border: 1px solid #d5d8dc;
843
+ border-radius: 4px;
844
+ background-color: #ffffff;
845
+ font-size: 0.8em;
846
+ cursor: pointer;
847
+ transition: background-color 0.15s, border-color 0.15s, box-shadow 0.15s;
848
+ user-select: none;
849
+ -webkit-user-select: none;
850
+ position: relative;
851
+ }
852
+ .pict-flow-palette-card:hover {
853
+ background-color: #eaf2f8;
854
+ border-color: #3498db;
855
+ box-shadow: 0 1px 3px rgba(52, 152, 219, 0.15);
856
+ }
857
+ .pict-flow-palette-card.disabled {
858
+ opacity: 0.45;
859
+ pointer-events: none;
860
+ cursor: default;
861
+ }
862
+ .pict-flow-palette-card-icon {
863
+ font-size: 1.1em;
864
+ line-height: 1;
865
+ }
866
+ .pict-flow-palette-card-swatch {
867
+ width: 10px;
868
+ height: 10px;
869
+ border-radius: 2px;
870
+ flex-shrink: 0;
871
+ }
872
+ .pict-flow-palette-card-title {
873
+ font-weight: 500;
874
+ color: #2c3e50;
875
+ white-space: nowrap;
876
+ }
877
+ .pict-flow-palette-card-code {
878
+ font-size: 0.8em;
879
+ color: #95a5a6;
880
+ font-family: monospace;
881
+ }
882
+ .pict-flow-toolbar-select.layout-select {
883
+ min-width: 120px;
884
+ max-width: 200px;
885
+ }
886
+ `;
887
+ }
888
+
889
+ // ── Popups ────────────────────────────────────────────────────────────
890
+ /**
891
+ * CSS for toolbar dropdown popups (Add Node, Cards, Layout).
892
+ * @returns {string}
893
+ */
894
+ getPopupCSS()
895
+ {
896
+ return /*css*/`
897
+ .pict-flow-toolbar-popup-anchor {
898
+ position: relative;
899
+ }
900
+ .pict-flow-toolbar-popup {
901
+ position: absolute;
902
+ z-index: 1000;
903
+ background: #ffffff;
904
+ border: 1px solid #d5d8dc;
905
+ border-radius: 6px;
906
+ box-shadow: 0 4px 16px rgba(0,0,0,0.12);
907
+ min-width: 240px;
908
+ max-height: 340px;
909
+ overflow-y: auto;
910
+ padding: 0.35em 0;
911
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
912
+ font-size: 13px;
913
+ }
914
+ .pict-flow-popup-search-wrapper {
915
+ position: relative;
916
+ padding: 0.4em 0.5em;
917
+ border-bottom: 1px solid #ecf0f1;
918
+ }
919
+ .pict-flow-popup-search-icon {
920
+ position: absolute;
921
+ left: 0.85em;
922
+ top: 50%;
923
+ transform: translateY(-50%);
924
+ pointer-events: none;
925
+ line-height: 1;
926
+ display: flex;
927
+ align-items: center;
928
+ }
929
+ .pict-flow-popup-search {
930
+ width: 100%;
931
+ padding: 0.4em 0.5em 0.4em 2em;
932
+ border: 1px solid #d5d8dc;
933
+ border-radius: 4px;
934
+ font-size: 0.9em;
935
+ outline: none;
936
+ box-sizing: border-box;
937
+ }
938
+ .pict-flow-popup-search:focus {
939
+ border-color: #3498db;
940
+ }
941
+ .pict-flow-popup-list-item {
942
+ display: flex;
943
+ align-items: center;
944
+ gap: 0.5em;
945
+ padding: 0.45em 0.75em;
946
+ cursor: pointer;
947
+ transition: background-color 0.1s;
948
+ }
949
+ .pict-flow-popup-list-item:hover {
950
+ background-color: #eaf2f8;
951
+ }
952
+ .pict-flow-popup-list-item-icon {
953
+ display: inline-flex;
954
+ align-items: center;
955
+ flex-shrink: 0;
956
+ line-height: 1;
957
+ }
958
+ .pict-flow-popup-list-item-label {
959
+ flex: 1;
960
+ color: #2c3e50;
961
+ font-weight: 500;
962
+ }
963
+ .pict-flow-popup-list-item-code {
964
+ font-size: 0.8em;
965
+ color: #95a5a6;
966
+ font-family: monospace;
967
+ background: #f0f3f5;
968
+ padding: 0.1em 0.4em;
969
+ border-radius: 3px;
970
+ }
971
+ .pict-flow-popup-divider {
972
+ height: 1px;
973
+ background: #ecf0f1;
974
+ margin: 0.25em 0;
975
+ }
976
+ .pict-flow-popup-list-empty {
977
+ text-align: center;
978
+ color: #95a5a6;
979
+ padding: 1.5em 0.75em;
980
+ font-size: 0.9em;
981
+ }
982
+ .pict-flow-popup-layout-save {
983
+ display: flex;
984
+ align-items: center;
985
+ gap: 0.5em;
986
+ padding: 0.45em 0.75em;
987
+ cursor: pointer;
988
+ transition: background-color 0.1s;
989
+ color: #2c3e50;
990
+ font-weight: 500;
991
+ }
992
+ .pict-flow-popup-layout-save:hover {
993
+ background-color: #eaf2f8;
994
+ }
995
+ .pict-flow-popup-layout-save-icon {
996
+ display: inline-flex;
997
+ align-items: center;
998
+ flex-shrink: 0;
999
+ line-height: 1;
1000
+ }
1001
+ .pict-flow-popup-layout-save-input-row {
1002
+ display: flex;
1003
+ align-items: center;
1004
+ gap: 0.35em;
1005
+ padding: 0.35em 0.5em;
1006
+ }
1007
+ .pict-flow-popup-layout-save-input {
1008
+ flex: 1;
1009
+ padding: 0.35em 0.5em;
1010
+ border: 1px solid #d5d8dc;
1011
+ border-radius: 4px;
1012
+ font-size: 0.9em;
1013
+ outline: none;
1014
+ box-sizing: border-box;
1015
+ }
1016
+ .pict-flow-popup-layout-save-input:focus {
1017
+ border-color: #3498db;
1018
+ }
1019
+ .pict-flow-popup-layout-save-confirm {
1020
+ display: inline-flex;
1021
+ align-items: center;
1022
+ justify-content: center;
1023
+ width: 28px;
1024
+ height: 28px;
1025
+ border: 1px solid #d5d8dc;
1026
+ border-radius: 4px;
1027
+ background: #fff;
1028
+ cursor: pointer;
1029
+ flex-shrink: 0;
1030
+ transition: background-color 0.15s, border-color 0.15s;
1031
+ line-height: 1;
1032
+ }
1033
+ .pict-flow-popup-layout-save-confirm:hover {
1034
+ background-color: #eaf2f8;
1035
+ border-color: #3498db;
1036
+ }
1037
+ .pict-flow-popup-layout-row {
1038
+ display: flex;
1039
+ align-items: center;
1040
+ padding: 0.45em 0.75em;
1041
+ cursor: pointer;
1042
+ transition: background-color 0.1s;
1043
+ }
1044
+ .pict-flow-popup-layout-row:hover {
1045
+ background-color: #eaf2f8;
1046
+ }
1047
+ .pict-flow-popup-layout-name {
1048
+ flex: 1;
1049
+ color: #2c3e50;
1050
+ }
1051
+ .pict-flow-popup-layout-delete {
1052
+ display: none;
1053
+ align-items: center;
1054
+ justify-content: center;
1055
+ border: none;
1056
+ background: none;
1057
+ color: #e74c3c;
1058
+ cursor: pointer;
1059
+ padding: 2px 4px;
1060
+ border-radius: 3px;
1061
+ line-height: 1;
1062
+ }
1063
+ .pict-flow-popup-layout-row:hover .pict-flow-popup-layout-delete {
1064
+ display: inline-flex;
1065
+ }
1066
+ .pict-flow-popup-layout-delete:hover {
1067
+ background-color: #fdedec;
1068
+ }
1069
+ .pict-flow-popup-settings-section {
1070
+ padding: 0.5em 0.75em;
1071
+ }
1072
+ .pict-flow-popup-settings-label {
1073
+ display: block;
1074
+ font-size: 0.8em;
1075
+ font-weight: 600;
1076
+ color: #7f8c8d;
1077
+ text-transform: uppercase;
1078
+ letter-spacing: 0.05em;
1079
+ margin-bottom: 0.35em;
1080
+ }
1081
+ .pict-flow-popup-settings-select {
1082
+ width: 100%;
1083
+ padding: 0.4em 0.5em;
1084
+ border: 1px solid #d5d8dc;
1085
+ border-radius: 4px;
1086
+ font-size: 0.9em;
1087
+ background: #fff;
1088
+ color: #2c3e50;
1089
+ cursor: pointer;
1090
+ outline: none;
1091
+ box-sizing: border-box;
1092
+ }
1093
+ .pict-flow-popup-settings-select:focus {
1094
+ border-color: #3498db;
1095
+ }
1096
+ .pict-flow-popup-settings-slider-row {
1097
+ display: flex;
1098
+ align-items: center;
1099
+ gap: 0.5em;
1100
+ }
1101
+ .pict-flow-popup-settings-slider {
1102
+ flex: 1;
1103
+ -webkit-appearance: none;
1104
+ appearance: none;
1105
+ height: 4px;
1106
+ background: #d5d8dc;
1107
+ border-radius: 2px;
1108
+ outline: none;
1109
+ cursor: pointer;
1110
+ }
1111
+ .pict-flow-popup-settings-slider::-webkit-slider-thumb {
1112
+ -webkit-appearance: none;
1113
+ appearance: none;
1114
+ width: 14px;
1115
+ height: 14px;
1116
+ background: #3498db;
1117
+ border-radius: 50%;
1118
+ cursor: pointer;
1119
+ }
1120
+ .pict-flow-popup-settings-slider::-moz-range-thumb {
1121
+ width: 14px;
1122
+ height: 14px;
1123
+ background: #3498db;
1124
+ border-radius: 50%;
1125
+ cursor: pointer;
1126
+ border: none;
1127
+ }
1128
+ .pict-flow-popup-settings-slider-value {
1129
+ font-size: 0.85em;
1130
+ color: #7f8c8d;
1131
+ min-width: 2.5em;
1132
+ text-align: right;
1133
+ }
1134
+ `;
1135
+ }
1136
+
1137
+ // ── Collapsed Toolbar ─────────────────────────────────────────────────
1138
+ /**
1139
+ * CSS for the collapsed toolbar state (small expand button in corner).
1140
+ * @returns {string}
1141
+ */
1142
+ getCollapsedToolbarCSS()
1143
+ {
1144
+ return /*css*/`
1145
+ .pict-flow-toolbar-collapsed {
1146
+ position: absolute;
1147
+ top: 8px;
1148
+ right: 8px;
1149
+ z-index: 100;
1150
+ display: none;
1151
+ }
1152
+ .pict-flow-toolbar-collapsed.visible {
1153
+ display: block;
1154
+ }
1155
+ .pict-flow-toolbar-expand-btn {
1156
+ width: 36px;
1157
+ height: 36px;
1158
+ border-radius: 6px;
1159
+ border: 1px solid #d5d8dc;
1160
+ background-color: #ffffff;
1161
+ box-shadow: 0 2px 6px rgba(0,0,0,0.1);
1162
+ cursor: pointer;
1163
+ display: flex;
1164
+ align-items: center;
1165
+ justify-content: center;
1166
+ transition: background-color 0.15s, box-shadow 0.15s;
1167
+ }
1168
+ .pict-flow-toolbar-expand-btn:hover {
1169
+ background-color: #ecf0f1;
1170
+ box-shadow: 0 2px 8px rgba(0,0,0,0.15);
1171
+ }
1172
+ `;
1173
+ }
1174
+
1175
+ // ── Floating Toolbar ──────────────────────────────────────────────────
1176
+ /**
1177
+ * CSS for the floating draggable toolbar.
1178
+ * @returns {string}
1179
+ */
1180
+ getFloatingToolbarCSS()
1181
+ {
1182
+ return /*css*/`
1183
+ .pict-flow-floating-toolbar {
1184
+ position: absolute;
1185
+ z-index: 100;
1186
+ display: flex;
1187
+ flex-direction: column;
1188
+ gap: 2px;
1189
+ padding: 4px;
1190
+ border-radius: 8px;
1191
+ border: 1px solid #d5d8dc;
1192
+ background-color: #ffffff;
1193
+ box-shadow: 0 4px 16px rgba(0,0,0,0.12);
1194
+ pointer-events: auto;
1195
+ }
1196
+ .pict-flow-floating-grip {
1197
+ cursor: grab;
1198
+ padding: 4px;
1199
+ border-radius: 4px;
1200
+ display: flex;
1201
+ align-items: center;
1202
+ justify-content: center;
1203
+ transition: background-color 0.15s;
1204
+ }
1205
+ .pict-flow-floating-grip:hover {
1206
+ background-color: #ecf0f1;
1207
+ }
1208
+ .pict-flow-floating-grip:active {
1209
+ cursor: grabbing;
1210
+ }
1211
+ .pict-flow-floating-btn {
1212
+ width: 32px;
1213
+ height: 32px;
1214
+ border: none;
1215
+ border-radius: 4px;
1216
+ background-color: transparent;
1217
+ cursor: pointer;
1218
+ display: flex;
1219
+ align-items: center;
1220
+ justify-content: center;
1221
+ transition: background-color 0.15s;
1222
+ }
1223
+ .pict-flow-floating-btn:hover {
1224
+ background-color: #ecf0f1;
1225
+ }
1226
+ .pict-flow-floating-btn.danger:hover {
1227
+ background-color: #fdedec;
1228
+ }
1229
+ .pict-flow-floating-separator {
1230
+ height: 1px;
1231
+ background-color: #ecf0f1;
1232
+ margin: 2px 4px;
1233
+ }
1234
+ /* Collapsed floating toolbar — grip-only draggable square */
1235
+ .pict-flow-floating-toolbar.collapsed .pict-flow-floating-btn,
1236
+ .pict-flow-floating-toolbar.collapsed .pict-flow-floating-separator {
1237
+ display: none;
1238
+ }
1239
+ .pict-flow-floating-toolbar.collapsed {
1240
+ padding: 0;
1241
+ border-radius: 6px;
1242
+ }
1243
+ .pict-flow-floating-toolbar.collapsed .pict-flow-floating-grip {
1244
+ width: 32px;
1245
+ height: 32px;
1246
+ padding: 0;
1247
+ display: flex;
1248
+ align-items: center;
1249
+ justify-content: center;
1250
+ }
1251
+ .pict-flow-floating-toolbar.collapsed .pict-flow-floating-grip span {
1252
+ display: flex;
1253
+ align-items: center;
1254
+ justify-content: center;
1255
+ line-height: 1;
1256
+ }
1257
+ `;
1258
+ }
1259
+
1260
+ // ── Icons ─────────────────────────────────────────────────────────────
1261
+ /**
1262
+ * CSS for inline SVG icons in palette cards, toolbar buttons, info panel headers, and panel close buttons.
1263
+ * @returns {string}
1264
+ */
1265
+ getIconCSS()
1266
+ {
1267
+ return /*css*/`
1268
+ .pict-flow-icon-svg {
1269
+ pointer-events: none;
1270
+ }
1271
+ .pict-flow-palette-card-icon svg {
1272
+ display: inline-block;
1273
+ vertical-align: middle;
1274
+ }
1275
+ .pict-flow-toolbar-btn-icon {
1276
+ display: inline-flex;
1277
+ align-items: center;
1278
+ justify-content: center;
1279
+ line-height: 1;
1280
+ }
1281
+ .pict-flow-toolbar-btn-icon svg {
1282
+ display: block;
1283
+ vertical-align: middle;
1284
+ }
1285
+ .pict-flow-info-panel-header.with-icon svg {
1286
+ display: inline-block;
1287
+ vertical-align: middle;
1288
+ margin-right: 4px;
1289
+ }
1290
+ .pict-flow-panel-close-icon {
1291
+ display: inline-flex;
1292
+ align-items: center;
1293
+ justify-content: center;
1294
+ line-height: 1;
1295
+ }
1296
+ .pict-flow-panel-close-icon svg {
1297
+ display: block;
1298
+ }
1299
+ .pict-flow-palette-toggle-arrow svg {
1300
+ display: block;
1301
+ }
1302
+ `;
1303
+ }
1304
+
1305
+ // ── Bracket Node CSS ──────────────────────────────────────────────────
1306
+ /**
1307
+ * CSS for bracket-style node bodies used by sketch/blueprint themes.
1308
+ *
1309
+ * The bracket fill rects share class `.pict-flow-node-body` for fill
1310
+ * inheritance, so these rules must use parent-qualified selectors
1311
+ * (specificity ≥ 0,2,0) to override the base, variant, hover, and
1312
+ * selected rules that set stroke and rx/ry on `.pict-flow-node-body`.
1313
+ *
1314
+ * @returns {string}
1315
+ */
1316
+ getBracketNodeCSS()
1317
+ {
1318
+ return /*css*/`
1319
+ /* Bracket outline path */
1320
+ .pict-flow-node-bracket {
1321
+ fill: none;
1322
+ stroke: var(--pf-node-body-stroke);
1323
+ stroke-width: 2;
1324
+ stroke-linecap: round;
1325
+ stroke-linejoin: round;
1326
+ }
1327
+ .pict-flow-node.selected .pict-flow-node-bracket {
1328
+ stroke: var(--pf-node-selected-stroke);
1329
+ stroke-width: 2;
1330
+ }
1331
+ .pict-flow-node:hover .pict-flow-node-bracket {
1332
+ stroke: #b0b8c0;
1333
+ stroke-width: 1.5;
1334
+ }
1335
+
1336
+ /* Bracket fill rects: no stroke, no rounded corners.
1337
+ Uses parent-qualified selectors to beat variant rules
1338
+ (e.g. .pict-flow-node-start .pict-flow-node-body). */
1339
+ .pict-flow-node .pict-flow-node-bracket-fill,
1340
+ .pict-flow-node .pict-flow-node-bracket-title-fill {
1341
+ stroke: none;
1342
+ stroke-width: 0;
1343
+ rx: 0;
1344
+ ry: 0;
1345
+ }
1346
+ /* Beat hover rule: .pict-flow-node:hover .pict-flow-node-body */
1347
+ .pict-flow-node:hover .pict-flow-node-bracket-fill,
1348
+ .pict-flow-node:hover .pict-flow-node-bracket-title-fill {
1349
+ stroke: none;
1350
+ stroke-width: 0;
1351
+ }
1352
+ /* Beat selected rule: .pict-flow-node.selected .pict-flow-node-body */
1353
+ .pict-flow-node.selected .pict-flow-node-bracket-fill,
1354
+ .pict-flow-node.selected .pict-flow-node-bracket-title-fill {
1355
+ stroke: none;
1356
+ stroke-width: 0;
1357
+ }
1358
+ `;
1359
+ }
1360
+
1361
+ // ── Aggregate Methods ──────────────────────────────────────────────────
1362
+
1363
+ /**
1364
+ * Concatenate all domain CSS getters into a single CSS string.
1365
+ * Includes theme overrides if a theme provider is active.
1366
+ * @returns {string}
1367
+ */
1368
+ generateCSS()
1369
+ {
1370
+ let tmpBaseCSS = (
1371
+ this.getContainerCSS() +
1372
+ this.getNodeCSS() +
1373
+ this.getBodyContentCSS() +
1374
+ this.getNodeVariantCSS() +
1375
+ this.getPortCSS() +
1376
+ this.getConnectionCSS() +
1377
+ this.getHandleCSS() +
1378
+ this.getTetherCSS() +
1379
+ this.getPanelCSS() +
1380
+ this.getInfoPanelCSS() +
1381
+ this.getNodePropsEditorCSS() +
1382
+ this.getBracketNodeCSS() +
1383
+ this.getFullscreenCSS() +
1384
+ this.getToolbarCSS() +
1385
+ this.getPaletteCSS() +
1386
+ this.getPopupCSS() +
1387
+ this.getCollapsedToolbarCSS() +
1388
+ this.getFloatingToolbarCSS() +
1389
+ this.getIconCSS()
1390
+ );
1391
+
1392
+ // Apply theme overrides if a theme provider exists
1393
+ if (this._FlowView && this._FlowView._ThemeProvider)
1394
+ {
1395
+ let tmpTheme = this._FlowView._ThemeProvider.getActiveTheme();
1396
+ if (tmpTheme && tmpTheme.CSSVariables && Object.keys(tmpTheme.CSSVariables).length > 0)
1397
+ {
1398
+ let tmpOverrides = '.pict-flow-container {\n';
1399
+ for (let tmpKey in tmpTheme.CSSVariables)
1400
+ {
1401
+ tmpOverrides += '\t' + tmpKey + ': ' + tmpTheme.CSSVariables[tmpKey] + ';\n';
1402
+ }
1403
+ tmpOverrides += '}\n';
1404
+ tmpBaseCSS += tmpOverrides;
1405
+ }
1406
+ if (tmpTheme && tmpTheme.AdditionalCSS)
1407
+ {
1408
+ tmpBaseCSS += tmpTheme.AdditionalCSS;
1409
+ }
1410
+ }
1411
+
1412
+ return tmpBaseCSS;
1413
+ }
1414
+
1415
+ /**
1416
+ * Register all flow CSS with pict's CSSMap service.
1417
+ * Uses correct parameter ordering: (hash, content, priority, provider).
1418
+ * Removes existing CSS first to allow theme re-registration,
1419
+ * then re-injects into the DOM.
1420
+ */
1421
+ registerCSS()
1422
+ {
1423
+ if (this.fable && this.fable.CSSMap)
1424
+ {
1425
+ // Remove existing CSS first so we can re-register with updated theme overrides
1426
+ this.fable.CSSMap.removeCSS('PictSectionFlow-CSS');
1427
+ this.fable.CSSMap.addCSS('PictSectionFlow-CSS', this.generateCSS(), 500, 'PictProviderFlowCSS');
1428
+ // Re-inject into the DOM to apply the updated CSS
1429
+ this.fable.CSSMap.injectCSS();
1430
+ }
1431
+ else
1432
+ {
1433
+ this.log.warn('PictProviderFlowCSS: CSSMap not available; CSS not registered.');
1434
+ }
1435
+ }
1436
+ }
1437
+
1438
+ module.exports = PictProviderFlowCSS;
1439
+
1440
+ module.exports.default_configuration = _ProviderConfiguration;