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
@@ -55,6 +55,10 @@ const _DefaultConfiguration =
55
55
  {
56
56
  Hash: 'Flow-InfoPanel-Port-Constraint',
57
57
  Template: ' <span class="pict-flow-info-panel-port-constraint">{~D:Record.ConstraintText~}</span>'
58
+ },
59
+ {
60
+ Hash: 'Flow-NodeProps-Editor',
61
+ Template: '<div class="pict-flow-node-props-fields"><div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Title</label><input type="text" class="pict-flow-node-props-input" data-prop="Title" value="{~D:Record.Title~}" /></div><div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Width</label><input type="number" class="pict-flow-node-props-input" data-prop="Width" value="{~D:Record.Width~}" min="60" step="10" /></div><div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Height</label><input type="number" class="pict-flow-node-props-input" data-prop="Height" value="{~D:Record.Height~}" min="40" step="10" /></div><div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Body Fill</label><input type="color" class="pict-flow-node-props-input pict-flow-node-props-color" data-prop="Style.BodyFill" value="{~D:Record.BodyFillValue~}" /></div><div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Body Stroke</label><input type="color" class="pict-flow-node-props-input pict-flow-node-props-color" data-prop="Style.BodyStroke" value="{~D:Record.BodyStrokeValue~}" /></div><div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Stroke Width</label><input type="number" class="pict-flow-node-props-input" data-prop="Style.BodyStrokeWidth" value="{~D:Record.BodyStrokeWidthValue~}" min="0" max="10" step="0.5" /></div><div class="pict-flow-node-props-field"><label class="pict-flow-node-props-label">Title Bar</label><input type="color" class="pict-flow-node-props-input pict-flow-node-props-color" data-prop="Style.TitleBarColor" value="{~D:Record.TitleBarColorValue~}" /></div></div>'
58
62
  }
59
63
  ]
60
64
  };
@@ -192,6 +196,13 @@ class PictViewFlowPropertiesPanel extends libPictView
192
196
  {
193
197
  this._renderPanelContent(pPanelData, tmpBody);
194
198
  }
199
+
200
+ // Render the collapsible node properties editor at the bottom of the panel
201
+ let tmpFO = pPanelsLayer.querySelector(`[data-panel-hash="${pPanelData.Hash}"]`);
202
+ if (tmpFO)
203
+ {
204
+ this._renderNodePropsEditor(pPanelData, tmpFO);
205
+ }
195
206
  }
196
207
 
197
208
  /**
@@ -279,10 +290,24 @@ class PictViewFlowPropertiesPanel extends libPictView
279
290
  let tmpContentParts = [];
280
291
 
281
292
  // Header
282
- if (tmpMeta.Icon)
293
+ let tmpIconProvider = this._FlowView._IconProvider;
294
+ if (tmpMeta.Icon && tmpIconProvider && !tmpIconProvider.isEmojiIcon(tmpMeta.Icon))
295
+ {
296
+ // SVG icon markup for the header
297
+ let tmpResolvedKey = tmpIconProvider.resolveIconKey(tmpMeta);
298
+ let tmpIconMarkup = tmpIconProvider.getIconSVGMarkup(tmpResolvedKey, 18);
299
+ tmpContentParts.push(this.pict.parseTemplateByHash('Flow-InfoPanel-Header-Icon', { Icon: tmpIconMarkup, Label: tmpLabel }));
300
+ }
301
+ else if (tmpMeta.Icon)
283
302
  {
284
303
  tmpContentParts.push(this.pict.parseTemplateByHash('Flow-InfoPanel-Header-Icon', { Icon: tmpMeta.Icon, Label: tmpLabel }));
285
304
  }
305
+ else if (tmpIconProvider)
306
+ {
307
+ // No icon specified — render default fallback
308
+ let tmpDefaultMarkup = tmpIconProvider.getIconSVGMarkup('default', 18);
309
+ tmpContentParts.push(this.pict.parseTemplateByHash('Flow-InfoPanel-Header-Icon', { Icon: tmpDefaultMarkup, Label: tmpLabel }));
310
+ }
286
311
  else
287
312
  {
288
313
  tmpContentParts.push(this.pict.parseTemplateByHash('Flow-InfoPanel-Header', { Label: tmpLabel }));
@@ -368,6 +393,156 @@ class PictViewFlowPropertiesPanel extends libPictView
368
393
  return '';
369
394
  }
370
395
 
396
+ /**
397
+ * Render the collapsible node properties editor into a panel's foreignObject.
398
+ * Populates the editor fields with current node values and wires up live
399
+ * change handlers for immediate visual feedback.
400
+ *
401
+ * @param {Object} pPanelData - Panel data from OpenPanels
402
+ * @param {Element} pForeignObject - The panel's SVG foreignObject element
403
+ */
404
+ _renderNodePropsEditor(pPanelData, pForeignObject)
405
+ {
406
+ let tmpNodeData = this._FlowView.getNode(pPanelData.NodeHash);
407
+ if (!tmpNodeData) return;
408
+
409
+ let tmpPropsBody = pForeignObject.querySelector('.pict-flow-panel-node-props-body');
410
+ if (!tmpPropsBody) return;
411
+
412
+ // Build the template record with safe defaults for Style values
413
+ let tmpStyle = tmpNodeData.Style || {};
414
+
415
+ // Resolve default colors from the node type config or CSS token defaults
416
+ let tmpNodeTypeConfig = this._FlowView._NodeTypeProvider.getNodeType(tmpNodeData.Type);
417
+ let tmpDefaultTitleBarColor = '#2c3e50';
418
+ let tmpDefaultBodyFill = '#ffffff';
419
+ let tmpDefaultBodyStroke = '#d0d4d8';
420
+ if (tmpNodeTypeConfig)
421
+ {
422
+ if (tmpNodeTypeConfig.TitleBarColor) tmpDefaultTitleBarColor = tmpNodeTypeConfig.TitleBarColor;
423
+ if (tmpNodeTypeConfig.BodyStyle)
424
+ {
425
+ if (tmpNodeTypeConfig.BodyStyle.fill) tmpDefaultBodyFill = tmpNodeTypeConfig.BodyStyle.fill;
426
+ if (tmpNodeTypeConfig.BodyStyle.stroke) tmpDefaultBodyStroke = tmpNodeTypeConfig.BodyStyle.stroke;
427
+ }
428
+ }
429
+
430
+ let tmpRecord =
431
+ {
432
+ Title: tmpNodeData.Title || '',
433
+ Width: tmpNodeData.Width || 180,
434
+ Height: tmpNodeData.Height || 80,
435
+ BodyFillValue: tmpStyle.BodyFill || tmpDefaultBodyFill,
436
+ BodyStrokeValue: tmpStyle.BodyStroke || tmpDefaultBodyStroke,
437
+ BodyStrokeWidthValue: tmpStyle.BodyStrokeWidth || 1,
438
+ TitleBarColorValue: tmpStyle.TitleBarColor || tmpDefaultTitleBarColor
439
+ };
440
+
441
+ tmpPropsBody.innerHTML = this.pict.parseTemplateByHash('Flow-NodeProps-Editor', tmpRecord);
442
+
443
+ // Wire up the expand/collapse toggle with dynamic panel height adjustment
444
+ let tmpHeader = pForeignObject.querySelector('.pict-flow-panel-node-props-header');
445
+ if (tmpHeader)
446
+ {
447
+ // Store the original panel height before the section was expanded
448
+ let tmpOriginalHeight = parseInt(pForeignObject.getAttribute('height'), 10) || 200;
449
+
450
+ tmpHeader.addEventListener('click', (pEvent) =>
451
+ {
452
+ pEvent.stopPropagation();
453
+ let tmpIsExpanded = tmpPropsBody.style.display !== 'none';
454
+ tmpPropsBody.style.display = tmpIsExpanded ? 'none' : 'block';
455
+ let tmpChevron = tmpHeader.querySelector('.pict-flow-panel-node-props-chevron');
456
+ if (tmpChevron)
457
+ {
458
+ tmpChevron.classList.toggle('expanded', !tmpIsExpanded);
459
+ }
460
+
461
+ // Resize the foreignObject to accommodate the expanded/collapsed section
462
+ let tmpEditorHeight = tmpIsExpanded ? 0 : tmpPropsBody.scrollHeight;
463
+ let tmpNewHeight = tmpOriginalHeight + tmpEditorHeight;
464
+ pForeignObject.setAttribute('height', String(tmpNewHeight));
465
+
466
+ // Update the panel data so tethers and position tracking stay in sync
467
+ let tmpPanelDataEntry = this._FlowView._FlowData.OpenPanels.find(
468
+ (pPanel) => pPanel.Hash === pPanelData.Hash);
469
+ if (tmpPanelDataEntry)
470
+ {
471
+ tmpPanelDataEntry.Height = tmpNewHeight;
472
+ }
473
+ });
474
+ }
475
+
476
+ // Wire up live change handlers on all input fields
477
+ let tmpInputs = tmpPropsBody.querySelectorAll('.pict-flow-node-props-input');
478
+ for (let i = 0; i < tmpInputs.length; i++)
479
+ {
480
+ let tmpInput = tmpInputs[i];
481
+ let tmpProp = tmpInput.getAttribute('data-prop');
482
+
483
+ tmpInput.addEventListener('input', (pEvent) =>
484
+ {
485
+ pEvent.stopPropagation();
486
+ this._applyNodePropChange(pPanelData.NodeHash, tmpProp, tmpInput.value, tmpInput.type);
487
+ });
488
+
489
+ // Prevent pointer events from propagating to SVG drag handler
490
+ tmpInput.addEventListener('pointerdown', (pEvent) => { pEvent.stopPropagation(); });
491
+ }
492
+ }
493
+
494
+ /**
495
+ * Apply a node property change from the properties editor and re-render.
496
+ *
497
+ * @param {string} pNodeHash - Hash of the node to update
498
+ * @param {string} pPropPath - Property path (e.g. 'Title', 'Width', 'Style.BodyFill')
499
+ * @param {string} pValue - The new value from the input
500
+ * @param {string} pInputType - The input element type ('text', 'number', 'color')
501
+ */
502
+ _applyNodePropChange(pNodeHash, pPropPath, pValue, pInputType)
503
+ {
504
+ let tmpNodeData = this._FlowView.getNode(pNodeHash);
505
+ if (!tmpNodeData) return;
506
+
507
+ // Parse numeric values
508
+ let tmpValue = pValue;
509
+ if (pInputType === 'number')
510
+ {
511
+ tmpValue = parseFloat(pValue);
512
+ if (isNaN(tmpValue)) return;
513
+ }
514
+
515
+ // Apply the value based on the property path
516
+ if (pPropPath === 'Title')
517
+ {
518
+ tmpNodeData.Title = tmpValue;
519
+ }
520
+ else if (pPropPath === 'Width')
521
+ {
522
+ tmpNodeData.Width = tmpValue;
523
+ }
524
+ else if (pPropPath === 'Height')
525
+ {
526
+ tmpNodeData.Height = tmpValue;
527
+ }
528
+ else if (pPropPath.startsWith('Style.'))
529
+ {
530
+ if (!tmpNodeData.Style) tmpNodeData.Style = {};
531
+ let tmpStyleKey = pPropPath.substring(6); // Remove 'Style.' prefix
532
+ tmpNodeData.Style[tmpStyleKey] = tmpValue;
533
+ }
534
+
535
+ // Re-render the flow to reflect changes
536
+ this._FlowView.renderFlow();
537
+ this._FlowView.marshalFromView();
538
+
539
+ // Fire change event
540
+ if (this._FlowView._EventHandlerProvider)
541
+ {
542
+ this._FlowView._EventHandlerProvider.fireEvent('onFlowChanged', this._FlowView._FlowData);
543
+ }
544
+ }
545
+
371
546
  /**
372
547
  * Render a tether from a panel to its node.
373
548
  * Delegates to the TetherService for geometry, path generation, and SVG element creation.